Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Implement loaded feature caching to reduce load path searching. #452

Merged
merged 4 commits into from

3 participants

@headius
Owner

This is an idea borrowed from Ruby 2.0.0, whereby the short names
of loaded features are held in an internal structure and used as
a first-line cache to avoid re-searching for re-required files.
Before this patch, in both JRuby and MRI, re-required files had to
do a full load path search every time because the loaded features
list only held the full canonical paths, which are only available
after searching for the proper load path entry under which the
file exists. With the patch in place, re-required files will
usually be immediately found in the cache.

The population and invalidation of the cache proceeds as follows:

  • A reference is held to a dup of the loaded features array. When this reference is captured.
  • When adding a new feature to loaded features, we re-capture the dup'ed features array and add the short name to a separate internal cache.
  • As long as the dup'ed features array is non-null and matches the current loaded features array, we know we can safely use the loaded features index.
  • When the dup'ed features array does not match, we clear the loaded features index and start populating it anew. Since most typical Ruby code does not modify the loaded features array directly, this should be rare.

Numbers with and without this change show the performance gained.
The following benchmark populates load path with 1000 entries and
requires the same file 1000 times.

Before:

system ~/projects/jruby $ rvm jruby do jruby bench_loaded_features.rb 1000 1000
8.110000 3.880000 11.990000 ( 9.708000)

After:

system ~/projects/jruby $ jruby bench_loaded_features.rb 1000 1000
0.990000 0.050000 1.040000 ( 0.324000)

headius and others added some commits
@headius headius Implement loaded feature caching to reduce load path searching.
This is an idea borrowed from Ruby 2.0.0, whereby the short names
of loaded features are held in an internal structure and used as
a first-line cache to avoid re-searching for re-required files.
Before this patch, in both JRuby and MRI, re-required files had to
do a full load path search every time because the loaded features
list only held the full canonical paths, which are only available
after searching for the proper load path entry under which the
file exists. With the patch in place, re-required files will
usually be immediately found in the cache.

The population and invalidation of the cache proceeds as follows:

* A reference is held to a dup of the loaded features array. When
this reference is captured.
* When adding a new feature to loaded features, we re-capture
the dup'ed features array and add the short name to a separate
internal cache.
* As long as the dup'ed features array is non-null and matches
the current loaded features array, we know we can safely use the
loaded features index.
* When the dup'ed features array does not match, we clear the
loaded features index and start populating it anew. Since most
typical Ruby code does not modify the loaded features array
directly, this should be rare.

Numbers with and without this change show the performance gained.
The following benchmark populates load path with 1000 entries and
requires the same file 1000 times.

Before:

system ~/projects/jruby $ rvm jruby do jruby bench_loaded_features.rb 1000 1000
  8.110000   3.880000  11.990000 (  9.708000)

After:

system ~/projects/jruby $ jruby bench_loaded_features.rb 1000 1000
  0.990000   0.050000   1.040000 (  0.324000)
daba4d3
@enebo enebo Add helper method c83dd6a
@headius headius Disable tracing during loaded features index check. a93b72d
@headius headius Remove getValues that never ended up being useful. 2c44ffd
@BanzaiMan
Owner

Looks good to me!

@headius headius merged commit 01982e3 into master
@LTe LTe referenced this pull request in rubinius/rubinius
Merged

