77
Help us understand the problem. What are the problem?

More than 5 years have passed since last update.

posted at

Organization

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)

検証コード

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

あとがき

そんな気はしてたよ。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
77
Help us understand the problem. What are the problem?