{ "@context":[ "https://www.w3.org/ns/activitystreams", {"Hashtag":"as:Hashtag"} ], "published":"2025-01-22T14:23:17.649Z", "attributedTo":"https://epiktistes.com/actors/toddsundsted", "replies":"https://epiktistes.com/objects/isePXGePzZ4/replies", "to":["https://www.w3.org/ns/activitystreams#Public"], "cc":["https://epiktistes.com/actors/toddsundsted/followers"], "content":"

Crystal is fast because methods are monomorphized at compile time. In simple terms, that means that at compile time, a polymorphic method is replaced by one or more type-specific instantiations of that method. The following polymorphic code...

def plus(x, y)\n  x + y\nend

...is effectively replaced by two methods—one that does integer addition if called with two integers, and one that does string concatenation if called with two strings.

This extends to inherited methods, which are implicitly also passed self. You can see this in action if you dump and inspect the symbols in a compiled program:

class FooBar\n  def self.foo\n    puts \"#{self}.foo\"\n  end\n\n  def bar\n    puts \"#{self}.bar\"\n  end\nend\n\nFooBar.foo\nFooBar.new.bar\n\nclass Quux < FooBar\nend\n\nQuux.foo\nQuux.new.bar

Dumping the symbols, you see multiple instantiations of the methods foo and bar:

...\n_*FooBar#bar:Nil\n_*FooBar::foo:Nil\n_*FooBar@Object::to_s<String::Builder>:Nil\n_*FooBar@Reference#to_s<String::Builder>:Nil\n_*FooBar@Reference::new:FooBar\n_*Quux@FooBar#bar:Nil\n_*Quux@FooBar::foo:Nil\n_*Quux@Object::to_s<String::Builder>:Nil\n_*Quux@Reference#to_s<String::Builder>:Nil\n_*Quux@Reference::new:Quux\n...

The optimizer in release builds is pretty good at cleaning up the obvious duplication. But during my optimization work on Ktistec, I found that a lot of duplicate code shows up anyway.

Most pernicious are weighty methods that don't depend on class or instance state (don't make explicit or implicit reference to self). As I blogged about earlier, this commit replaced calls to the inherited method map on subclasses with calls to the method map defined on the base class and reduced the executable size by ~5.8%. The code was identical and the optimizer could remove the unused duplicates.

So, as a general rule, if you intend to use inheritance, put utility code that doesn't reference the state or the methods on the class or instance in an adjacent utility class—as I eventually did with this commit.

(The full thread starts here.)

#ktistec #crystallang #optimization

", "contentMap":{ "en-US":"

Crystal is fast because methods are monomorphized at compile time. In simple terms, that means that at compile time, a polymorphic method is replaced by one or more type-specific instantiations of that method. The following polymorphic code...

def plus(x, y)\n  x + y\nend

...is effectively replaced by two methods—one that does integer addition if called with two integers, and one that does string concatenation if called with two strings.

This extends to inherited methods, which are implicitly also passed self. You can see this in action if you dump and inspect the symbols in a compiled program:

class FooBar\n  def self.foo\n    puts \"#{self}.foo\"\n  end\n\n  def bar\n    puts \"#{self}.bar\"\n  end\nend\n\nFooBar.foo\nFooBar.new.bar\n\nclass Quux < FooBar\nend\n\nQuux.foo\nQuux.new.bar

Dumping the symbols, you see multiple instantiations of the methods foo and bar:

...\n_*FooBar#bar:Nil\n_*FooBar::foo:Nil\n_*FooBar@Object::to_s<String::Builder>:Nil\n_*FooBar@Reference#to_s<String::Builder>:Nil\n_*FooBar@Reference::new:FooBar\n_*Quux@FooBar#bar:Nil\n_*Quux@FooBar::foo:Nil\n_*Quux@Object::to_s<String::Builder>:Nil\n_*Quux@Reference#to_s<String::Builder>:Nil\n_*Quux@Reference::new:Quux\n...

The optimizer in release builds is pretty good at cleaning up the obvious duplication. But during my optimization work on Ktistec, I found that a lot of duplicate code shows up anyway.

Most pernicious are weighty methods that don't depend on class or instance state (don't make explicit or implicit reference to self). As I blogged about earlier, this commit replaced calls to the inherited method map on subclasses with calls to the method map defined on the base class and reduced the executable size by ~5.8%. The code was identical and the optimizer could remove the unused duplicates.

So, as a general rule, if you intend to use inheritance, put utility code that doesn't reference the state or the methods on the class or instance in an adjacent utility class—as I eventually did with this commit.

(The full thread starts here.)

#ktistec #crystallang #optimization

" }, "mediaType":"text/html", "attachment":[], "tag":[ {"type":"Hashtag","name":"#ktistec","href":"https://epiktistes.com/tags/ktistec"}, {"type":"Hashtag","name":"#crystallang","href":"https://epiktistes.com/tags/crystallang"}, {"type":"Hashtag","name":"#optimization","href":"https://epiktistes.com/tags/optimization"} ], "url":["https://epiktistes.com/ktistec/performance/build-time-and-server-size-growth-5"], "type":"Note", "id":"https://epiktistes.com/objects/MMpFASbFsQ8" }