Emacs-Lispのリスト用関数

よく知られたmapcar以外にもリスト操作のための関数がないか探したら、seq-系の関数が該当するようだ。

www.gnu.org

(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は、IOread_nonblockと同様にexception: falseIO::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倍速い。 しかし差がつくのはそもそも接続が来ないときなんだよな。 使ったほうがよいのかどうか迷う。

Emacsのimenuで選択される関数名の横幅をもっと広くする

Emacsでプログラミングするときにhelm-imenuでよく関数定義へジャンプするのだが、デフォルトだとimenuの候補に出てくる関数名が60文字でぶった切られて、長い関数名が隠れてしまう。名前空間がネストしたり分かりやすいクラス名やメソッド名をつけると60文字を超えてしまうこともよくあるので不便だった。

imenu.elを調べたらimenu-max-item-lengthで長さを設定できることが分かった。

(setq imenu-max-item-length 256)

これで解決。

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

LinuxUNIX Domain Socketを何度もlisten(2)できるが、WSL (Windows Subsystem for Linux)は2回目のlisten(2)がエラーになる。backlogを変更しようとしてはまった。Rubyで検証した結果は次のとおり。

Linux:

$ 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)できるようだ。

Linux:

$ 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>