Skip to content

Commit cd28d80

Browse files
committed
perf(msgpack): optimize map operations and hot-path functions
Key optimizations: - Optimize mapPut to use getOrPut instead of getKeyPtr + put Reduces hash table lookups from 2 to 1 for each insertion - Add inline hints to frequently called functions: * Payload creation helpers (nilToPayload, boolToPayload, etc.) * Timestamp constructors (new, fromSeconds) * Wrapper functions (wrapStr, wrapBin, wrapEXT) - Keep arrPayload initialization for safety While initialization adds overhead, it prevents undefined behavior when arrays are freed before full initialization These optimizations improve performance for: - Map-heavy workloads (fewer hash lookups) - Payload construction (reduced call overhead) - Type wrapping operations (inline expansion) All tests pass (87/87)
1 parent 45b2732 commit cd28d80

File tree

1 file changed

+23
-16
lines changed

1 file changed

+23
-16
lines changed

src/msgpack.zig

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ pub const Str = struct {
127127
};
128128

129129
/// this is for encode str in struct
130-
pub fn wrapStr(str: []const u8) Str {
130+
pub inline fn wrapStr(str: []const u8) Str {
131131
return Str{ .str = str };
132132
}
133133

@@ -142,7 +142,7 @@ pub const Bin = struct {
142142
};
143143

144144
/// this is wrapping for bin
145-
pub fn wrapBin(bin: []u8) Bin {
145+
pub inline fn wrapBin(bin: []u8) Bin {
146146
return Bin{ .bin = bin };
147147
}
148148

@@ -153,7 +153,7 @@ pub const EXT = struct {
153153
};
154154

155155
/// t is type, data is data
156-
pub fn wrapEXT(t: i8, data: []u8) EXT {
156+
pub inline fn wrapEXT(t: i8, data: []u8) EXT {
157157
return EXT{
158158
.type = t,
159159
.data = data,
@@ -171,15 +171,15 @@ pub const Timestamp = struct {
171171
nanoseconds: u32,
172172

173173
/// Create a new timestamp
174-
pub fn new(seconds: i64, nanoseconds: u32) Timestamp {
174+
pub inline fn new(seconds: i64, nanoseconds: u32) Timestamp {
175175
return Timestamp{
176176
.seconds = seconds,
177177
.nanoseconds = nanoseconds,
178178
};
179179
}
180180

181181
/// Create timestamp from seconds only (nanoseconds = 0)
182-
pub fn fromSeconds(seconds: i64) Timestamp {
182+
pub inline fn fromSeconds(seconds: i64) Timestamp {
183183
return Timestamp{
184184
.seconds = seconds,
185185
.nanoseconds = 0,
@@ -255,46 +255,48 @@ pub const Payload = union(enum) {
255255
return Error.NotMap;
256256
}
257257

258-
if (self.map.getKeyPtr(key)) |existing_key| {
259-
try self.map.put(existing_key.*, val);
260-
} else {
258+
// Optimization: Use getOrPut to reduce from two hash lookups to one
259+
const entry = try self.map.getOrPut(key);
260+
if (!entry.found_existing) {
261+
// New key: allocate and copy
261262
const new_key = try self.map.allocator.alloc(u8, key.len);
262263
errdefer self.map.allocator.free(new_key);
263264
@memcpy(new_key, key);
264-
try self.map.put(new_key, val);
265+
entry.key_ptr.* = new_key;
265266
}
267+
entry.value_ptr.* = val;
266268
}
267269

268270
/// get a NIL payload
269-
pub fn nilToPayload() Payload {
271+
pub inline fn nilToPayload() Payload {
270272
return Payload{
271273
.nil = void{},
272274
};
273275
}
274276

275277
/// get a bool payload
276-
pub fn boolToPayload(val: bool) Payload {
278+
pub inline fn boolToPayload(val: bool) Payload {
277279
return Payload{
278280
.bool = val,
279281
};
280282
}
281283

282284
/// get a int payload
283-
pub fn intToPayload(val: i64) Payload {
285+
pub inline fn intToPayload(val: i64) Payload {
284286
return Payload{
285287
.int = val,
286288
};
287289
}
288290

289291
/// get a uint payload
290-
pub fn uintToPayload(val: u64) Payload {
292+
pub inline fn uintToPayload(val: u64) Payload {
291293
return Payload{
292294
.uint = val,
293295
};
294296
}
295297

296298
/// get a float payload
297-
pub fn floatToPayload(val: f64) Payload {
299+
pub inline fn floatToPayload(val: f64) Payload {
298300
return Payload{
299301
.float = val,
300302
};
@@ -325,6 +327,9 @@ pub const Payload = union(enum) {
325327
/// get an array payload
326328
pub fn arrPayload(len: usize, allocator: Allocator) !Payload {
327329
const arr = try allocator.alloc(Payload, len);
330+
// Initialize with nil to ensure safe memory state for free()
331+
// Note: While this adds overhead, it prevents undefined behavior
332+
// when arrays are partially filled or freed before full initialization
328333
for (0..len) |i| {
329334
arr[i] = Payload.nilToPayload();
330335
}
@@ -352,14 +357,14 @@ pub const Payload = union(enum) {
352357
}
353358

354359
/// get a timestamp payload
355-
pub fn timestampToPayload(seconds: i64, nanoseconds: u32) Payload {
360+
pub inline fn timestampToPayload(seconds: i64, nanoseconds: u32) Payload {
356361
return Payload{
357362
.timestamp = Timestamp.new(seconds, nanoseconds),
358363
};
359364
}
360365

361366
/// get a timestamp payload from seconds only
362-
pub fn timestampFromSeconds(seconds: i64) Payload {
367+
pub inline fn timestampFromSeconds(seconds: i64) Payload {
363368
return Payload{
364369
.timestamp = Timestamp.fromSeconds(seconds),
365370
};
@@ -1526,6 +1531,7 @@ pub fn PackWithLimits(
15261531
}
15271532

15281533
inline fn validateBinLength(len: usize) !void {
1534+
// Inline validation for hot path
15291535
if (len > parse_limits.max_bin_length) {
15301536
return MsgPackError.BinDataLengthTooLong;
15311537
}
@@ -1571,6 +1577,7 @@ pub fn PackWithLimits(
15711577
}
15721578

15731579
inline fn validateExtLength(len: usize) !void {
1580+
// Inline validation for hot path
15741581
if (len > parse_limits.max_ext_length) {
15751582
return MsgPackError.ExtDataTooLarge;
15761583
}

0 commit comments

Comments
 (0)