Todd Sundsted
Better dead than bored.
🌎Sector 001

The Cost of Small Methods

Ktistec uses a template engine for it's views.

View templates are transformed into Crystal code that generates HTML when executed. As you'd expect, the template language allows you to use string interpolation syntax (e.g. #{expression}) for dynamic values.

To ensure expression is only evaluated once, and to limit the scope of the temporary variable holding the evaluated value of expression, I originally bound the value to the variable using Object#tap (commit 5e1bf19e). The generated code looked something like:

(expression).tap do |__value__|
   <template code that uses expression>

Blocks in Crystal are always inlined, so the code above should be equivalent to the following (sacrificing local scope):

__value__ = (expression)
<template code that uses expression>

Functionally, they are equivalent. But operationally, not so much! With Object#tap, the Ktistec executable is about 1% larger (36823603 bytes vs. 36526307 bytes) and build times take 20% longer (23 seconds vs. 19 seconds, generally).

In total, view templates represent about 6% of the Ktistec executable by size, so it doesn't surprise me that there's a measurable impact when I make changes to the template engine, but wow...! I can almost live with the size of the executable, but the build time...!

The cost has to be the method call.

What I'm looking for is something like let in Scheme. The following macro comes close, but doesn't limit scope quite the same way:

macro let(expr, &block)
  {{block.args.first}} = ({{expr}})

I maybe have to live with the macro—I tried to implement let as a method with the annotation @[AlwaysInline] but there was no improvement over the original.

The template engine is a fork of Slang—which I've been evolving to be more Slim-compatible.

#ktistec #crystallang

HertzDevil is in need of a new job. You probably know who he is: Core Team member since mid 2022, and main developer behind the recent advances in Windows support. And that’s not only it, he has done lots of work in very distant parts of the compiler and ecosystem. You’ll have fun reading through the almost 700 PRs¹ that he got in.

As his team lead this past year, I’m sorry to let go such an amazing person. Funding reasons forced us to. I have a privileged spot to see him working, an experience I can share with anyone interested. Quite frankly, he’s the Messi of Software Engineering, but with a humble heart!

#fedihired #crystallang #jobsearch


getting into the spirit of mardi gras early by getting my funky meters albums out!

i installed ollama. 20 minutes later i had llama2 running locally. almost all of that time was downloading the pre-trained model. performance and quality are better than i expected! (technical reference point: i'm on a macbook m3 max with 48gb of memory but that does not seem to be in any way required—i'm going to try some older hardware later.)

i'm still trying to sort out which llms are really open source (architecture, source code, training data, weights) and which are not.

#ollama #llama2

i feel like watching those new zealand tourism videos i mean the lord of the rings...

two architectural decision records down. writing is hard work!

prompt: "create a boring painting"

the best part is that there's also no way to get around that desk...!


I replaced five indexes* on the relationships table with two**, improved query performance in at least one case, and cut the size of the database down by 11.4% (98MB).

Lessons (finally) learned:

  1. You can have too many indexes. At best, this makes the database larger. In at least one case, however, this caused the query planner to pick a less effective index, which resulted in worse performance.
  2. Unless you understand the data well, it is hard to know what indexes you are going to need up front. For example, on the relationships table, it's clear in retrospect that an index on the to_iri column has better selectivity than an index on the from_iri column—and, in fact, no index is even necessary on the from_iri column. For reasons of symmetry, I created both when I created the table. I'll go so far as to say, don't even create indexes until/unless you can analyze actual data. (Aside: the SQLite3 function likelihood is an excellent way to hint about that data to the query planner.)
  3. Ordering results using the automatically assigned, monotonically increasing id primary key behaves identically to ordering by something like created_at, so order by id and save yourself an index on created_at.

#ktistec #sqlite #optimization

* The original five:

CREATE INDEX idx_relationships_type_from_iri_created_at
    ON relationships (type ASC, from_iri ASC, created_at DESC);
CREATE INDEX idx_relationships_from_iri_created_at_type
    ON relationships (from_iri ASC, created_at DESC, type ASC);
CREATE INDEX idx_relationships_type_to_iri
    ON relationships (type ASC, to_iri ASC);
CREATE INDEX idx_relationships_to_iri_type
    ON relationships (to_iri ASC, type ASC);
CREATE INDEX idx_relationships_type_id
    ON relationships (type ASC, id ASC);

* The final two:

CREATE INDEX idx_relationships_type
    ON relationships (type ASC);
CREATE INDEX idx_relationships_to_iri
    ON relationships (to_iri ASC);

i'm listening to the soundtrack for the legend of zelda: breath of the wild and some of the tracks (the battle themes, of course) still trigger me! it's amazing the strength of the associations we have to sounds (and smells, too, i've heard).