* Some benchmarks [not found] <AANLkTi=M+qGa7G1PvYyB+fbJUzrersJQwtDkct3hZEiy@mail.gmail.com> @ 2010-12-22 14:01 ` Iñaki Baz Castillo 2010-12-22 19:56 ` Eric Wong 0 siblings, 1 reply; 7+ messages in thread From: Iñaki Baz Castillo @ 2010-12-22 14:01 UTC (permalink / raw) To: ruby.io.splice Hi, I've done some benchamarks comparing FileUtils vs io_splice when copying files in my computer (AMD 64 Phenom II X4 965): Test data: - Source file size: 496 bytes - Number of iterations: 1 Results: - FileUtils.cp: 0.00013375282287597656 - FileUtils.copy_stream: 0.0001666545867919922 - IO.splice: 6.341934204101562e-05 Test data: - Source file size: 496 bytes - Number of iterations: 10 Results: - FileUtils.cp: 0.0027534961700439453 - FileUtils.copy_stream: 0.002769947052001953 - IO.splice: 0.0014998912811279297 Test data: - Source file size: 496 bytes - Number of iterations: 100 Results: - FileUtils.cp: 0.02746438980102539 - FileUtils.copy_stream: 0.018292665481567383 - IO.splice: 0.010256767272949219 Test data: - Source file size: 496 bytes - Number of iterations: 1000 Results: - FileUtils.cp: 0.25453615188598633 - FileUtils.copy_stream: 0.13935613632202148 - IO.splice: 0.05126452445983887 Test data: - Source file size: 16102 bytes - Number of iterations: 1 Results: - FileUtils.cp: 0.00014328956604003906 - FileUtils.copy_stream: 0.0004949569702148438 - IO.splice: 0.00028967857360839844 Test data: - Source file size: 16102 bytes - Number of iterations: 10 Results: - FileUtils.cp: 0.0018320083618164062 - FileUtils.copy_stream: 0.001699686050415039 - IO.splice: 0.0004978179931640625 Test data: - Source file size: 16102 bytes - Number of iterations: 100 Results: - FileUtils.cp: 0.039102792739868164 - FileUtils.copy_stream: 0.0330507755279541 - IO.splice: 0.01671743392944336 Test data: - Source file size: 16102 bytes - Number of iterations: 1000 Results: - FileUtils.cp: 0.3610107898712158 - FileUtils.copy_stream: 0.35822200775146484 - IO.splice: 0.08929753303527832 Test data: - Source file size: 1156222 bytes - Number of iterations: 1 Results: - FileUtils.cp: 0.001172780990600586 - FileUtils.copy_stream: 0.0011954307556152344 - IO.splice: 0.0009520053863525391 Test data: - Source file size: 1156222 bytes - Number of iterations: 10 Results: - FileUtils.cp: 0.08811688423156738 - FileUtils.copy_stream: 0.09790825843811035 - IO.splice: 0.014630317687988281 Test data: - Source file size: 1156222 bytes - Number of iterations: 100 Results: - FileUtils.cp: 1.1194334030151367 - FileUtils.copy_stream: 1.5543222427368164 - IO.splice: 0.0931394100189209 Test data: - Source file size: 1156222 bytes - Number of iterations: 1000 Results: - FileUtils.cp: 12.707785606384277 - FileUtils.copy_stream: 13.745135068893433 - IO.splice: 9.723489761352539 The script is below. It's clear that using io_splice is good for big files (or lot of copies from same small source file). I don't understand why in the last test (big file, 1000 copies) io_splice takes so long, maybe it takes more time initializing each object within the benchmark block? Script: --------------------------------------------------------- #!/usr/bin/ruby require "fileutils" require "benchmark" require "io/splice" SRC_FILE = ARGV[0] DST_FILE = ARGV[1] TIMES = ( ARGV[2] ? ARGV[2].to_i : 1 ) puts "Test data:" puts "- Source file size: #{File.size(SRC_FILE)} bytes" puts "- Number of iterations: #{TIMES}" puts "Results:" print "- FileUtils.cp: " puts Benchmark.realtime { TIMES.times do FileUtils.cp SRC_FILE, DST_FILE end } print "- FileUtils.copy_stream: " puts Benchmark.realtime { TIMES.times do FileUtils.copy_stream SRC_FILE, DST_FILE end } print "- IO.splice: " puts Benchmark.realtime { TIMES.times do |n| source = File.open(SRC_FILE, 'rb') dest = File.open(DST_FILE + "_#{n}", 'wb') source_fd = source.fileno dest_fd = dest.fileno # We use a pipe as a ring buffer in kernel space. # pipes may store up to IO::Splice::PIPE_CAPA bytes pipe = IO.pipe rfd, wfd = pipe.map { |io| io.fileno } begin nread = begin # first pull as many bytes as possible into the pipe IO.splice(source_fd, nil, wfd, nil, IO::Splice::PIPE_CAPA, 0) rescue EOFError break end # now move the contents of the pipe buffer into the destination file # the copied data never enters userspace nwritten = IO.splice(rfd, nil, dest_fd, nil, nread, 0) nwritten == nread or abort "short splice to destination file: #{nwritten} != #{nread}" end while true end } --------------------------------------------------------- I've tryed to declare source, source_fd and pipe = IO.pipe before the benchmark block but then I get empty copied files (I need to declare all of them within the benchmark block). I assume the test script can be improved. Regards. -- Iñaki Baz Castillo <ibc@aliax.net> ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: Some benchmarks 2010-12-22 14:01 ` Some benchmarks Iñaki Baz Castillo @ 2010-12-22 19:56 ` Eric Wong 2010-12-23 15:41 ` Iñaki Baz Castillo 0 siblings, 1 reply; 7+ messages in thread From: Eric Wong @ 2010-12-22 19:56 UTC (permalink / raw) To: ruby.io.splice Iñaki Baz Castillo <ibc@aliax.net> wrote: > Hi, I've done some benchamarks comparing FileUtils vs io_splice when > copying files in my computer (AMD 64 Phenom II X4 965): <snip> > Test data: > - Source file size: 1156222 bytes > - Number of iterations: 1000 > Results: > - FileUtils.cp: 12.707785606384277 > - FileUtils.copy_stream: 13.745135068893433 > - IO.splice: 9.723489761352539 > > > The script is below. > > It's clear that using io_splice is good for big files (or lot of > copies from same small source file). > > I don't understand why in the last test (big file, 1000 copies) > io_splice takes so long, maybe it takes more time initializing each > object within the benchmark block? It'ls likely the test wrote enough to force blocking writes to disk, and your disk is now the bottleneck. In that case, all the memory tricks in the world won't help :) Try it on a tmpfs mount. Also, relying on GC to close file descriptors could be a small performance problem given the number of iterations. > Script: > --------------------------------------------------------- > #!/usr/bin/ruby > > require "fileutils" > require "benchmark" > require "io/splice" > > > SRC_FILE = ARGV[0] > DST_FILE = ARGV[1] > TIMES = ( ARGV[2] ? ARGV[2].to_i : 1 ) > > > puts "Test data:" > puts "- Source file size: #{File.size(SRC_FILE)} bytes" > puts "- Number of iterations: #{TIMES}" > > > puts "Results:" > > print "- FileUtils.cp: " > puts Benchmark.realtime { > TIMES.times do > FileUtils.cp SRC_FILE, DST_FILE > end > } > > > print "- FileUtils.copy_stream: " > puts Benchmark.realtime { > TIMES.times do > FileUtils.copy_stream SRC_FILE, DST_FILE > end > } > > > print "- IO.splice: " > puts Benchmark.realtime { > TIMES.times do |n| > source = File.open(SRC_FILE, 'rb') > dest = File.open(DST_FILE + "_#{n}", 'wb') > source_fd = source.fileno > dest_fd = dest.fileno > > # We use a pipe as a ring buffer in kernel space. > # pipes may store up to IO::Splice::PIPE_CAPA bytes > pipe = IO.pipe You can reuse the pipe object through multiple runs assuming you drain it properly. > rfd, wfd = pipe.map { |io| io.fileno } > > begin > nread = begin > # first pull as many bytes as possible into the pipe > IO.splice(source_fd, nil, wfd, nil, IO::Splice::PIPE_CAPA, 0) > rescue EOFError > break > end > > # now move the contents of the pipe buffer into the destination file > # the copied data never enters userspace > nwritten = IO.splice(rfd, nil, dest_fd, nil, nread, 0) > > nwritten == nread or > abort "short splice to destination file: #{nwritten} != #{nread}" > end while true > end > } > --------------------------------------------------------- > > I've tryed to declare source, source_fd and pipe = IO.pipe before the > benchmark block but then I get empty copied files (I need to declare > all of them within the benchmark block). I assume the test script can > be improved. You need to rewind source if you don't specify an input offset for splice. Likewise for the dest and destination offset. The open+close is part of the test for the FileUtils things, though, so I would just open close (you were leaving the close up to GC, which usually fine for MRI but not for benchmark purposes). ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: Some benchmarks 2010-12-22 19:56 ` Eric Wong @ 2010-12-23 15:41 ` Iñaki Baz Castillo 2010-12-23 18:06 ` Eric Wong 2010-12-27 17:33 ` Iñaki Baz Castillo 0 siblings, 2 replies; 7+ messages in thread From: Iñaki Baz Castillo @ 2010-12-23 15:41 UTC (permalink / raw) To: ruby.io.splice 2010/12/22 Eric Wong <normalperson@yhbt.net>: >> I don't understand why in the last test (big file, 1000 copies) >> io_splice takes so long, maybe it takes more time initializing each >> object within the benchmark block? > > It'ls likely the test wrote enough to force blocking writes to disk, > and your disk is now the bottleneck. In that case, all the memory > tricks in the world won't help :) Aha, it makes sense. > Try it on a tmpfs mount. Yes, that would be my next test. > Also, relying on GC to close file descriptors could be a small > performance problem given the number of iterations. Well, this is just a strange use case test. Indeed I should call File.close by myself. >> # We use a pipe as a ring buffer in kernel space. >> # pipes may store up to IO::Splice::PIPE_CAPA bytes >> pipe = IO.pipe > > You can reuse the pipe object through multiple runs assuming you drain > it properly. So I need to learn what "draining a pipe" means. I assume I need to empty/rewind it. I suppose there ar API methods for this. >> I've tryed to declare source, source_fd and pipe = IO.pipe before the >> benchmark block but then I get empty copied files (I need to declare >> all of them within the benchmark block). I assume the test script can >> be improved. > > You need to rewind source if you don't specify an input offset for splice. > Likewise for the dest and destination offset. The open+close is part of > the test for the FileUtils things, though, so I would just open close > (you were leaving the close up to GC, which usually fine for MRI but not > for benchmark purposes). Ok. Thanks a lot! -- Iñaki Baz Castillo <ibc@aliax.net> ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: Some benchmarks 2010-12-23 15:41 ` Iñaki Baz Castillo @ 2010-12-23 18:06 ` Eric Wong 2010-12-27 10:01 ` Iñaki Baz Castillo 2010-12-27 17:33 ` Iñaki Baz Castillo 1 sibling, 1 reply; 7+ messages in thread From: Eric Wong @ 2010-12-23 18:06 UTC (permalink / raw) To: ruby.io.splice Iñaki Baz Castillo <ibc@aliax.net> wrote: > 2010/12/22 Eric Wong <normalperson@yhbt.net>: > > You can reuse the pipe object through multiple runs assuming you drain > > it properly. > > So I need to learn what "draining a pipe" means. I assume I need to > empty/rewind it. I suppose there ar API methods for this. Just ensure you read everything from the pipe between each iteration (which you seem to be doing). A pipe is just a ring buffer inside the kernel. Traditionally write() puts data into the buffer, read() removes it, and nowadays splice() can do it without copying the data into userspace. -- Eric Wong ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: Some benchmarks 2010-12-23 18:06 ` Eric Wong @ 2010-12-27 10:01 ` Iñaki Baz Castillo 0 siblings, 0 replies; 7+ messages in thread From: Iñaki Baz Castillo @ 2010-12-27 10:01 UTC (permalink / raw) To: ruby.io.splice 2010/12/23 Eric Wong <normalperson@yhbt.net>: > Just ensure you read everything from the pipe between each iteration > (which you seem to be doing). A pipe is just a ring buffer inside the > kernel. Traditionally write() puts data into the buffer, read() removes > it, and nowadays splice() can do it without copying the data into > userspace. Ok, thanks a lot. I'm coding a server using EventMachine, and the reactor process communicates with other processes by Posix_MQ. In case the message body is too big then I plan the reactor to copy the body data to a file (maybe ramfs) and pass the file path to the worker processes who would read it. So I'll come back with some questions soon :) -- Iñaki Baz Castillo <ibc@aliax.net> ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: Some benchmarks 2010-12-23 15:41 ` Iñaki Baz Castillo 2010-12-23 18:06 ` Eric Wong @ 2010-12-27 17:33 ` Iñaki Baz Castillo 2010-12-27 21:38 ` Eric Wong 1 sibling, 1 reply; 7+ messages in thread From: Iñaki Baz Castillo @ 2010-12-27 17:33 UTC (permalink / raw) To: ruby.io.splice [-- Attachment #1: Type: text/plain, Size: 1850 bytes --] 2010/12/23 Iñaki Baz Castillo <ibc@aliax.net>: >> Try it on a tmpfs mount. > > Yes, that would be my next test. Hummm, I get "non expected" results when using a tmpfs as destination for the copy. I attach a new test file which makes usage of IO splice in different ways ("manual", using IO objects and using file names). Results: Test data: - Source file size: 464 bytes - Number of iterations: 1 Results: - FileUtils.cp: 0.0004868507385253906 - FileUtils.copy_stream: 7.581710815429688e-05 - IO.splice (manual): 0.0004825592041015625 - IO.splice (using IO): 0.00017380714416503906 - IO.splice (using file names): 0.00012922286987304688 Test data: - Source file size: 464 bytes - Number of iterations: 100 Results: - FileUtils.cp: 0.005738258361816406 - FileUtils.copy_stream: 0.0026025772094726562 - IO.splice (manual): 0.038815975189208984 - IO.splice (using IO): 0.016385793685913086 - IO.splice (using file names): 0.009386062622070312 Test data: - Source file size: 1156222 bytes - Number of iterations: 1 Results: - FileUtils.cp: 0.0017499923706054688 - FileUtils.copy_stream: 0.0020220279693603516 - IO.splice (manual): 0.001650094985961914 - IO.splice (using IO): 0.001832723617553711 - IO.splice (using file names): 0.0017614364624023438 Test data: - Source file size: 1156222 bytes - Number of iterations: 100 Results: - FileUtils.cp: 0.20581936836242676 - FileUtils.copy_stream: 0.19777441024780273 - IO.splice (manual): 0.1709754467010498 - IO.splice (using IO): 0.19835829734802246 - IO.splice (using file names): 0.1958324909210205 -- Iñaki Baz Castillo <ibc@aliax.net> ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: Some benchmarks 2010-12-27 17:33 ` Iñaki Baz Castillo @ 2010-12-27 21:38 ` Eric Wong 0 siblings, 0 replies; 7+ messages in thread From: Eric Wong @ 2010-12-27 21:38 UTC (permalink / raw) To: ruby.io.splice Iñaki Baz Castillo <ibc@aliax.net> wrote: > 2010/12/23 Iñaki Baz Castillo <ibc@aliax.net>: > >> Try it on a tmpfs mount. > > > > Yes, that would be my next test. > > Hummm, I get "non expected" results when using a tmpfs as destination > for the copy. I attach a new test file which makes usage of IO splice > in different ways ("manual", using IO objects and using file names). The setup of the splice methods is rather expensive (both the pure Ruby portion and also the kernel pipe()). I don't think a 1M file is really enough to test with (consider the size of L2 caches these days), try playing around with bigger files, maybe tens or even hundreds of megs (if it can fit in tmpfs). You should also get Benchmark to report system and user time, realtime can be misleading -- Eric Wong ^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2010-12-27 21:39 UTC | newest] Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- [not found] <AANLkTi=M+qGa7G1PvYyB+fbJUzrersJQwtDkct3hZEiy@mail.gmail.com> 2010-12-22 14:01 ` Some benchmarks Iñaki Baz Castillo 2010-12-22 19:56 ` Eric Wong 2010-12-23 15:41 ` Iñaki Baz Castillo 2010-12-23 18:06 ` Eric Wong 2010-12-27 10:01 ` Iñaki Baz Castillo 2010-12-27 17:33 ` Iñaki Baz Castillo 2010-12-27 21:38 ` Eric Wong
Code repositories for project(s) associated with this public inbox https://yhbt.net/ruby_io_splice.git/ This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).