きっかけ
双方向のプロセス間通信をする必要が出てきたのですが、TCPのlocalhost接続とUNIX Domain Socketと、どっちの方がパフォーマンスがいいのか、実測してみました
検証内容
接続するとランダムな文字列を返すだけのサーバを作成
以下の接続方法について、10,000リクエストを同時接続 1/5/10 と条件を変えて計測
- TCP localhost
- Unix Domain Socket / Filesystem Namespace
- 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)
検証コード
Gemfile
source "https://rubygems.org"
gem "celluloid-io", :require => "celluloid/io"
gem "parallel"
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
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 }
}
あとがき
そんな気はしてたよ。