From c70295a0f177555240b95f5564ce773d15b2a4b1 Mon Sep 17 00:00:00 2001 From: Hartley McGuire Date: Sat, 27 Jun 2026 13:24:20 -0400 Subject: [PATCH 1/2] Rename object_complete_symbol -> frozen To use with other, non-symbol, pre-frozen objects. --- ext/msgpack/unpacker.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ext/msgpack/unpacker.c b/ext/msgpack/unpacker.c index 5024cd50..f08ceae1 100644 --- a/ext/msgpack/unpacker.c +++ b/ext/msgpack/unpacker.c @@ -211,7 +211,7 @@ static inline int object_complete(msgpack_unpacker_t* uk, VALUE object) return PRIMITIVE_OBJECT_COMPLETE; } -static inline int object_complete_symbol(msgpack_unpacker_t* uk, VALUE object) +static inline int object_complete_frozen(msgpack_unpacker_t* uk, VALUE object) { uk->last_object = object; reset_head_byte(uk); @@ -222,9 +222,9 @@ static inline int object_complete_ext(msgpack_unpacker_t* uk, int ext_type, VALU { if (uk->optimized_symbol_ext_type && ext_type == uk->symbol_ext_type) { if (RB_UNLIKELY(NIL_P(str))) { // empty extension is returned as Qnil - return object_complete_symbol(uk, ID2SYM(rb_intern3("", 0, rb_utf8_encoding()))); + return object_complete_frozen(uk, ID2SYM(rb_intern3("", 0, rb_utf8_encoding()))); } - return object_complete_symbol(uk, rb_str_intern(str)); + return object_complete_frozen(uk, rb_str_intern(str)); } int ext_flags; @@ -398,7 +398,7 @@ static inline int read_raw_body_begin(msgpack_unpacker_t* uk, int raw_type) int ret; if ((uk->optimized_symbol_ext_type && uk->symbol_ext_type == raw_type)) { VALUE symbol = msgpack_buffer_read_top_as_symbol(UNPACKER_BUFFER_(uk), length, raw_type != RAW_TYPE_BINARY); - ret = object_complete_symbol(uk, symbol); + ret = object_complete_frozen(uk, symbol); } else if (is_reading_map_key(uk) && raw_type == RAW_TYPE_STRING) { /* don't use zerocopy for hash keys but get a frozen string directly * because rb_hash_aset freezes keys and it causes copying */ @@ -409,7 +409,7 @@ static inline int read_raw_body_begin(msgpack_unpacker_t* uk, int raw_type) } else { key = msgpack_buffer_read_top_as_symbol(UNPACKER_BUFFER_(uk), length, true); } - ret = object_complete_symbol(uk, key); + ret = object_complete_frozen(uk, key); } else { if (uk->use_key_cache) { key = msgpack_buffer_read_top_as_interned_string(UNPACKER_BUFFER_(uk), &uk->key_cache, length); From a35d35014dd874633476382af7fbe93813fbbcc2 Mon Sep 17 00:00:00 2001 From: Hartley McGuire Date: Sat, 27 Jun 2026 13:53:59 -0400 Subject: [PATCH 2/2] Speed up immediate unpack when freeze: true by 10% MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since immediates are already frozen, we can skip over "maybe freeze" behavior completely for those types. ``` ruby 4.0.2 (2026-03-17 revision d3da9fec82) +YJIT +PRISM [x86_64-linux] Warming up -------------------------------------- control 5.437k i/100ms frozen before 4.733k i/100ms Calculating ------------------------------------- control 54.830k (± 1.8%) i/s (18.24 μs/i) - 277.287k in 5.057252s frozen before 49.078k (± 1.9%) i/s (20.38 μs/i) - 246.116k in 5.014762s Comparison: frozen after: 55009.0 i/s control: 54829.6 i/s - same-ish: difference falls within error frozen before: 49078.3 i/s - 1.12x slower ``` ```ruby require 'msgpack' require 'benchmark/ips' data = [] 100.times do data << rand(0..127) data << -rand(0..30) data << nil data << true data << false data << rand(0..65535) end packed = MessagePack.pack(data) up_ctl = MessagePack::Unpacker.new(freeze: false) up_frz = MessagePack::Unpacker.new(freeze: true) Benchmark.ips do |x| x.report("control") { up_ctl.feed(packed); up_ctl.read } x.report("frozen #{ENV.fetch("STAGE", "after")}") { up_frz.feed(packed); up_frz.read } x.save!("/tmp/msgpack_un_freeze") x.compare! end ``` --- ext/msgpack/unpacker.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/ext/msgpack/unpacker.c b/ext/msgpack/unpacker.c index f08ceae1..b2651cb0 100644 --- a/ext/msgpack/unpacker.c +++ b/ext/msgpack/unpacker.c @@ -450,10 +450,10 @@ static int read_primitive(msgpack_unpacker_t* uk) SWITCH_RANGE_BEGIN(b) SWITCH_RANGE(b, 0x00, 0x7f) // Positive Fixnum - return object_complete(uk, INT2NUM(b)); + return object_complete_frozen(uk, INT2NUM(b)); SWITCH_RANGE(b, 0xe0, 0xff) // Negative Fixnum - return object_complete(uk, INT2NUM((int8_t)b)); + return object_complete_frozen(uk, INT2NUM((int8_t)b)); SWITCH_RANGE(b, 0xa0, 0xbf) // FixRaw / fixstr size_t count = b & 0x1f; @@ -478,15 +478,15 @@ static int read_primitive(msgpack_unpacker_t* uk) SWITCH_RANGE(b, 0xc0, 0xdf) // Variable switch(b) { case 0xc0: // nil - return object_complete(uk, Qnil); + return object_complete_frozen(uk, Qnil); //case 0xc1: // string case 0xc2: // false - return object_complete(uk, Qfalse); + return object_complete_frozen(uk, Qfalse); case 0xc3: // true - return object_complete(uk, Qtrue); + return object_complete_frozen(uk, Qtrue); case 0xc7: // ext 8 { @@ -542,14 +542,14 @@ static int read_primitive(msgpack_unpacker_t* uk) { READ_CAST_BLOCK_OR_RETURN_EOF(cb, uk, 1); uint8_t u8 = cb.u8; - return object_complete(uk, INT2NUM((int)u8)); + return object_complete_frozen(uk, INT2NUM((int)u8)); } case 0xcd: // unsigned int 16 { READ_CAST_BLOCK_OR_RETURN_EOF(cb, uk, 2); uint16_t u16 = _msgpack_be16(cb.u16); - return object_complete(uk, INT2NUM((int)u16)); + return object_complete_frozen(uk, INT2NUM((int)u16)); } case 0xce: // unsigned int 32 @@ -570,14 +570,14 @@ static int read_primitive(msgpack_unpacker_t* uk) { READ_CAST_BLOCK_OR_RETURN_EOF(cb, uk, 1); int8_t i8 = cb.i8; - return object_complete(uk, INT2NUM((int)i8)); + return object_complete_frozen(uk, INT2NUM((int)i8)); } case 0xd1: // signed int 16 { READ_CAST_BLOCK_OR_RETURN_EOF(cb, uk, 2); int16_t i16 = _msgpack_be16(cb.i16); - return object_complete(uk, INT2NUM((int)i16)); + return object_complete_frozen(uk, INT2NUM((int)i16)); } case 0xd2: // signed int 32