Created
March 30, 2020 20:05
-
-
Save ShopifyEng/67a8c115bc6acc26122d43d275e9b901 to your computer and use it in GitHub Desktop.
Optimizing Ruby Lazy Initialization in TruffleRuby with Deoptimization - Code ||= Lazy Initialization Profiling
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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