|
require 'bundler/inline' |
|
|
|
require 'socket' |
|
class MyRedis |
|
CRLF = "\r\n" |
|
|
|
def initialize |
|
@connection = TCPSocket.new('localhost', 6379) |
|
@connection.sync = false |
|
end |
|
|
|
def get(key) |
|
@connection << "*2\r\n$3\r\nget\r\n$#{key.bytesize}\r\n#{key}\r\n" |
|
@connection.flush |
|
result = @connection.gets |
|
if result.start_with?('$') |
|
value = '' |
|
bytesize = result.tap { |r| r[0] = '' }.to_i |
|
return nil if bytesize < 0 # Key does not exist |
|
@connection.read bytesize + 2, value |
|
value.chomp! CRLF |
|
value |
|
else |
|
raise "idk what to do with this: #{result.inspect}" |
|
end |
|
end |
|
end |
|
|
|
gemfile true do |
|
source 'https://rubygems.org' |
|
gem 'redis' |
|
gem 'hiredis' |
|
gem 'benchmark-ips' |
|
end |
|
|
|
require 'benchmark' |
|
require 'benchmark/ips' |
|
require 'redis' |
|
require 'hiredis' |
|
|
|
redis = Redis.new(driver: :ruby) |
|
hiredis = Redis.new(driver: :hiredis) |
|
myredis = MyRedis.new |
|
key = "foo" |
|
cpu_time_iterations = 100_000 |
|
|
|
# Quick functionality check on our custom Redis |
|
redis.set key, "test" |
|
if (myredis_value = myredis.get(key)) == (redis_value = redis.get(key)) |
|
puts "Cache-hit function check passed" |
|
else |
|
raise "Value mismatch. Redis: #{redis_value.inspect}, MyRedis: #{myredis_value.inspect}" |
|
end |
|
redis.del key |
|
if (myredis_value = myredis.get(key)).nil? |
|
puts "Cache-miss function check passed" |
|
else |
|
raise "MyRedis cache miss broken: #{myredis_value.inspect}" |
|
end |
|
|
|
|
|
puts |
|
puts "Cache hit (returns a 2KB string)" |
|
redis.set key, "." * 2048 # 2KB cache entry |
|
Benchmark.ips do |x| |
|
x.report("ruby") { redis.get key } |
|
x.report("hiredis") { hiredis.get key } |
|
x.report("myredis") { myredis.get key } |
|
|
|
x.compare! |
|
end |
|
|
|
puts |
|
puts "Cache hit CPU time" |
|
puts "ruby : %.06f" % [ruby_cpu = Benchmark.measure { cpu_time_iterations.times { redis.get key } }.total] |
|
puts "hiredis: %.06f" % [hiredis_cpu = Benchmark.measure { cpu_time_iterations.times { hiredis.get key } }.total] |
|
puts "myredis: %.06f" % [myredis_cpu = Benchmark.measure { cpu_time_iterations.times { myredis.get key } }.total] |
|
fastest = [ruby_cpu, hiredis_cpu, myredis_cpu].min |
|
puts "\e[3Aruby : %.06f (%.02fx)" % [ruby_cpu, ruby_cpu / fastest] |
|
puts "hiredis: %.06f (%.02fx)" % [hiredis_cpu, hiredis_cpu / fastest] |
|
puts "myredis: %.06f (%.02fx)" % [myredis_cpu, myredis_cpu / fastest] |
|
|
|
puts |
|
puts "Cache miss (returns nil)" |
|
redis.del key |
|
Benchmark.ips do |x| |
|
x.report("ruby") { redis.get key } |
|
x.report("hiredis") { hiredis.get key } |
|
x.report("myredis") { myredis.get key } |
|
|
|
x.compare! |
|
end |
|
|
|
puts |
|
puts "Cache miss CPU time" |
|
puts "ruby : %.06f" % [ruby_cpu = Benchmark.measure { cpu_time_iterations.times { redis.get key } }.total] |
|
puts "hiredis: %.06f" % [hiredis_cpu = Benchmark.measure { cpu_time_iterations.times { hiredis.get key } }.total] |
|
puts "myredis: %.06f" % [myredis_cpu = Benchmark.measure { cpu_time_iterations.times { myredis.get key } }.total] |
|
fastest = [ruby_cpu, hiredis_cpu, myredis_cpu].min |
|
puts "\e[3Aruby : %.06f (%.02fx)" % [ruby_cpu, ruby_cpu / fastest] |
|
puts "hiredis: %.06f (%.02fx)" % [hiredis_cpu, hiredis_cpu / fastest] |
|
puts "myredis: %.06f (%.02fx)" % [myredis_cpu, myredis_cpu / fastest] |