From 9f5201b77ccb6ef0d021e741cab8468151f2535d Mon Sep 17 00:00:00 2001 From: Mike Perham Date: Sun, 15 Mar 2009 20:39:11 -0500 Subject: [PATCH] Optimization: use raw socket timeouts where possible. --- History.rdoc | 6 +++ lib/memcache.rb | 92 +++++++++++++++--------------------------- performance.txt | 81 +++++++++++++++++++++++++++++++++++++ test/test_benchmark.rb | 2 +- 4 files changed, 120 insertions(+), 61 deletions(-) create mode 100644 performance.txt diff --git a/History.rdoc b/History.rdoc index 43385dd..b4430b0 100644 --- a/History.rdoc +++ b/History.rdoc @@ -1,3 +1,9 @@ += 1.7.1 + +* Performance optimization. Rely on higher performance operating system socket timeouts for + low-level socket read/writes where possible, instead of the (slower) SystemTimer or (slowest, + unreliable) Timeout libraries. + = 1.7.0 (2009-03-08) * Go through the memcached protocol document and implement any commands not already implemented: diff --git a/lib/memcache.rb b/lib/memcache.rb index 8ce881f..45a2c64 100644 --- a/lib/memcache.rb +++ b/lib/memcache.rb @@ -33,7 +33,7 @@ class MemCache ## # The version of MemCache you are using. - VERSION = '1.7.0' + VERSION = '1.7.1' ## # Default options for the cache object. @@ -822,7 +822,7 @@ def with_socket_management(server, &block) block.call(socket) - rescue SocketError, Timeout::Error => err + rescue SocketError, Errno::EAGAIN, Timeout::Error => err logger.warn { "Socket failure: #{err.message}" } if logger server.mark_dead(err) handle_error(server, err) @@ -921,13 +921,6 @@ def check_multithread_status! class Server - ## - # The amount of time to wait to establish a connection with a memcached - # server. If a connection cannot be established within this time limit, - # the server will be marked as down. - - CONNECT_TIMEOUT = 0.25 - ## # The amount of time to wait before attempting to re-establish a # connection with a server that is marked dead. @@ -1011,14 +1004,11 @@ def socket # Attempt to connect if not already connected. begin - @sock = @timeout ? TCPTimeoutSocket.new(@host, @port, @timeout) : TCPSocket.new(@host, @port) - - if Socket.constants.include? 'TCP_NODELAY' then - @sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1 - end + @sock = connect_to(@host, @port, @timeout) + @sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1 @retry = nil @status = 'CONNECTED' - rescue SocketError, SystemCallError, IOError, Timeout::Error => err + rescue SocketError, SystemCallError, IOError => err logger.warn { "Unable to open socket: #{err.class.name}, #{err.message}" } if logger mark_dead err end @@ -1026,6 +1016,33 @@ def socket return @sock end + def connect_to(host, port, timeout=nil) + addr = Socket.getaddrinfo(host, nil) + sock = Socket.new(Socket.const_get(addr[0][0]), Socket::SOCK_STREAM, 0) + + if timeout + secs = Integer(timeout) + usecs = Integer((timeout - secs) * 1_000_000) + optval = [secs, usecs].pack("l_2") + sock.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval + sock.setsockopt Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, optval + + # Socket timeouts don't work for more complex IO operations + # like gets which lay on top of read. We need to fall back to + # the standard Timeout mechanism. + sock.instance_eval <<-EOR + alias :blocking_gets :gets + def gets + MemCacheTimer.timeout(#{timeout}) do + self.blocking_gets + end + end + EOR + end + sock.connect(Socket.pack_sockaddr_in(port, addr[0][3])) + sock + end + ## # Close the connection to the memcached server targeted by this # object. The server is not considered dead. @@ -1059,51 +1076,6 @@ class MemCacheError < RuntimeError; end end -# TCPSocket facade class which implements timeouts. -class TCPTimeoutSocket - - def initialize(host, port, timeout) - MemCacheTimer.timeout(MemCache::Server::CONNECT_TIMEOUT) do - @sock = TCPSocket.new(host, port) - @len = timeout - end - end - - def write(*args) - MemCacheTimer.timeout(@len) do - @sock.write(*args) - end - end - - def gets(*args) - MemCacheTimer.timeout(@len) do - @sock.gets(*args) - end - end - - def read(*args) - MemCacheTimer.timeout(@len) do - @sock.read(*args) - end - end - - def _socket - @sock - end - - def method_missing(meth, *args) - @sock.__send__(meth, *args) - end - - def closed? - @sock.closed? - end - - def close - @sock.close - end -end - module Continuum POINTS_PER_SERVER = 160 # this is the default in libmemcached diff --git a/performance.txt b/performance.txt new file mode 100644 index 0000000..ebbb56a --- /dev/null +++ b/performance.txt @@ -0,0 +1,81 @@ +== 1.5.0, 1.8.6 (default in Rails 2.2 and lower) + + user system total real +set:plain:memcache-client 41.550000 0.590000 42.140000 ( 43.740685) +set:ruby:memcache-client 41.540000 0.590000 42.130000 ( 43.733796) +get:plain:memcache-client 41.920000 0.610000 42.530000 ( 44.031005) +get:ruby:memcache-client 41.940000 0.600000 42.540000 ( 44.082447) +multiget:ruby:memcache-client 46.120000 0.440000 46.560000 ( 47.354041) +missing:ruby:memcache-client 41.490000 0.580000 42.070000 ( 43.610837) +mixed:ruby:memcache-client 83.820000 1.190000 85.010000 ( 88.117077) + + +== 1.7.0, timeout, 1.8.6 (closest to default in Rails 2.3) + user system total real +set:plain:memcache-client 4.320000 2.280000 6.600000 ( 7.102900) +set:ruby:memcache-client 4.400000 2.300000 6.700000 ( 6.856992) +get:plain:memcache-client 9.890000 6.830000 16.720000 ( 16.984208) +get:ruby:memcache-client 10.040000 6.890000 16.930000 ( 17.141128) +multiget:ruby:memcache-client 5.350000 4.110000 9.460000 ( 9.542898) +missing:ruby:memcache-client 4.710000 3.180000 7.890000 ( 8.030969) +mixed:ruby:memcache-client 14.540000 9.200000 23.740000 ( 24.121824) + +== 1.7.0, timeout, system_timer, 1.8.6 + user system total real +set:plain:memcache-client 3.840000 0.640000 4.480000 ( 4.643790) +set:ruby:memcache-client 3.930000 0.650000 4.580000 ( 4.731868) +get:plain:memcache-client 8.320000 1.290000 9.610000 ( 9.903877) +get:ruby:memcache-client 8.460000 1.310000 9.770000 ( 9.986694) +multiget:ruby:memcache-client 4.250000 0.560000 4.810000 ( 4.935326) +missing:ruby:memcache-client 3.840000 0.640000 4.480000 ( 4.569696) +mixed:ruby:memcache-client 12.400000 1.960000 14.360000 ( 14.857924) + +== 1.7.0, timeout, 1.9.1 + user system total real +set:plain:memcache-client 2.130000 2.150000 4.280000 ( 3.774238) +set:ruby:memcache-client 2.230000 2.230000 4.460000 ( 3.883686) +get:plain:memcache-client 4.030000 4.250000 8.280000 ( 6.702740) +get:ruby:memcache-client 4.090000 4.220000 8.310000 ( 6.749134) +multiget:ruby:memcache-client 1.960000 1.840000 3.800000 ( 3.089448) +missing:ruby:memcache-client 2.110000 2.210000 4.320000 ( 3.659019) +mixed:ruby:memcache-client 6.400000 6.560000 12.960000 ( 11.116317) + +== 1.7.0, no timeout, 1.9.1 + user system total real +set:plain:memcache-client 0.560000 0.320000 0.880000 ( 1.849380) +set:ruby:memcache-client 0.630000 0.320000 0.950000 ( 1.968208) +get:plain:memcache-client 0.640000 0.330000 0.970000 ( 1.962473) +get:ruby:memcache-client 0.690000 0.320000 1.010000 ( 2.002295) +multiget:ruby:memcache-client 0.460000 0.110000 0.570000 ( 0.885827) +missing:ruby:memcache-client 0.530000 0.320000 0.850000 ( 1.721371) +mixed:ruby:memcache-client 1.340000 0.660000 2.000000 ( 3.973213) + +== 1.7.0, no timeout, 1.8.6 + user system total real +set:plain:memcache-client 1.220000 0.310000 1.530000 ( 2.763310) +set:ruby:memcache-client 1.270000 0.300000 1.570000 ( 2.806251) +get:plain:memcache-client 1.400000 0.300000 1.700000 ( 2.944343) +get:ruby:memcache-client 1.450000 0.310000 1.760000 ( 2.997234) +multiget:ruby:memcache-client 1.120000 0.110000 1.230000 ( 1.665716) +missing:ruby:memcache-client 1.160000 0.300000 1.460000 ( 2.683376) +mixed:ruby:memcache-client 2.760000 0.610000 3.370000 ( 5.851047) + +== 1.7.1, timeout, 1.8.6, raw + gets SystemTimer + user system total real +set:plain:memcache-client 2.670000 0.510000 3.180000 ( 3.489509) +set:ruby:memcache-client 2.810000 0.530000 3.340000 ( 3.675955) +get:plain:memcache-client 4.380000 0.720000 5.100000 ( 5.400587) +get:ruby:memcache-client 4.490000 0.730000 5.220000 ( 5.477543) +multiget:ruby:memcache-client 2.570000 0.310000 2.880000 ( 3.034944) +missing:ruby:memcache-client 2.800000 0.530000 3.330000 ( 3.547073) +mixed:ruby:memcache-client 7.460000 1.250000 8.710000 ( 9.272177) + +== 1.7.1, timeout, 1.9.1, raw + gets Timeout + user system total real +set:plain:memcache-client 1.370000 1.300000 2.670000 ( 2.708669) +set:ruby:memcache-client 1.400000 1.240000 2.640000 ( 2.713737) +get:plain:memcache-client 2.070000 2.020000 4.090000 ( 3.950879) +get:ruby:memcache-client 2.160000 2.090000 4.250000 ( 3.924613) +multiget:ruby:memcache-client 1.080000 0.820000 1.900000 ( 1.744107) +missing:ruby:memcache-client 1.330000 1.270000 2.600000 ( 2.547597) +mixed:ruby:memcache-client 3.540000 3.270000 6.810000 ( 6.735349) diff --git a/test/test_benchmark.rb b/test/test_benchmark.rb index a603add..86208f3 100644 --- a/test/test_benchmark.rb +++ b/test/test_benchmark.rb @@ -1,5 +1,5 @@ HERE = File.dirname(__FILE__) -$LOAD_PATH << "#{HERE}/../lib/" +$LOAD_PATH.unshift "#{HERE}/../lib" #$LOAD_PATH << "/Library/Ruby/Gems/1.8/gems/activesupport-2.2.2/lib/active_support/vendor/memcache-client-1.5.1" require 'benchmark'