Skip to content

Instantly share code, notes, and snippets.

@shirosaki
Created October 27, 2012 13:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save shirosaki/3964679 to your computer and use it in GitHub Desktop.
Save shirosaki/3964679 to your computer and use it in GitHub Desktop.
Benchmark require #7158
#!/usr/bin/env ruby
require "benchmark"
in_dir = File.expand_path('input/empty_files', File.dirname(__FILE__))
200.times do |i|
$LOAD_PATH.push File.expand_path(i.to_s)
end
$LOAD_PATH.push in_dir
Benchmark.bm do |x|
x.report do
1000.times do |i|
require "empty#{i}"
end
end
end
puts "LOAD_PATH SIZE = #{$:.size}"
puts "LOADED_FEATURES SIZE = #{$".size}"
#!/usr/bin/env ruby
require "benchmark"
in_dir = File.expand_path('input/empty_files', File.dirname(__FILE__))
200.times do |i|
$LOAD_PATH.push i.to_s
end
$LOAD_PATH.push in_dir
Benchmark.bm do |x|
x.report do
1000.times do |i|
require "empty#{i}"
end
end
end
puts "LOAD_PATH SIZE = #{$:.size}"
puts "LOADED_FEATURES SIZE = #{$".size}"

Best of 5 trials

LOAD_PATH SIZE = 209

LOADED_FEATURES SIZE = 1010

# trunk
ruby 2.0.0dev (2012-10-27 trunk 37341) [x86_64-darwin12.2.0]
       user     system      total        real
   0.620000   0.390000   1.010000 (  1.022564)

# Greg's patch
ruby 2.0.0dev (2012-10-27 trunk 37341) [x86_64-darwin12.2.0]
       user     system      total        real
   0.380000   0.350000   0.730000 (  0.729237)

# Greg's patch + my patch 1 and 2
ruby 2.0.0dev (2012-10-27 trunk 37341) [x86_64-darwin12.2.0]
       user     system      total        real
   0.380000   0.350000   0.730000 (  0.736977)

# Greg's patch + my patch 1, 2 and 3
ruby 2.0.0dev (2012-10-27 trunk 37341) [x86_64-darwin12.2.0]
       user     system      total        real
   0.380000   0.350000   0.730000 (  0.727115)

bench

patched contains all my patches.

# trunk bench_require_empty.rb
ruby 2.0.0dev (2012-11-01 trunk 37415) [x86_64-darwin12.2.0]
       user     system      total        real
   0.610000   0.390000   1.000000 (  1.002916)
LOAD_PATH SIZE = 209 
LOADED_FEATURES SIZE = 1010

# patched bench_require_empty.rb
ruby 2.0.0dev (2012-11-01 trunk 37415) [x86_64-darwin12.2.0]
       user     system      total        real
   0.390000   0.350000   0.740000 (  0.735971)
LOAD_PATH SIZE = 209 
LOADED_FEATURES SIZE = 1010


# trunk bench_require_empty_relative.rb
ruby 2.0.0dev (2012-11-01 trunk 37415) [x86_64-darwin12.2.0]
       user     system      total        real
   1.600000   2.470000   4.070000 (  4.068619)
LOAD_PATH SIZE = 209 
LOADED_FEATURES SIZE = 1010

# patched bench_require_empty_relative.rb
ruby 2.0.0dev (2012-11-01 trunk 37415) [x86_64-darwin12.2.0]
       user     system      total        real
   0.420000   0.360000   0.780000 (  0.787250)
