Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Performance] Add early checks to loaded_feature_path to make it faster #3010

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Compatibility:

Performance:

* Improve `Truffle::FeatureLoader.loaded_feature_path` by removing expensive string ops from a loop. Speeds up feature lookup time (#3010, @itarato).

Changes:

Expand Down
32 changes: 16 additions & 16 deletions spec/truffle/kernel/feature_loader_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,44 +53,44 @@
end
end

describe "Truffle::FeatureLoader.loaded_feature_path" do
describe "Truffle::FeatureLoader.feature_path_loaded?" do
it "returns path for matching feature" do
load_path = ["/path/ruby/lib/ruby/2.6.0"]
loaded_feature = "/path/ruby/lib/ruby/2.6.0/benchmark.rb"
feature = "benchmark"
path = Truffle::FeatureLoader.loaded_feature_path(loaded_feature, feature, load_path)
path.should == load_path[0]
path = Truffle::FeatureLoader.feature_path_loaded?(loaded_feature, feature, load_path)
path.should be_true

feature = "benchmark.rb"
path = Truffle::FeatureLoader.loaded_feature_path(loaded_feature, feature, load_path)
path.should == load_path[0]
path = Truffle::FeatureLoader.feature_path_loaded?(loaded_feature, feature, load_path)
path.should be_true
end

it "returns nil for missing features" do
it "returns false for missing features" do
load_path = ["/path/ruby/lib/ruby/2.6.0"]
path = Truffle::FeatureLoader.loaded_feature_path("/path/ruby/lib/ruby/2.6.0/benchmark.rb", "missing", load_path)
path.should == nil
path = Truffle::FeatureLoader.feature_path_loaded?("/path/ruby/lib/ruby/2.6.0/benchmark.rb", "missing", load_path)
path.should be_false

long_feature = "/path/ruby/lib/ruby/2.6.0/extra-path/benchmark.rb"
path = Truffle::FeatureLoader.loaded_feature_path("/path/ruby/lib/ruby/2.6.0/benchmark.rb", long_feature, load_path)
path.should == nil
path = Truffle::FeatureLoader.feature_path_loaded?("/path/ruby/lib/ruby/2.6.0/benchmark.rb", long_feature, load_path)
path.should be_false
end

it "returns correct paths for non-rb paths" do
load_path = ["/path/ruby/lib/ruby/2.6.0"]
loaded_feature = "/path/ruby/lib/ruby/2.6.0/benchmark.so"

feature = "benchmark"
path = Truffle::FeatureLoader.loaded_feature_path(loaded_feature, feature, load_path)
path.should == load_path[0]
path = Truffle::FeatureLoader.feature_path_loaded?(loaded_feature, feature, load_path)
path.should be_true

feature = "benchmark.so"
path = Truffle::FeatureLoader.loaded_feature_path(loaded_feature, feature, load_path)
path.should == load_path[0]
path = Truffle::FeatureLoader.feature_path_loaded?(loaded_feature, feature, load_path)
path.should be_true

feature = "benchmark.rb"
path = Truffle::FeatureLoader.loaded_feature_path(loaded_feature, feature, load_path)
path.should == nil
path = Truffle::FeatureLoader.feature_path_loaded?(loaded_feature, feature, load_path)
path.should be_false
end

end
Expand Down
33 changes: 20 additions & 13 deletions src/main/ruby/truffleruby/core/truffle/feature_loader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -165,16 +165,16 @@ def self.feature_provided?(feature, expanded)
loaded_feature = $LOADED_FEATURES[fe.index]

next if loaded_feature.size < feature.size
feature_path = if loaded_feature.start_with?(feature)
feature
else
if expanded
nil
else
loaded_feature_path(loaded_feature, feature, get_expanded_load_path)
end
end
if feature_path
found_feature_path = if loaded_feature.start_with?(feature)
true
else
if expanded
false
else
feature_path_loaded?(loaded_feature, feature, get_expanded_load_path)
end
end
if found_feature_path
loaded_feature_ext = extension_symbol(loaded_feature)
if !loaded_feature_ext
return :unknown unless feature_ext
Expand All @@ -198,11 +198,18 @@ def self.feature_provided?(feature, expanded)
# MRI: loaded_feature_path
# Search if $LOAD_PATH[i]/feature corresponds to loaded_feature.
# Returns the $LOAD_PATH entry containing feature.
def self.loaded_feature_path(loaded_feature, feature, load_path)
def self.feature_path_loaded?(loaded_feature, feature, load_path)
name_ext = extension(loaded_feature)
load_path.find do |p|
loaded_feature == "#{p}/#{feature}#{name_ext}" || loaded_feature == "#{p}/#{feature}"

if name_ext && (suffix_with_ext = "/#{feature}#{name_ext}") && loaded_feature.end_with?(suffix_with_ext)
path = loaded_feature[0...-suffix_with_ext.size]
elsif loaded_feature.end_with?(feature) && loaded_feature.getbyte(-(feature.bytesize + 1)) == 47 # '/'.ord = 47
path = loaded_feature[0...-(feature.size + 1)]
else
return false
end

load_path.include?(path)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we could store the $LOAD_PATH index in the FeatureEntry, and use that as a faster check to avoid the loop (i.e. load_path[index] == path or load_path.include?(path)). OTOH we would need to care about $LOAD_PATH changing (tracked via version), in which case we should recompute the index for the FeatureEntry.

Actually, I think something simpler and easier would be to keep a Hash of the $LOAD_PATH (recomputed by version). And then we can do load_path_hash.include?(path) which seems really nice and simple.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe something along that line can be accomplished. I'd rather ship this separately as that cached result change would still need some thinking how to position it properly. Would that work?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's definitely something that can be done separately.

end

# MRI: rb_provide_feature
Expand Down