-
Notifications
You must be signed in to change notification settings - Fork 24
Description
Hello!
My company experienced a meaningfully negative impact to caching performance
After upgrading zstd-ruby gem from v1.5.6.4 to v1.5.6.6, my project experienced a meaningfully negative impact to caching performance (higher average time/command)
After investigation, I believe this is to do release of the Global Virtual Machine Lock (GVL) during (de)compression. In v1.5.6.4, the Global Virtual Machine Lock (GVL) is grabbed and held by the (de)compressor until it is finished. This blocks other threads from executing. In v1.5.6.6, the GVL is released during (de)compression. This allows other threads to grab the GVL.
On the surface this seems like it would be faster, but releasing the GVL comes with overhead. For the caching of many small items, the setup/teardown of the GVL release might take more time than (de)compression itself. In this event, it’s better to hold on to the GVL and only release upon (de)compression completion.
Some benchmarking to demonstrate this behavior:
# Benchmark script
require "zstd-ruby"
require "benchmark/ips"
SMALL_PAYLOAD = "a" * 1024 # 1KB
LARGE_PAYLOAD = "a" * 1024 * 1024 # 1MB
puts "Testing zstd-ruby version: #{Zstd::VERSION}"
Benchmark.ips do |x|
x.config(time: 5, warmup: 2)
x.report("Small Payload (1KB)") do
Zstd.compress(SMALL_PAYLOAD)
end
x.report("Large Payload (1MB)") do
Zstd.compress(LARGE_PAYLOAD)
end
end# Benchmark results
Testing zstd-ruby version: 1.5.6.4
ruby 3.2.9 (2025-07-24 revision 8f611e0c46) [arm64-darwin24]
Warming up --------------------------------------
Small Payload (1KB) 103.561k i/100ms
Large Payload (1MB) 1.867k i/100ms
Calculating -------------------------------------
Small Payload (1KB) 1.085M (± 1.5%) i/s (921.81 ns/i) - 5.489M in 5.060737s
Large Payload (1MB) 18.585k (± 3.1%) i/s (53.81 μs/i) - 93.350k in 5.027988s
Testing zstd-ruby version: 1.5.6.6
ruby 3.2.9 (2025-07-24 revision 8f611e0c46) [arm64-darwin24]
Warming up --------------------------------------
Small Payload (1KB) 87.453k i/100ms
Large Payload (1MB) 1.733k i/100ms
Calculating -------------------------------------
Small Payload (1KB) 871.830k (± 2.7%) i/s (1.15 μs/i) - 4.373M in 5.019124s
Large Payload (1MB) 17.277k (± 3.8%) i/s (57.88 μs/i) - 86.650k in 5.022710s
From v1.5.6.4 to v1.5.6.6, compression of small payloads is ~20% less efficient.
Is this a known tradeoff?
Do you have recommendations for a project which caches many (read: MANY) small payloads and is experiencing a negative performance impact from this gem update?
Cheers and thanks for all your work on this 😄