atproto utils for zig zat.dev
atproto sdk zig
26
fork

Configure Feed

Select the types of activity you want to include in your feed.

optimize encoder: fused text writes, stack sort for small maps

Fuse header+payload into a single writeAll for text strings < 24 bytes
(all AT Protocol map keys), halving writer dispatch count per key. Use a
stack buffer for sorting maps with ≤16 entries (all AT Protocol records),
eliminating allocator calls on the sort path. Encode+CID path improves
~9%, full record encode ~7%.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

jcalabro 129ec288 968d03a4

+31 -8
+31 -8
src/internal/repo/cbor.zig
··· 494 494 return std.mem.order(u8, a.key, b.key) == .lt; 495 495 } 496 496 497 + /// write a short text string (< 24 bytes) as a single fused write. 498 + /// this is the hot path for map keys in AT Protocol records, where keys 499 + /// are always short ASCII strings. fusing header+payload into one writeAll 500 + /// halves the writer dispatch count. 501 + fn writeShortText(writer: anytype, text: []const u8) !void { 502 + if (text.len < 24) { 503 + var buf: [24]u8 = undefined; 504 + buf[0] = 0x60 | @as(u8, @intCast(text.len)); 505 + @memcpy(buf[1..][0..text.len], text); 506 + try writer.writeAll(buf[0 .. 1 + text.len]); 507 + } else { 508 + try writeArgument(writer, 3, text.len); 509 + try writer.writeAll(text); 510 + } 511 + } 512 + 497 513 /// encode a Value to the given writer in DAG-CBOR format. 498 514 /// allocator is needed for sorting map keys during encoding. 499 515 pub fn encode(allocator: Allocator, writer: anytype, value: Value) !void { ··· 508 524 try writeArgument(writer, 2, b.len); 509 525 try writer.writeAll(b); 510 526 }, 511 - .text => |t| { 512 - try writeArgument(writer, 3, t.len); 513 - try writer.writeAll(t); 514 - }, 527 + .text => |t| try writeShortText(writer, t), 515 528 .array => |items| { 516 529 try writeArgument(writer, 4, items.len); 517 530 for (items) |item| { ··· 521 534 .map => |entries| { 522 535 try writeArgument(writer, 5, entries.len); 523 536 // DAG-CBOR: keys sorted by byte length, then lexicographically. 524 - // fast path: skip allocation + sort when keys are already in order 525 - // (common for decoded data and hand-constructed records). 537 + // three paths: already sorted (common for decoded data), stack sort 538 + // for small maps (≤16 entries, covers all AT Protocol records), or 539 + // heap sort for rare large maps. 526 540 if (keysAlreadySorted(entries)) { 527 541 for (entries) |entry| { 528 - try encode(allocator, writer, .{ .text = entry.key }); 542 + try writeShortText(writer, entry.key); 543 + try encode(allocator, writer, entry.value); 544 + } 545 + } else if (entries.len <= 16) { 546 + var buf: [16]Value.MapEntry = undefined; 547 + const sorted = buf[0..entries.len]; 548 + @memcpy(sorted, entries); 549 + std.mem.sort(Value.MapEntry, sorted, {}, dagCborKeyLessThan); 550 + for (sorted) |entry| { 551 + try writeShortText(writer, entry.key); 529 552 try encode(allocator, writer, entry.value); 530 553 } 531 554 } else { ··· 533 556 defer allocator.free(sorted); 534 557 std.mem.sort(Value.MapEntry, sorted, {}, dagCborKeyLessThan); 535 558 for (sorted) |entry| { 536 - try encode(allocator, writer, .{ .text = entry.key }); 559 + try writeShortText(writer, entry.key); 537 560 try encode(allocator, writer, entry.value); 538 561 } 539 562 }