Emacs-Lispのリスト用関数
よく知られたmapcar
以外にもリスト操作のための関数がないか探したら、seq-
系の関数が該当するようだ。
(seq-elt sequence index) (seq-length sequence) (seqp sequence) (seq-drop sequence n) (seq-take sequence n) (seq-take-while predicate sequence) (seq-drop-while predicate sequence) (seq-do function sequence) (seq-map function sequence) (seq-map-indexed function sequence) (seq-mapn function &rest sequences) (seq-remove predicate sequence) (seq-reduce function sequence initial-value) (seq-some predicate sequence) (seq-find predicate sequence &optional default) (seq-every-p predicate sequence) (seq-empty-p sequence) (seq-count predicate sequence) (seq-sort function sequence) (seq-sort-by function predicate sequence) (seq-set-equal-p sequence1 sequence2 &optional testfn) (seq-position sequence elt &optional function) (seq-uniq sequence &optional function) (seq-subseq sequence start &optional end) (seq-concatenate type &rest sequences) (seq-mapcat function sequence &optional type) (seq-partition sequence n) (seq-intersection sequence1 sequence2 &optional function) (seq-difference sequence1 sequence2 &optional function) (seq-group-by function sequence) (seq-into sequence type) (seq-min sequence) (seq-max sequence) (seq-doseq (var sequence) body...) (seq-let arguments sequence body...) (seq-random-elt sequence)
たいていのことは出来そうだ。
RubyのRefinementされたメソッドの性能
通常のメソッドと比べて性能に差があるのかどうか比べてみた。
require 'benchmark' class Integer def to_comma to_s.reverse.scan(/\d\d?\d?/).join(',').reverse end end def method_no_args end module RefineMethod refine Object do def refine_method_no_args end end end n = ARGV.shift || '1_000_000' n = n.to_i puts "#{n.to_comma} times." Benchmark.bm(45) do |x| x.report('method_no_args') { n.times do method_no_args end } x.report('refine_method_no_args') { using RefineMethod n.times do refine_method_no_args end } end
結果、まったく差がなかった。
1,000,000 times. user system total real method_no_args 0.549699 0.000000 0.549699 ( 0.550169) refine_method_no_args 0.500810 0.000000 0.500810 ( 0.501193)
性能のことは気にせずRefinementを使おう。
Rubyのaccept_nonblock(exception: false)
Rubyのサーバーソケットのaccept_nonblock
は、IO
のread_nonblock
と同様にexception: false
でIO::WaitReadable
を投げないようにできる。
例外を投げる場合と投げない場合で、どれくらい差があるものなのか実験してみた。
require 'benchmark' require 'socket' class Integer def to_comma to_s.reverse.scan(/\d\d?\d?/).join(',').reverse end end repeat_num = Integer(ARGV.shift || '1_000_000') puts "repeat: #{repeat_num.to_comma}" s = TCPServer.new(0) Benchmark.bm(35) do |x| x.report('accept_nonblock') do repeat_num.times do begin s.accept_nonblock rescue IO::WaitReadable, Errno::EINTR # nothing to do. end end end x.report('accept_nonblock(exception: false)') do repeat_num.times do begin if (r = s.accept_nonblock(exception: false)) then if (r != :wait_readable) then r end end rescue Errno::EINTR # `exception: false'で捕捉されるのは # EAGAIN/EWOULDBLOCK,ECONNABORTE,EPROTOだけなので、例外EINTRの # 捕捉は必要。 end end end end
結果:
$ ./benchmark_socket_accept_nonblock.rb repeat: 1,000,000 user system total real accept_nonblock 20.477151 7.877416 28.354567 ( 29.273192) accept_nonblock(exception: false) 2.606960 6.471008 9.077968 ( 9.108173)
exception: false
の方が3倍速い。
しかし差がつくのはそもそも接続が来ないときなんだよな。
使ったほうがよいのかどうか迷う。
rubyにおけるソケットのaccept_nonblockの競合
Rubyで1つのサーバーソケットを2つのプロセスから同時にaccept_nonblockするとどうなるか。サーバーのgraceful restartでよくありそうなシチュエーションを試してみる。
実験コード:
require 'io/wait' require 'socket' s = TCPServer.new(0) p [ :listen, s ] fork{ begin p [ $$, :wait, s.wait_readable(3600) ] p [ $$, :accept, s.accept_nonblock ] rescue p [ $$, $! ] end } fork{ begin p [ $$, :wait, s.wait_readable(3600) ] p [ $$, :accept, s.accept_nonblock ] rescue p [ $$, $! ] end } sleep 1 fork{ TCPSocket.new('localhost', s.local_address.ip_port) } Process.waitall
結果:
$ ruby sock_accept_nonblock_dup.rb [:listen, #<TCPServer:fd 5, AF_INET, 0.0.0.0, 45821>] [13290, :wait, #<TCPServer:fd 5, AF_INET, 0.0.0.0, 45821>] [13291, :wait, #<TCPServer:fd 5, AF_INET, 0.0.0.0, 45821>] [13291, :accept, #<TCPSocket:fd 6, AF_INET, 127.0.0.1, 45821>] [13290, #<IO::EAGAINWaitReadable: Resource temporarily unavailable - accept(2) would block>]
やはり片方が成功しもう片方はリトライしなければならないようだ。
LinuxとWSLの差異: UNIX Domain Socket
LinuxはUNIX Domain Socketを何度もlisten(2)できるが、WSL (Windows Subsystem for Linux)は2回目のlisten(2)がエラーになる。backlogを変更しようとしてはまった。Rubyで検証した結果は次のとおり。
$ uname -a Linux gut 4.14.74-v7+ #1149 SMP Mon Oct 8 17:39:42 BST 2018 armv7l GNU/Linux $ irb -r socket irb(main):001:0> s = UNIXServer.new('/tmp/s') => #<UNIXServer:/tmp/s> irb(main):002:0> s.listen(10) => 0 irb(main):003:0>
WSL:
$ uname -a Linux DESKTOP-T2R3I3H 4.4.0-17763-Microsoft #253-Microsoft Mon Dec 31 17:49:00 PST 2018 x86_64 x86_64 x86_64 GNU/Linux $ irb -r socket irb(main):001:0> s = UNIXServer.new('/tmp/s') => #<UNIXServer:/tmp/s> irb(main):002:0> s.listen(10) Traceback (most recent call last): 5: from /usr/local/bin/irb:23:in `<main>' 4: from /usr/local/bin/irb:23:in `load' 3: from /usr/local/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>' 2: from (irb):2 1: from (irb):2:in `listen' Errno::EADDRINUSE (Address already in use - listen(2)) irb(main):003:0>
ちなみにTCP Socketは両方とも何度でもlisten(2)できるようだ。
$ uname -a Linux gut 4.14.74-v7+ #1149 SMP Mon Oct 8 17:39:42 BST 2018 armv7l GNU/Linux $ irb -r socket irb(main):001:0> s = TCPServer.new(0) => #<TCPServer:fd 7, AF_INET, 0.0.0.0, 39901> irb(main):002:0> s.listen(10) => 0 irb(main):003:0>
WSL:
$ uname -a Linux DESKTOP-T2R3I3H 4.4.0-17763-Microsoft #253-Microsoft Mon Dec 31 17:49:00 PST 2018 x86_64 x86_64 x86_64 GNU/Linux $ irb -r socket irb(main):001:0> s = TCPServer.new(0) => #<TCPServer:fd 7, AF_INET, 0.0.0.0, 56466> irb(main):002:0> s.listen(10) => 0 irb(main):003:0>