Skip to content

Performance degradation for many small payloads after updating to v1.5.6.6 #122

@mattwd7

Description

@mattwd7

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 😄

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions