I’ve been brainstorming ideas for performance improvements on dry-view but it seems most of the initial wins are coming from dry-configurable so, rather than post another PR to the dry-view repo, I thought a discussion here might be better.
In most of my code, I prefer clarity over performance, but in a contrived benchmark comparing the render time of 100 nested partials in Action View, dry-view, Phlex, and View Component, I found that dry-view was ~10x slower than Action View. I hope to bring some ideas to the table for levelling the playing field in the knowledge that any suggestion needs to be weighed against many other factors and may be undesirable to the community.
Since I’m unfamiliar with all the ways dry-configurable is used, my experiments have been inside dry-view. I can’t imagine this is what we want, but I figured it could drive discussion, and we can worry about the details at a later stage.
Most recently, I’ve been looking at how dry-configurable relies upon method_missing
to forward requests onto [](name)
and []=(name, value)
. Unfortunately, method_missing
is quite slow, which has a noticeable impact on a componentised site.
Interestingly, I see no gain when working off main, however, if I apply PR 156 and PR 157, iterations per second are increased by 30% when generating settings getters and setters as instance methods.
Main 100.263 i/s - 1.008k in 10.076792s
Baseline 245.789 i/s - 2.472k in 10.085665s
Experiment 320.663 i/s - 3.224k in 10.078677s
For the sake of proving the idea, I created the following (rather ugly) code:
module Dry
class View
class Config < Dry::Configurable::Config
# PR 156
include Dry::Equalizer(:values, immutable: true)
# PR 157
def [](name)
name = name.to_sym
return _values[name] if _values.key?(name)
super
_values[name] = _settings[name].to_value unless _values.key?(name)
_values[name]
end
def method_missing(name, *args)
setting_name = setting_name_from_method(name)
setting = _settings[setting_name]
return super unless setting
if name.end_with?("=")
instance_eval <<~RUBY, __FILE__, __LINE__ + 1
def #{setting_name}= # def path=
self[:#{setting_name}] = args[0] # self[:path] = args[0]
end # end
RUBY
self[setting_name] = args[0]
else
instance_eval <<~RUBY, __FILE__, __LINE__ + 1
def #{setting_name} # def path
self[:#{setting_name}] # self[:path]
end # end
RUBY
self[setting_name]
end
end
end
...
# PR 156
extend Dry::Configurable(config_class: Dry::View::Config)