Add cache for $LOAD_FEATURES and $LOAD_PATH #2353

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Dec 17, 2012
  1. @headius

    Implement loaded feature caching to reduce load path searching.

    headius authored
    This is an idea borrowed from Ruby 2.0.0, whereby the short names
    of loaded features are held in an internal structure and used as
    a first-line cache to avoid re-searching for re-required files.
    Before this patch, in both JRuby and MRI, re-required files had to
    do a full load path search every time because the loaded features
    list only held the full canonical paths, which are only available
    after searching for the proper load path entry under which the
    file exists. With the patch in place, re-required files will
    usually be immediately found in the cache.
    
    The population and invalidation of the cache proceeds as follows:
    
    * A reference is held to a dup of the loaded features array. When
    this reference is captured.
    * When adding a new feature to loaded features, we re-capture
    the dup'ed features array and add the short name to a separate
    internal cache.
    * As long as the dup'ed features array is non-null and matches
    the current loaded features array, we know we can safely use the
    loaded features index.
    * When the dup'ed features array does not match, we clear the
    loaded features index and start populating it anew. Since most
    typical Ruby code does not modify the loaded features array
    directly, this should be rare.
    
    Numbers with and without this change show the performance gained.
    The following benchmark populates load path with 1000 entries and
    requires the same file 1000 times.
    
    Before:
    
    system ~/projects/jruby $ rvm jruby do jruby bench_loaded_features.rb 1000 1000
      8.110000   3.880000  11.990000 (  9.708000)
    
    After:
    
    system ~/projects/jruby $ jruby bench_loaded_features.rb 1000 1000
      0.990000   0.050000   1.040000 (  0.324000)
  2. @enebo

    Add helper method

    enebo authored
  3. @headius
  4. @headius
This page is out of date. Refresh to see the latest.
View
4 src/org/jruby/RubyArray.java
@@ -416,8 +416,8 @@ public int getLength() {
public IRubyObject[] toJavaArrayMaybeUnsafe() {
return (!isShared && begin == 0 && values.length == realLength) ? values : toJavaArray();
- }
-
+ }
+
/** rb_ary_make_shared
*
*/
View
39 src/org/jruby/runtime/load/LoadService.java
@@ -43,10 +43,12 @@
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
import java.util.jar.JarFile;
@@ -204,6 +206,8 @@
protected RubyArray loadPath;
protected StringArraySet loadedFeatures;
+ protected RubyArray loadedFeaturesDup;
+ private final Map<String, String> loadedFeaturesIndex = new ConcurrentHashMap<String, String>();
protected final Map<String, Library> builtinLibraries = new HashMap<String, Library>();
protected final Map<String, JarFile> jarFiles = new HashMap<String, JarFile>();
@@ -296,9 +300,20 @@ public void addPaths(String... additionalDirectories) {
addPath(dir);
}
}
+
+ protected boolean isFeatureInIndex(String shortName) {
+ return loadedFeaturesIndex.containsKey(shortName);
+ }
- protected void addLoadedFeature(String name) {
+ protected void addLoadedFeature(String shortName, String name) {
loadedFeatures.append(RubyString.newString(runtime, name));
+
+ addFeatureToIndex(shortName, name);
+ }
+
+ protected void addFeatureToIndex(String shortName, String name) {
+ loadedFeaturesDup = (RubyArray)loadedFeatures.dup();
+ loadedFeaturesIndex.put(shortName, name);
}
protected void addPath(String path) {
@@ -536,7 +551,7 @@ private boolean smartLoadInternal(String file) {
boolean loaded = tryLoadingLibraryOrScript(runtime, state);
if (loaded) {
- addLoadedFeature(state.loadName);
+ addLoadedFeature(file, state.loadName);
}
return loaded;
}
@@ -627,9 +642,27 @@ public void removeInternalLoadedFeature(String name) {
RubyString nameRubyString = runtime.newString(name);
loadedFeatures.delete(runtime.getCurrentContext(), nameRubyString, Block.NULL_BLOCK);
}
+
+ private boolean isFeaturesIndexUpToDate() {
+ // disable tracing during index check
+ runtime.getCurrentContext().preTrace();
+ try {
+ return loadedFeaturesDup != null && loadedFeaturesDup.eql(loadedFeatures);
+ } finally {
+ runtime.getCurrentContext().postTrace();
+ }
+ }
protected boolean featureAlreadyLoaded(String name) {
- return loadedFeatures.containsString(name);
+ if (loadedFeatures.containsString(name)) return true;
+
+ // Bail if our features index fell out of date.
+ if (!isFeaturesIndexUpToDate()) {
+ loadedFeaturesIndex.clear();
+ return false;
+ }
+
+ return isFeatureInIndex(name);
}
protected boolean isJarfileLibrary(SearchState state, final String file) {
Something went wrong with that request. Please try again.