From bf38e836199070c2ddc286a2643b3d82d422891d Mon Sep 17 00:00:00 2001 From: Shizuo Fujita Date: Fri, 26 Dec 2025 16:01:33 +0900 Subject: [PATCH] Improve Zstd.decompress performance by avoiding unnecessary memory copy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, `rb_decompress` allocates a temporary buffer and copies the input string using `ALLOC_N` and `memcpy`. This overhead is significant, especially for large data, and doubles the memory usage during decompression. ## benchmark ```ruby require 'bundler/inline' gemfile do source 'https://rubygems.org' gem 'zstd-ruby' gem 'benchmark-ips' end require 'securerandom' large_data = SecureRandom.random_bytes(1024 * 1024 * 100) compressed_data = Zstd.compress(large_data) Benchmark.ips do |x| x.time = 15 x.report("zstd decompress") do Zstd.decompress(compressed_data) end end ``` ## before ``` $ ruby zstd.rb ruby 3.4.8 (2025-12-17 revision 995b59f666) +PRISM [x86_64-linux] Warming up -------------------------------------- zstd decompress 1.000 i/100ms Calculating ------------------------------------- zstd decompress 11.148 (± 9.0%) i/s (89.70 ms/i) - 167.000 in 15.085672s ``` ## after ``` $ ruby zstd.rb ruby 3.4.8 (2025-12-17 revision 995b59f666) +PRISM [x86_64-linux] Warming up -------------------------------------- zstd decompress 2.000 i/100ms Calculating ------------------------------------- zstd decompress 20.890 (± 9.6%) i/s (47.87 ms/i) - 312.000 in 15.015814s ``` --- ext/zstdruby/zstdruby.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/ext/zstdruby/zstdruby.c b/ext/zstdruby/zstdruby.c index ec7adc3..cde7b5e 100644 --- a/ext/zstdruby/zstdruby.c +++ b/ext/zstdruby/zstdruby.c @@ -78,9 +78,7 @@ static VALUE rb_decompress(int argc, VALUE *argv, VALUE self) StringValue(input_value); size_t in_size = RSTRING_LEN(input_value); - const unsigned char *in_r = (const unsigned char *)RSTRING_PTR(input_value); - unsigned char *in = ALLOC_N(unsigned char, in_size); - memcpy(in, in_r, in_size); + const unsigned char *in = (const unsigned char *)RSTRING_PTR(input_value); size_t off = 0; const uint32_t ZSTD_MAGIC = 0xFD2FB528U; @@ -107,14 +105,12 @@ static VALUE rb_decompress(int argc, VALUE *argv, VALUE self) if (magic == ZSTD_MAGIC) { ZSTD_DCtx *dctx = ZSTD_createDCtx(); if (!dctx) { - xfree(in); rb_raise(rb_eRuntimeError, "ZSTD_createDCtx failed"); } VALUE out = decode_one_frame(dctx, in + off, in_size - off, kwargs); ZSTD_freeDCtx(dctx); - xfree(in); RB_GC_GUARD(input_value); return out; } @@ -122,7 +118,6 @@ static VALUE rb_decompress(int argc, VALUE *argv, VALUE self) off += 1; } - xfree(in); RB_GC_GUARD(input_value); rb_raise(rb_eRuntimeError, "not a zstd frame (magic not found)"); }