1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

Dir.empty? releases GVL

This converts all slow syscalls in the Dir.empty? implementation
to release GVL.  We avoid unnecessarily GVL release and
reacquire for each slow call (opendir, readdir, closedir) and
instead only release and acquire the GVL once in the common
case.

Benchmark results show a small degradation in single-threaded
performance:
Execution time (sec)
name	trunk	built
dir_empty_p	0.689	0.758

Speedup ratio: compare with the result of `trunk' (greater is better)
name	built
dir_empty_p	0.909

* dir.c (rb_gc_for_fd_with_gvl): new function
  (nogvl_dir_empty_p): ditto
  (dir_s_empty_p): use new functions to release GVL
* benchmark/bm_dir_empty_p.rb: new benchmark
  [ruby-core:83071] [Feature #13958]

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@60111 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
normal 2017-10-04 00:04:51 +00:00
parent fbb3432736
commit 1e14126b4f
2 changed files with 53 additions and 24 deletions

View file

@ -0,0 +1,5 @@
require 'tmpdir'
max = 100_000
Dir.mktmpdir('bm_dir_empty_p') do |dir|
max.times { Dir.empty?(dir) }
end

72
dir.c
View file

@ -13,6 +13,7 @@
#include "internal.h"
#include "encindex.h"
#include "ruby/thread.h"
#include <sys/types.h>
#include <sys/stat.h>
@ -722,6 +723,8 @@ fundamental_encoding_p(rb_encoding *enc)
#else
# define READDIR(dir, enc) readdir((dir))
#endif
/* safe to use without GVL */
static int
to_be_skipped(const struct dirent *dp)
{
@ -2982,6 +2985,46 @@ rb_dir_exists_p(VALUE obj, VALUE fname)
return rb_file_directory_p(obj, fname);
}
static void *
gc_for_fd_with_gvl(void *ptr)
{
int *e = ptr;
return (void *)(rb_gc_for_fd(*e) ? Qtrue : Qfalse);
}
static void *
nogvl_dir_empty_p(void *ptr)
{
const char *path = ptr;
DIR *dir = opendir(path);
struct dirent *dp;
VALUE result = Qtrue;
if (!dir) {
int e = errno;
switch ((int)(VALUE)rb_thread_call_with_gvl(gc_for_fd_with_gvl, &e)) {
default:
dir = opendir(path);
if (dir) break;
e = errno;
/* fall through */
case 0:
if (e == ENOTDIR) return (void *)Qfalse;
errno = e; /* for rb_sys_fail_path */
return (void *)Qundef;
}
}
while ((dp = READDIR(dir, NULL)) != NULL) {
if (!to_be_skipped(dp)) {
result = Qfalse;
break;
}
}
closedir(dir);
return (void *)result;
}
/*
* call-seq:
* Dir.empty?(path_name) -> true or false
@ -2992,9 +3035,7 @@ rb_dir_exists_p(VALUE obj, VALUE fname)
static VALUE
rb_dir_s_empty_p(VALUE obj, VALUE dirname)
{
DIR *dir;
struct dirent *dp;
VALUE result = Qtrue, orig;
VALUE result, orig;
const char *path;
enum {false_on_notdir = 1};
@ -3023,28 +3064,11 @@ rb_dir_s_empty_p(VALUE obj, VALUE dirname)
}
#endif
dir = opendir(path);
if (!dir) {
int e = errno;
switch (rb_gc_for_fd(e)) {
default:
dir = opendir(path);
if (dir) break;
e = errno;
/* fall through */
case 0:
if (false_on_notdir && e == ENOTDIR) return Qfalse;
rb_syserr_fail_path(e, orig);
}
result = (VALUE)rb_thread_call_without_gvl(nogvl_dir_empty_p, (void *)path,
RUBY_UBF_IO, 0);
if (result == Qundef) {
rb_sys_fail_path(orig);
}
errno = 0;
while ((dp = READDIR(dir, NULL)) != NULL) {
if (!to_be_skipped(dp)) {
result = Qfalse;
break;
}
}
closedir(dir);
return result;
}