-
Notifications
You must be signed in to change notification settings - Fork 1k
Expand file tree
/
Copy pathresp_comparison.rb
More file actions
105 lines (91 loc) · 3.43 KB
/
Copy pathresp_comparison.rb
File metadata and controls
105 lines (91 loc) · 3.43 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# frozen_string_literal: true
# RESP2 vs RESP3 for redis-rb across reply-reshape complexity:
# 1. GET (string — no reshape, baseline)
# 2. HGETALL (Hashify: flat array -> Hash under RESP2, native map under RESP3)
# 3. ZRANGE WITHSCORES (FloatifyPairs: pairs -> [member, Float] under RESP2, native doubles under RESP3)
# 4. XRANGE (HashifyStreamEntries — deeply nested reshape)
# Each cell runs single-threaded and multi-threaded (one client per thread).
#
# DURATION=6 THREADS=8 bundle exec ruby bench/resp_comparison.rb
require "redis"
PORT = Integer(ENV.fetch("REDIS_PORT", "6381"))
DURATION = Float(ENV.fetch("DURATION", "6"))
THREADS = Integer(ENV.fetch("THREADS", "8"))
ELEMS = Integer(ENV.fetch("ELEMENTS", "100"))
DRIVER = ENV.fetch("DRIVER", "ruby").to_sym
require "hiredis-client" if DRIVER == :hiredis
SKEY = "bench:str"
HKEY = "bench:hash"
ZKEY = "bench:zset"
XKEY = "bench:stream"
def new_client(protocol)
Redis.new(host: "127.0.0.1", port: PORT, protocol: protocol, driver: DRIVER)
end
def monotonic
Process.clock_gettime(Process::CLOCK_MONOTONIC)
end
def seed
r = new_client(2)
r.set(SKEY, "x" * 50)
r.del(HKEY, ZKEY, XKEY)
fields = ELEMS.times.to_h { |i| ["field#{i}", "value-#{i}-#{'y' * 20}"] }
r.hset(HKEY, fields)
r.zadd(ZKEY, ELEMS.times.map { |i| [i * 1.5, "member-#{i}"] })
ELEMS.times { |i| r.xadd(XKEY, { "sensor" => "s#{i}", "temp" => (20 + i).to_s }) }
r.close
end
# Sanity: both protocols must produce identical Ruby shapes.
def verify_shapes!
c2, c3 = new_client(2), new_client(3)
raise "hgetall mismatch" unless c2.hgetall(HKEY) == c3.hgetall(HKEY)
raise "zrange mismatch" unless c2.zrange(ZKEY, 0, -1, with_scores: true) == c3.zrange(ZKEY, 0, -1, with_scores: true)
raise "xrange mismatch" unless c2.xrange(XKEY) == c3.xrange(XKEY)
[c2, c3].each(&:close)
end
WORKLOADS = {
"GET (50B string)" => ->(r) { r.get(SKEY) },
"HGETALL (#{ELEMS} fields)" => ->(r) { r.hgetall(HKEY) },
"ZRANGE WITHSCORES (#{ELEMS})" => ->(r) { r.zrange(ZKEY, 0, -1, with_scores: true) },
"XRANGE (#{ELEMS} entries)" => ->(r) { r.xrange(XKEY) }
}.freeze
def cpu_now
t = Process.times
t.utime + t.stime
end
def run_cell(protocol, threads, op)
clients = Array.new(threads) { new_client(protocol) }
clients.each { |r| 200.times { op.call(r) } } # warmup: connect + JIT
GC.start
stop_at = monotonic + DURATION
counts = Array.new(threads, 0)
cpu0, t0 = cpu_now, monotonic
threads.times.map do |i|
Thread.new do
r = clients[i]
n = 0
n += 1 while op.call(r) && monotonic < stop_at
counts[i] = n
end
end.each(&:join)
wall, cpu = monotonic - t0, cpu_now - cpu0
clients.each(&:close)
total = counts.sum
{ rps: total / wall, us_cpu: (cpu / total) * 1_000_000.0 }
end
seed
verify_shapes!
puts "redis-rb #{Redis::VERSION} ruby #{RUBY_VERSION} driver=#{DRIVER} duration=#{DURATION}s/cell elements=#{ELEMS}"
puts
[1, THREADS].each do |threads|
puts "== #{threads} thread#{'s' if threads > 1} =="
puts format(" %-28s %12s %12s %8s %14s %14s", "workload", "RESP2 RPS", "RESP3 RPS", "Δ RPS", "RESP2 usCPU/op",
"RESP3 usCPU/op")
WORKLOADS.each do |label, op|
r2 = run_cell(2, threads, op)
r3 = run_cell(3, threads, op)
delta = (r3[:rps] / r2[:rps] - 1) * 100
puts format(" %-28s %12.0f %12.0f %+7.1f%% %14.1f %14.1f",
label, r2[:rps], r3[:rps], delta, r2[:us_cpu], r3[:us_cpu])
end
puts
end