LOAD_PATH SIZE = 209 
LOADED_FEATURES SIZE = 1010
From 1245d796305be72b80a8c623faf3195ee92fb1f3 Mon Sep 17 00:00:00 2001
From: Hiroshi Shirosaki <h.shirosaki@gmail.com>
Date: Sat, 27 Oct 2012 18:39:01 +0900
Subject: [PATCH 1/3] Expand relative load path always
* load.c (rb_construct_expanded_load_path): add a option for expanding
only relative load paths.
* load.c (rb_get_expanded_load_path): expand relative load path always.
---
load.c | 15 ++++++++++++---
1 file changed, 12 insertions(+), 3 deletions(-)
diff --git a/load.c b/load.c
index dfea0ef..b3e316c 100644
--- a/load.c
+++ b/load.c
@@ -34,10 +34,11 @@ rb_get_load_path(void)
}
static void
-rb_construct_expanded_load_path(void)
+rb_construct_expanded_load_path(int only_relative)
{
rb_vm_t *vm = GET_VM();
VALUE load_path = vm->load_path;
+ VALUE expanded_load_path = vm->expanded_load_path;
VALUE ary;
long i;
@@ -46,6 +47,10 @@ rb_construct_expanded_load_path(void)
VALUE path, as_str, expanded_path;
as_str = path = RARRAY_PTR(load_path)[i];
StringValue(as_str);
+ if (only_relative && rb_is_absolute_path(StringValuePtr(as_str))) {
+ rb_ary_push(ary, RARRAY_PTR(expanded_load_path)[i]);
+ continue;
+ }
if (as_str != path)
rb_ary_store(load_path, i, as_str);
rb_str_freeze(as_str);
@@ -63,8 +68,12 @@ rb_get_expanded_load_path(void)
{
rb_vm_t *vm = GET_VM();
if (!rb_ary_shared_with_p(vm->load_path_snapshot, vm->load_path)) {
- /* The load path was modified. Rebuild the expanded load path. */
- rb_construct_expanded_load_path();
+ /* The load path was modified. Rebuild the expanded load path. */
+ rb_construct_expanded_load_path(0);
+ }
+ else {
+ /* Expand only relative load path. */
+ rb_construct_expanded_load_path(1);
}
return vm->expanded_load_path;
}
--
1.7.10.2 (Apple Git-33)
From f0a7f264e53b64b0493c8de3b2d592bd5003bc0c Mon Sep 17 00:00:00 2001
From: Hiroshi Shirosaki <h.shirosaki@gmail.com>
Date: Sat, 27 Oct 2012 20:50:41 +0900
Subject: [PATCH 2/3] Cache expanded relative load path
* load.c (rb_construct_expanded_load_path): check relative path.
We call getcwd() only if load path has relative path, to avoid getcwd() failure.
* load.c (load_path_getcwd): add a function to get current working directory.
* load.c (rb_get_expanded_load_path): check if current working directory is changed
and rebuild expanded load path cache.
* load.c (Init_load): initialize vm->load_path_cwd.
* test/ruby/test_require.rb (TestRequire#test_require_): add a test for require
when changeing current working directory.
* vm.c (rb_vm_mark): mark vm->load_path_cwd.
* vm_core.h (rb_vm_struct): add vm->load_path_cwd to store current working directory
to check if expanded load path cache is invalid.
---
load.c | 33 +++++++++++++++++++++++++++------
test/ruby/test_require.rb | 22 ++++++++++++++++++++++
vm.c | 1 +
vm_core.h | 1 +
4 files changed, 51 insertions(+), 6 deletions(-)
diff --git a/load.c b/load.c
index b3e316c..53dc58c 100644
--- a/load.c
+++ b/load.c
@@ -34,7 +34,7 @@ rb_get_load_path(void)
}
static void
-rb_construct_expanded_load_path(int only_relative)
+rb_construct_expanded_load_path(int only_relative, int *has_relative)
{
rb_vm_t *vm = GET_VM();
VALUE load_path = vm->load_path;
@@ -51,6 +51,8 @@ rb_construct_expanded_load_path(int only_relative)
rb_ary_push(ary, RARRAY_PTR(expanded_load_path)[i]);
continue;
}
+ if (!*has_relative && !rb_is_absolute_path(StringValuePtr(as_str)))
+ *has_relative = 1;
if (as_str != path)
rb_ary_store(load_path, i, as_str);
rb_str_freeze(as_str);
@@ -64,16 +66,34 @@ rb_construct_expanded_load_path(int only_relative)
}
static VALUE
+load_path_getcwd(void)
+{
+ char *cwd = my_getcwd();
+ VALUE cwd_str = rb_filesystem_str_new_cstr(cwd);
+ xfree(cwd);
+ return cwd_str;
+}
+
+static VALUE
rb_get_expanded_load_path(void)
{
rb_vm_t *vm = GET_VM();
+
if (!rb_ary_shared_with_p(vm->load_path_snapshot, vm->load_path)) {
/* The load path was modified. Rebuild the expanded load path. */
- rb_construct_expanded_load_path(0);
- }
- else {
- /* Expand only relative load path. */
- rb_construct_expanded_load_path(1);
+ int has_relative = 0;
+ rb_construct_expanded_load_path(0, &has_relative);
+ vm->load_path_cwd = has_relative ? load_path_getcwd() : 0;
+ }
+ else if (vm->load_path_cwd) {
+ VALUE cwd = load_path_getcwd();
+ if (!rb_str_equal(vm->load_path_cwd, cwd)) {
+ /* Current working directory or filesystem encoding was changed.
+ Expand relative load path again. */
+ int has_relative = 1;
+ vm->load_path_cwd = cwd;
+ rb_construct_expanded_load_path(1, &has_relative);
+ }
}
return vm->expanded_load_path;
}
@@ -971,6 +991,7 @@ Init_load()
vm->load_path = rb_ary_new();
vm->expanded_load_path = rb_ary_new();
vm->load_path_snapshot = rb_ary_new();
+ vm->load_path_cwd = 0;
rb_define_virtual_variable("$\"", get_loaded_features, 0);
rb_define_virtual_variable("$LOADED_FEATURES", get_loaded_features, 0);
diff --git a/test/ruby/test_require.rb b/test/ruby/test_require.rb
index a7c75d0..8c72571 100644
--- a/test/ruby/test_require.rb
+++ b/test/ruby/test_require.rb
@@ -435,4 +435,26 @@ class TestRequire < Test::Unit::TestCase
$:.replace(loadpath)
$".replace(features)
end
+
+ def test_require_changed_current_dir
+ Dir.mktmpdir {|tmp|
+ Dir.chdir(tmp) {
+ Dir.mkdir("a")
+ Dir.mkdir("b")
+ open(File.join("a", "foo.rb"), "w") {}
+ open(File.join("b", "bar.rb"), "w") {|f|
+ f.puts "p :ok"
+ }
+ assert_in_out_err([], <<-INPUT, %w(:ok), [])
+ $: << "."
+ Dir.chdir("a")
+ require "foo"
+ Dir.chdir("../b")
+ p :ng unless require "bar"
+ Dir.chdir("..")
+ p :ng if require "b/bar"
+ INPUT
+ }
+ }
+ end
end
diff --git a/vm.c b/vm.c
index 9a8ebef..03cc79d 100644
--- a/vm.c
+++ b/vm.c
@@ -1508,6 +1508,7 @@ rb_vm_mark(void *ptr)
RUBY_MARK_UNLESS_NULL(vm->mark_object_ary);
RUBY_MARK_UNLESS_NULL(vm->load_path);
RUBY_MARK_UNLESS_NULL(vm->load_path_snapshot);
+ RUBY_MARK_UNLESS_NULL(vm->load_path_cwd);
RUBY_MARK_UNLESS_NULL(vm->expanded_load_path);
RUBY_MARK_UNLESS_NULL(vm->loaded_features);
RUBY_MARK_UNLESS_NULL(vm->loaded_features_snapshot);
diff --git a/vm_core.h b/vm_core.h
index b315fe4..3874231 100644
--- a/vm_core.h
+++ b/vm_core.h
@@ -355,6 +355,7 @@ typedef struct rb_vm_struct {
VALUE top_self;
VALUE load_path;
VALUE load_path_snapshot;
+ VALUE load_path_cwd;
VALUE expanded_load_path;
VALUE loaded_features;
VALUE loaded_features_snapshot;
--
1.7.10.2 (Apple Git-33)
From 6522cc9cf8b8ac918e09705342f5ad9d5a5c31f6 Mon Sep 17 00:00:00 2001
From: Hiroshi Shirosaki <h.shirosaki@gmail.com>
Date: Sat, 27 Oct 2012 21:46:59 +0900
Subject: [PATCH 3/3] Use rb_get_expanded_load_path() in find file
* file.c (rb_find_file_ext_safe): use rb_get_expanded_load_path() to reduce
expand cost.
* file.c (rb_find_file_safe): ditto.
* internal.h (rb_get_expanded_load_path): add a declaration.
* load.c (rb_get_expanded_load_path): make it accesible from other source
files.
---
file.c | 4 ++--
internal.h | 1 +
load.c | 2 +-
3 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/file.c b/file.c
index 5951d7b..7b22dcf 100644
--- a/file.c
+++ b/file.c
@@ -5258,7 +5258,7 @@ rb_find_file_ext_safe(VALUE *filep, const char *const *ext, int safe_level)
rb_raise(rb_eSecurityError, "loading from non-absolute path %s", f);
}
- RB_GC_GUARD(load_path) = rb_get_load_path();
+ RB_GC_GUARD(load_path) = rb_get_expanded_load_path();
if (!load_path) return 0;
fname = rb_str_dup(*filep);
@@ -5323,7 +5323,7 @@ rb_find_file_safe(VALUE path, int safe_level)
rb_raise(rb_eSecurityError, "loading from non-absolute path %s", f);
}
- RB_GC_GUARD(load_path) = rb_get_load_path();
+ RB_GC_GUARD(load_path) = rb_get_expanded_load_path();
if (load_path) {
long i;
diff --git a/internal.h b/internal.h
index e538de4..104539b 100644
--- a/internal.h
+++ b/internal.h
@@ -133,6 +133,7 @@ VALUE rb_iseq_clone(VALUE iseqval, VALUE newcbase);
/* load.c */
VALUE rb_get_load_path(void);
+VALUE rb_get_expanded_load_path(void);
NORETURN(void rb_load_fail(VALUE, const char*));
/* math.c */
diff --git a/load.c b/load.c
index 53dc58c..22d3532 100644
--- a/load.c
+++ b/load.c
@@ -74,7 +74,7 @@ load_path_getcwd(void)
return cwd_str;
}
-static VALUE
+VALUE
rb_get_expanded_load_path(void)
{
rb_vm_t *vm = GET_VM();
--
1.7.10.2 (Apple Git-33)
From f21777810c1dac501749218fe89d567dff39369b Mon Sep 17 00:00:00 2001
From: Hiroshi Shirosaki <h.shirosaki@gmail.com>
Date: Mon, 29 Oct 2012 23:22:01 +0900
Subject: [PATCH] Fix compatibility of require
* load.c (rb_construct_expanded_load_path): don't replace $LOAD_PATH.
We just freeze contents of $LOAD_PATH. We add an argument for home
directory expansion.
* load.c (rb_get_expanded_load_path): expand path which starts with ~
always for compatibility.
* test/ruby/test_require.rb (TestRequire): add tests for require
compatibility.
- not modify load path contents
- require when ENV['HOME'] is changed
---
load.c | 26 +++++++++++++++++---------
test/ruby/test_require.rb | 38 ++++++++++++++++++++++++++++++++++++++
2 files changed, 55 insertions(+), 9 deletions(-)
diff --git a/load.c b/load.c
index 22d3532..9f707a7 100644
--- a/load.c
+++ b/load.c
@@ -34,7 +34,7 @@ rb_get_load_path(void)
}
static void
-rb_construct_expanded_load_path(int only_relative, int *has_relative)
+rb_construct_expanded_load_path(int only_tilde, int only_relative, int *has_relative)
{
rb_vm_t *vm = GET_VM();
VALUE load_path = vm->load_path;
@@ -45,17 +45,21 @@ rb_construct_expanded_load_path(int only_relative, int *has_relative)
ary = rb_ary_new2(RARRAY_LEN(load_path));
for (i = 0; i < RARRAY_LEN(load_path); ++i) {
VALUE path, as_str, expanded_path;
+ char *as_cstr;
as_str = path = RARRAY_PTR(load_path)[i];
StringValue(as_str);
- if (only_relative && rb_is_absolute_path(StringValuePtr(as_str))) {
+ as_cstr = StringValuePtr(as_str);
+ if (only_relative && rb_is_absolute_path(as_cstr)) {
rb_ary_push(ary, RARRAY_PTR(expanded_load_path)[i]);
continue;
}
- if (!*has_relative && !rb_is_absolute_path(StringValuePtr(as_str)))
+ else if (only_tilde && (!as_cstr[0] || as_cstr[0] != '~')) {
+ rb_ary_push(ary, RARRAY_PTR(expanded_load_path)[i]);
+ continue;
+ }
+ if (!*has_relative && !rb_is_absolute_path(as_cstr))
*has_relative = 1;
- if (as_str != path)
- rb_ary_store(load_path, i, as_str);
- rb_str_freeze(as_str);
+ rb_obj_freeze(path);
expanded_path = rb_file_expand_path_fast(as_str, Qnil);
rb_str_freeze(expanded_path);
rb_ary_push(ary, expanded_path);
@@ -82,17 +86,21 @@ rb_get_expanded_load_path(void)
if (!rb_ary_shared_with_p(vm->load_path_snapshot, vm->load_path)) {
/* The load path was modified. Rebuild the expanded load path. */
int has_relative = 0;
- rb_construct_expanded_load_path(0, &has_relative);
+ rb_construct_expanded_load_path(0, 0, &has_relative);
vm->load_path_cwd = has_relative ? load_path_getcwd() : 0;
}
else if (vm->load_path_cwd) {
VALUE cwd = load_path_getcwd();
+ int has_relative = 1;
if (!rb_str_equal(vm->load_path_cwd, cwd)) {
/* Current working directory or filesystem encoding was changed.
Expand relative load path again. */
- int has_relative = 1;
vm->load_path_cwd = cwd;
- rb_construct_expanded_load_path(1, &has_relative);
+ rb_construct_expanded_load_path(0, 1, &has_relative);
+ }
+ else {
+ /* Expand only tilde (User HOME) */
+ rb_construct_expanded_load_path(1, 0, &has_relative);
}
}
return vm->expanded_load_path;
diff --git a/test/ruby/test_require.rb b/test/ruby/test_require.rb
index 8c72571..ec7f2aa 100644
--- a/test/ruby/test_require.rb
+++ b/test/ruby/test_require.rb
@@ -457,4 +457,42 @@ class TestRequire < Test::Unit::TestCase
}
}
end
+
+ def test_require_unmodified_lood_path
+ Dir.mktmpdir {|tmp|
+ Dir.chdir(tmp) {
+ open("foo.rb", "w") {}
+ assert_in_out_err([], <<-INPUT, %w(:ok), [])
+ $: << "#{tmp}"
+ a = 123
+ $: << a
+ begin
+ require "foo"
+ rescue TypeError
+ end
+ p :ok if $:.pop == a
+ INPUT
+ }
+ }
+ end
+
+ def test_require_changed_home
+ home = ENV['HOME']
+ Dir.mktmpdir {|tmp|
+ Dir.chdir(tmp) {
+ open("foo.rb", "w") {}
+ Dir.mkdir("a")
+ open(File.join("a", "bar.rb"), "w") {}
+ assert_in_out_err([], <<-INPUT, %w(:ok), [])
+ $: << '~'
+ ENV['HOME'] = "#{tmp}"
+ require "foo"
+ ENV['HOME'] = "#{tmp}/a"
+ p :ok if require "bar"
+ INPUT
+ }
+ }
+ ensure
+ ENV['HOME'] = home
+ end
end
--
1.7.10.2 (Apple Git-33)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment