TCP localhostとUnix Domain Socketはどちらが速いのか？

きっかけ
-------

双方向のプロセス間通信をする必要が出てきたのですが、TCPのlocalhost接続とUNIX Domain Socketと、どっちの方がパフォーマンスがいいのか、実測してみました


検証内容
--------

接続するとランダムな文字列を返すだけのサーバを作成
以下の接続方法について、10,000リクエストを同時接続 1/5/10 と条件を変えて計測

1. TCP localhost
2. Unix Domain Socket / Filesystem Namespace
3. Unix Domain Socket / Abstract Namespace


結論
----

UNIX Domain Socketの方が速い

当環境においては19倍の違いが計測された

また、UNIX Domain Socketの名前空間の比較においては、極めて若干だがAbstract名前空間の方が速い

プロセス間通信においては、サーバプロセス終了時のファイル削除の考慮も不要で扱いやすく、速度も速いUnix Domain SocketのAbstract名前空間を使用するのが好ましいと言える

グラフ: https://docs.google.com/spreadsheets/d/1yLxWHZtYPAvJLu0NT3OXpBn4s3tugsxYFcKQjtb440w/pubhtml


考察
----

TCP側が遅いのは、ソケット操作処理が支配的なのだと考えられる


計測環境とログ
--------------

* NEC LaViE G Type Z
  * Intel(R) Core(TM) i7-4500U CPU @ 1.80GHz
  * MemTotal:        3966364 kB
  * Disk: Model=SAMSUNG MZMTD256HAGM-000L1
* Ubuntu 14.04
* ruby 2.1.3p242
  * Celluloid-IO 0.16.1

### ログ ###

```
$ bundle exec ruby client.rb 
       user     system      total        real
tcp__1  1.150000   0.490000   1.640000 (  1.751974)
ufs__1  0.210000   0.220000   0.430000 (  1.326359)
uab__1  0.230000   0.170000   0.400000 (  1.314750)
tcp__5 17.040000   8.420000  25.460000 ( 21.795437)
ufs__5  0.660000   0.550000   1.210000 (  4.083943)
uab__5  0.800000   0.460000   1.260000 (  4.174697)
tcp_10 31.830000  16.440000  48.270000 ( 41.255494)
ufs_10  1.490000   1.040000   2.530000 (  8.242772)
uab_10  1.600000   0.890000   2.490000 (  8.438751)
$ bundle exec ruby client.rb 
       user     system      total        real
tcp__1  1.270000   0.440000   1.710000 (  1.823257)
ufs__1  0.360000   0.180000   0.540000 (  1.650133)
uab__1  0.210000   0.110000   0.320000 (  1.112440)
tcp__5 16.390000   8.130000  24.520000 ( 21.806182)
ufs__5  0.820000   0.610000   1.430000 (  4.612291)
uab__5  0.820000   0.560000   1.380000 (  4.604220)
tcp_10 32.470000  16.470000  48.940000 ( 41.773062)
ufs_10  1.500000   1.030000   2.530000 (  8.260133)
uab_10  1.460000   0.970000   2.430000 (  8.183293)
$ bundle exec ruby client.rb 
       user     system      total        real
tcp__1  1.240000   0.490000   1.730000 (  1.854051)
ufs__1  0.230000   0.170000   0.400000 (  1.298220)
uab__1  0.160000   0.200000   0.360000 (  1.197231)
tcp__5 16.720000   8.260000  24.980000 ( 22.068534)
ufs__5  0.870000   0.600000   1.470000 (  4.631703)
uab__5  0.830000   0.510000   1.340000 (  4.425737)
tcp_10 32.260000  16.220000  48.480000 ( 41.639263)
ufs_10  1.600000   0.950000   2.550000 (  8.327180)
uab_10  1.540000   1.010000   2.550000 (  8.519080)
```

### 検証コード ###

```ruby:Gemfile
source "https://rubygems.org"
gem "celluloid-io", :require => "celluloid/io"
gem "parallel"
```

```ruby:server.rb
require 'bundler'
Bundler.require(:default)

class BaseServer
  include Celluloid::IO
  finalizer :finalize

  def initialize
    raise "abstract"
  end

  def finalize
    @server.close if @server
  end

  def run
    loop { async.handle_connection @server.accept }
  end

  def handle_connection(socket)
    socket.write rand.to_s
  rescue EOFError
    nil
  ensure
    socket.close
  end
end

class UServer < BaseServer
  attr_reader :socket_path, :server

  def initialize(socket_path)
    STDERR.puts "** UnixServer start"
    @socket_path = socket_path
    @server = UNIXServer.new(@socket_path)
    async.run
  end

  def finalize
    super
    File.delete(@socket_path) if File.exists?(@socket_path)
  end
end

class TServer < BaseServer
  attr_reader :server

  def initialize(opts)
    STDERR.puts "** TCPServer start"
    @server = TCPServer.new(opts[:host], opts[:port])
    async.run
  end
end

unix_fs = UServer.supervise("./socket")
unix_ab = UServer.supervise("\0socket")
tcpl = TServer.supervise(:host => "127.0.0.1", :port => 1234)
trap("INT") {
  unix_fs.terminate
  unix_ab.terminate
  tcpl.terminate
  exit
}
STDERR.puts "STOP is CTRL+C"
sleep
```

```ruby:client.rb
require 'bundler'
Bundler.require(:default)
require "benchmark"

class ClientBase
  attr_reader :socket, :opts

  def run(loop_counter, mark)
    i = 0
    while (i < loop_counter) do
      @socket.open(*@opts) do |s|
        _ = "#{mark} > #{s.readpartial(4096)}"
      end
      i += 1
    end
  end
end

class TClient < ClientBase
  def initialize(opts)
    @opts = [opts[:host], opts[:port]]
    @socket = Celluloid::IO::TCPSocket
  end
end

class UClient < ClientBase
  def initialize(opts)
    @opts = [opts]
    @socket = Celluloid::IO::UNIXSocket
  end
end

tcp= TClient.new(:host => "127.0.0.1", :port => 1234)
ufs = UClient.new("./socket")
uab = UClient.new("\0socket")

reqs = 10_000

Benchmark.bm {|x|
  x.report(:tcp__1) { Parallel.each(1..1, :in_threads => 1) do |i| tcp.run(reqs, i) ; end }
  x.report(:ufs__1) { Parallel.each(1..1, :in_threads => 1) do |i| ufs.run(reqs, i) ; end }
  x.report(:uab__1) { Parallel.each(1..1, :in_threads => 1) do |i| uab.run(reqs, i) ; end }
  x.report(:tcp__5) { Parallel.each(1..5, :in_threads => 5) do |i| tcp.run(reqs, i) ; end }
  x.report(:ufs__5) { Parallel.each(1..5, :in_threads => 5) do |i| ufs.run(reqs, i) ; end }
  x.report(:uab__5) { Parallel.each(1..5, :in_threads => 5) do |i| uab.run(reqs, i) ; end }
  x.report(:tcp_10) { Parallel.each(1..10, :in_threads => 10) do |i| tcp.run(reqs, i) ; end }
  x.report(:ufs_10) { Parallel.each(1..10, :in_threads => 10) do |i| ufs.run(reqs, i) ; end }
  x.report(:uab_10) { Parallel.each(1..10, :in_threads => 10) do |i| uab.run(reqs, i) ; end }
}
```

あとがき
--------

そんな気はしてたよ。