Optimizing Ruby Lazy Initialization in TruffleRuby with Deoptimization - Code ||= Lazy Initialization Profiling
# When run on the following 20 repositories, it found 2082 ||= and it was used as lazy init 64.3% of the time | |
# Those numbers are fuzzy, as there is not certain way to prove if something was used as a lazy init | |
# This script only marks a usage as a lazy init if the variable is being set to a constant, | |
# in which case if it is not a lazy init then the developers have done something weird. | |
# It is also marked as a lazy init if the variable is an instance variable or a class variable (either @var or @@var) | |
# AND the lazy init is used in the top level of a function, AND the function name is contained by the variable or vice versa. | |
# These are almost certain to be lazy initialized because the assumed behaviour is to always call the method instead of the variable, | |
# and other usages would be both weird usage of instance/class variables and/or weird usage of the method naming | |
# This of course, misses a variety of cases but likely does not get any cases that do not lazy initialize. | |
# A better way to profile could be at run time, but then less viable code samples could be used | |
# List of repositories; | |
# https://github.com/Homebrew/brew/tree/2f283b70eb2f3d334af1d4526932bbcb7dbbc94d | |
# https://github.com/rubygems/bundler/tree/d852b30b66165dbb88682f72a8929568af7e7c57 | |
# https://github.com/chef/chef/tree/b32573ecddd5fb09413c69b4aace029be94a1fa3 | |
# https://github.com/erikhuda/thor/tree/fb625b223465692a9d8a88cc2a483e126f1a8978 | |
# https://github.com/github/choosealicense.com/tree/cb897a7471cbf4d8ac013267c18ebd44267cbaae | |
# https://github.com/Shopify/dashing/tree/bc1c106e42d52d3bfc042cba7bb0181d09413068 | |
# https://github.com/thepracticaldev/dev.to/tree/4e41e4a2ac893fa2a6c36990cfe475858ffb086a | |
# https://github.com/elastic/elasticsearch-ruby/tree/32e3952c96482a38791785fe9db6d144f69a7946 | |
# https://github.com/github/explore/tree/8a7b7a402df6f756bb0c83c2ede9fed7dac641fe | |
# https://github.com/Shopify/identity_cache/tree/0cbe7ec09516569f79fdee7a3ac25c9e16084229 | |
# https://github.com/omniauth/omniauth/tree/894cb9c2f6fc06ba3777084c47999dea8a71bd3e | |
# https://github.com/ruby/rake/tree/313721164307fca6375239c636fc7cd8680f175b | |
# https://github.com/mperham/sidekiq/tree/3510de7df3c2eaacec9061184666aa853a0abb13 | |
# https://github.com/spree/spree/tree/70c3ddf194de2b16e20de9477ada96b77debe0a0 | |
# https://github.com/stripe/stripe-ruby/tree/dcb503dc7120ea3f5783c463e04e83368e027d1a | |
# https://github.com/airbnb/synapse/tree/c94125d3b6bb3e48d50dcb98aa28717c82f18322 | |
# https://github.com/rubocop-hq/rubocop/tree/ddb1ba77c7947e04a226fe57ae7789c2eca7a74b | |
# https://github.com/github/scientist/tree/ced649720b0e8d5d58487f0b9bfca2e085d3e88b | |
# https://github.com/Shopify/shipit-engine/tree/b872c658eb8fcea56fe6dc16e91ddc5dd4da45c5 | |
# https://github.com/Shopify/shopify_app/tree/4e6e6ea49716983d43db1d6e6c1dad86de3a6b63 | |
require 'ripper' | |
require 'pp' | |
EMPTY_PARAMS = [:params, nil, nil, nil, nil, nil, nil, nil] | |
$certain_lazy = 0 | |
$total = 0 | |
def constant_check(c) | |
f = true | |
if c[0] == :array | |
return true if c[1].nil? | |
c[1].each do |val| | |
if !constant_check(val) | |
f = false | |
end | |
end | |
return f | |
elsif c[0] == :hash | |
return true if c[1].nil? | |
c[1][1].each do |val| | |
if !constant_check(val[2]) | |
f = false | |
end | |
end | |
return f | |
elsif %i[nil string_literal @const @int symbol_literal].include?(c[0]) | |
return true | |
else | |
return false | |
end | |
end | |
def constant(statement) | |
constant_check(statement[3]) | |
end | |
def in_func(def_tree) | |
return false if !(def_tree[2] == EMPTY_PARAMS && | |
def_tree[3][0] == :bodystmt) | |
def_tree[3][1].each do |statement| | |
next if !(statement[0] == :opassign && statement[2][0] == :@op && statement[2][1] == "||=") | |
if ((statement[1][1][0] == :@ivar && | |
(def_tree[1][1].include?(statement[1][1][1][1..]) || statement[1][1][1][1..].include?(def_tree[1][1]))) || | |
(statement[1][1][0] == :@cvar && | |
(def_tree[1][1].include?(statement[1][1][1][2..]) || statement[1][1][1][2..].include?(def_tree[1][1])))) | |
return true | |
end | |
end | |
false | |
end | |
def traverse(exp) | |
return if exp.nil? | |
l = exp.size | |
(0...l).each do |i| | |
if exp[i].class == Array && exp[i][0].class == Symbol | |
if exp[i][0] == :def | |
if in_func(exp[i]) | |
$certain_lazy += 1 | |
$total += 1 | |
next | |
end | |
elsif exp[i][0] == :opassign && exp[i][2][1] == "||=" && constant(exp[i]) | |
$certain_lazy += 1 | |
$total += 1 | |
next | |
elsif exp[i][0] == :@op && exp[i][1] == "||=" | |
$total += 1 | |
next | |
end | |
end | |
if exp[i].class == Array | |
traverse(exp[i]) | |
end | |
end | |
end | |
Dir.glob('./**/*.rb').each do|f| | |
next if File.directory?(f) | |
tree = Ripper.sexp(File.read(f)) | |
last_total = $total | |
last_certain = $certain_lazy | |
traverse(tree) | |
# prints files where ||= was found potentially without lazy init | |
if ($total - last_total) > ($certain_lazy - last_certain) | |
pp f | |
end | |
end | |
pp "fairly certain lazy init usage " + ($certain_lazy / $total.to_f * 100).to_s + "%" | |
pp "total double pipes " + $total.to_s |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment