From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on dcvr.yhbt.net X-Spam-Level: X-Spam-ASN: X-Spam-Status: No, score=-2.8 required=3.0 tests=ALL_TRUSTED,AWL,BAYES_00 shortcircuit=no autolearn=unavailable version=3.3.2 X-Original-To: sleepy-penguin@bogomips.org Received: from localhost (dcvr.yhbt.net [127.0.0.1]) by dcvr.yhbt.net (Postfix) with ESMTP id 117BE633820; Wed, 16 Mar 2016 03:13:50 +0000 (UTC) From: Eric Wong To: sleepy-penguin@bogomips.org Cc: ruby-io-splice@bogomips.org, Eric Wong Subject: [PATCH 2/2] implement copy_file_range support for Linux 4.5+ Date: Wed, 16 Mar 2016 03:13:40 +0000 Message-Id: <20160316031340.7164-3-e@80x24.org> In-Reply-To: <20160316031340.7164-1-e@80x24.org> References: <20160316031340.7164-1-e@80x24.org> List-Id: Under Linux 4.5, this allows for efficient copies and is similar to the splice and sendfile system calls. --- ext/sleepy_penguin/cfr.c | 68 +++++++++++++++++++++++++++++++++++++++++++ ext/sleepy_penguin/extconf.rb | 1 + ext/sleepy_penguin/init.c | 2 ++ ext/sleepy_penguin/sp_copy.h | 50 +++++++++++++++++++++++++++++++ ext/sleepy_penguin/splice.c | 54 ++-------------------------------- test/test_cfr.rb | 29 ++++++++++++++++++ 6 files changed, 153 insertions(+), 51 deletions(-) create mode 100644 ext/sleepy_penguin/cfr.c create mode 100644 ext/sleepy_penguin/sp_copy.h create mode 100644 test/test_cfr.rb diff --git a/ext/sleepy_penguin/cfr.c b/ext/sleepy_penguin/cfr.c new file mode 100644 index 0000000..ea17f82 --- /dev/null +++ b/ext/sleepy_penguin/cfr.c @@ -0,0 +1,68 @@ +#include "sleepy_penguin.h" +#include "sp_copy.h" +#include + +#ifndef HAVE_COPY_FILE_RANGE +# include +# if !defined(__NR_copy_file_range) && \ + (defined(__x86_64__) || defined(__i386__)) +# define __NR_copy_file_range 285 +# endif /* __NR_copy_file_range */ +#endif + +#ifdef __NR_copy_file_range +static ssize_t my_cfr(int fd_in, off_t *off_in, int fd_out, off_t *off_out, + size_t len, unsigned int flags) +{ + long n = syscall(__NR_copy_file_range, + fd_in, off_in, fd_out, off_out, len, flags); + + return (ssize_t)n; +} +# define copy_file_range(fd_in,off_in,fd_out,off_out,len,flags) \ + my_cfr((fd_in),(off_in),(fd_out),(off_out),(len),(flags)) +#endif + +static void *nogvl_cfr(void *ptr) +{ + struct copy_args *a = ptr; + + return (void *)copy_file_range(a->fd_in, a->off_in, + a->fd_out, a->off_out, a->len, a->flags); +} + +static VALUE rb_cfr(int argc, VALUE *argv, VALUE mod) +{ + off_t i, o; + VALUE io_in, off_in, io_out, off_out, len, flags; + ssize_t bytes; + struct copy_args a; + + rb_scan_args(argc, argv, "51", + &io_in, &off_in, &io_out, &off_out, &len, &flags); + + a.off_in = NIL_P(off_in) ? NULL : (i = NUM2OFFT(off_in), &i); + a.off_out = NIL_P(off_out) ? NULL : (o = NUM2OFFT(off_out), &o); + a.len = NUM2SIZET(len); + a.flags = NIL_P(flags) ? 0 : NUM2UINT(flags); + +again: + a.fd_in = rb_sp_fileno(io_in); + a.fd_out = rb_sp_fileno(io_out); + bytes = (ssize_t)IO_RUN(nogvl_cfr, &a); + if (bytes < 0) { + if (errno == EINTR) + goto again; + rb_sys_fail("copy_file_range"); + } else if (bytes == 0) { + rb_eof_error(); + } + return SSIZET2NUM(bytes); +} + +void sleepy_penguin_init_cfr(void) +{ + VALUE mod = rb_define_module("SleepyPenguin"); + + rb_define_singleton_method(mod, "copy_file_range", rb_cfr, -1); +} diff --git a/ext/sleepy_penguin/extconf.rb b/ext/sleepy_penguin/extconf.rb index 46d1059..53a2810 100644 --- a/ext/sleepy_penguin/extconf.rb +++ b/ext/sleepy_penguin/extconf.rb @@ -19,6 +19,7 @@ end have_type('clockid_t', 'time.h') have_func('clock_gettime', 'time.h') +have_func('copy_file_range', 'unistd.h') have_func('epoll_create1', %w(sys/epoll.h)) have_func('inotify_init1', %w(sys/inotify.h)) have_func('splice', %w(fcntl.h)) diff --git a/ext/sleepy_penguin/init.c b/ext/sleepy_penguin/init.c index 93e8092..0a1458b 100644 --- a/ext/sleepy_penguin/init.c +++ b/ext/sleepy_penguin/init.c @@ -53,6 +53,7 @@ void sleepy_penguin_init_signalfd(void); #endif void sleepy_penguin_init_splice(void); +void sleepy_penguin_init_cfr(void); static size_t l1_cache_line_size_detect(void) { @@ -130,4 +131,5 @@ void Init_sleepy_penguin_ext(void) sleepy_penguin_init_inotify(); sleepy_penguin_init_signalfd(); sleepy_penguin_init_splice(); + sleepy_penguin_init_cfr(); } diff --git a/ext/sleepy_penguin/sp_copy.h b/ext/sleepy_penguin/sp_copy.h new file mode 100644 index 0000000..83b9554 --- /dev/null +++ b/ext/sleepy_penguin/sp_copy.h @@ -0,0 +1,50 @@ +/* common splice and copy_file_range-related definitions */ + +#ifndef SSIZET2NUM +# define SSIZET2NUM(x) LONG2NUM(x) +#endif +#ifndef NUM2SIZET +# define NUM2SIZET(x) NUM2ULONG(x) +#endif + +#if defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL) && defined(HAVE_RUBY_THREAD_H) +/* Ruby 2.0+ */ +# include +# define WITHOUT_GVL(fn,a,ubf,b) \ + rb_thread_call_without_gvl((fn),(a),(ubf),(b)) +#elif defined(HAVE_RB_THREAD_BLOCKING_REGION) +typedef VALUE (*my_blocking_fn_t)(void*); +# define WITHOUT_GVL(fn,a,ubf,b) \ + rb_thread_blocking_region((my_blocking_fn_t)(fn),(a),(ubf),(b)) + +#else /* Ruby 1.8 */ +/* partial emulation of the 1.9 rb_thread_blocking_region under 1.8 */ +# include +# define RUBY_UBF_IO ((rb_unblock_function_t *)-1) +typedef void rb_unblock_function_t(void *); +typedef void * rb_blocking_function_t(void *); +static void * WITHOUT_GVL(rb_blocking_function_t *func, void *data1, + rb_unblock_function_t *ubf, void *data2) +{ + void *rv; + + assert(RUBY_UBF_IO == ubf && "RUBY_UBF_IO required for emulation"); + + TRAP_BEG; + rv = func(data1); + TRAP_END; + + return rv; +} +#endif /* ! HAVE_RB_THREAD_BLOCKING_REGION */ + +#define IO_RUN(fn,data) WITHOUT_GVL((fn),(data),RUBY_UBF_IO,0) + +struct copy_args { + int fd_in; + int fd_out; + off_t *off_in; + off_t *off_out; + size_t len; + unsigned flags; +}; diff --git a/ext/sleepy_penguin/splice.c b/ext/sleepy_penguin/splice.c index d2f9206..2f901a8 100644 --- a/ext/sleepy_penguin/splice.c +++ b/ext/sleepy_penguin/splice.c @@ -1,4 +1,5 @@ #include "sleepy_penguin.h" +#include "sp_copy.h" #ifdef HAVE_SPLICE #include #include @@ -18,13 +19,6 @@ static VALUE sym_EAGAIN; # define F_GETPIPE_SZ (F_LINUX_SPECIFIC_BASE + 8) #endif -#ifndef SSIZET2NUM -# define SSIZET2NUM(x) LONG2NUM(x) -#endif -#ifndef NUM2SIZET -# define NUM2SIZET(x) NUM2ULONG(x) -#endif - static int check_fileno(VALUE io) { int saved_errno = errno; @@ -33,51 +27,9 @@ static int check_fileno(VALUE io) return fd; } -#if defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL) && defined(HAVE_RUBY_THREAD_H) -/* Ruby 2.0+ */ -# include -# define WITHOUT_GVL(fn,a,ubf,b) \ - rb_thread_call_without_gvl((fn),(a),(ubf),(b)) -#elif defined(HAVE_RB_THREAD_BLOCKING_REGION) -typedef VALUE (*my_blocking_fn_t)(void*); -# define WITHOUT_GVL(fn,a,ubf,b) \ - rb_thread_blocking_region((my_blocking_fn_t)(fn),(a),(ubf),(b)) - -#else /* Ruby 1.8 */ -/* partial emulation of the 1.9 rb_thread_blocking_region under 1.8 */ -# include -# define RUBY_UBF_IO ((rb_unblock_function_t *)-1) -typedef void rb_unblock_function_t(void *); -typedef void * rb_blocking_function_t(void *); -static void * WITHOUT_GVL(rb_blocking_function_t *func, void *data1, - rb_unblock_function_t *ubf, void *data2) -{ - void *rv; - - assert(RUBY_UBF_IO == ubf && "RUBY_UBF_IO required for emulation"); - - TRAP_BEG; - rv = func(data1); - TRAP_END; - - return rv; -} -#endif /* ! HAVE_RB_THREAD_BLOCKING_REGION */ - -#define IO_RUN(fn,data) WITHOUT_GVL((fn),(data),RUBY_UBF_IO,0) - -struct splice_args { - int fd_in; - int fd_out; - off_t *off_in; - off_t *off_out; - size_t len; - unsigned flags; -}; - static void *nogvl_splice(void *ptr) { - struct splice_args *a = ptr; + struct copy_args *a = ptr; return (void *)splice(a->fd_in, a->off_in, a->fd_out, a->off_out, a->len, a->flags); @@ -87,7 +39,7 @@ static ssize_t do_splice(int argc, VALUE *argv, unsigned dflags) { off_t i = 0, o = 0; VALUE io_in, off_in, io_out, off_out, len, flags; - struct splice_args a; + struct copy_args a; ssize_t bytes; ssize_t total = 0; diff --git a/test/test_cfr.rb b/test/test_cfr.rb new file mode 100644 index 0000000..3483c5a --- /dev/null +++ b/test/test_cfr.rb @@ -0,0 +1,29 @@ +# -*- encoding: binary -*- +require 'test/unit' +require 'tempfile' +$-w = true +require 'sleepy_penguin' + +class TestCfr < Test::Unit::TestCase + def test_copy_file_range + str = 'abcde' + size = 5 + src = Tempfile.new('ruby_cfr_src') + dst = Tempfile.new('ruby_cfr_dst') + assert_equal 5, src.syswrite(str) + src.sysseek(0) + begin + nr = SleepyPenguin.copy_file_range(src, nil, dst, nil, size, 0) + rescue Errno::EINVAL + warn 'copy_file_range not supported (requires Linux 4.5+)' + warn "We have: #{`uname -a`}" + return + end + assert_equal nr, 5 + dst.sysseek(0) + assert_equal str, dst.sysread(5) + ensure + dst.close! + src.close! + end +end if SleepyPenguin.respond_to?(:copy_file_range) -- EW