authorgravatar for nico.b.elbers@gmail.comNico Elbers <nico.b.elbers@gmail.com> 2025-11-03 12:05:57+01:00
committergravatar for nico.b.elbers@gmail.comNico Elbers <nico.b.elbers@gmail.com> 2025-11-27 15:46:16+01:00
logceba9572ef5fc3a4c7e2e3f315d05842d2422d0c
tree58f2fc73dbe862b1457f1acd598535423f5ab046
parent854774d468b0a439def7eef73c544ac8d64f1b62
signaturelock-open Commit is signed but in an unrecognized format.

Optimize Writer.writeLeb128

Rewrite `writeLeb128` to no longer use `writeMultipleOf7Leb128` and instead: * Make use of byte aligned ints * Special case small numbers (fitting inside 7 bits) Amongst u8, u16, u32 and u64 performance gains are between ~1.5x and ~2x Amongst i8, i16, i32 ane i64 perfromance gains are between ~2x and >4x Additinally add test coverage for written encodings Microbenchmark: https://zigbin.io/7ed5fe

1 files changed, 150 insertions(+), 23 deletions(-)

lib/std/Io/Writer.zig+150-23
...@@ -1911,35 +1911,162 @@ pub fn writeSleb128(w: *Writer, value: anytype) Error!void {...@@ -1911,35 +1911,162 @@ pub fn writeSleb128(w: *Writer, value: anytype) Error!void {
19111911
1912/// Write a single integer as LEB128 to the given writer.1912/// Write a single integer as LEB128 to the given writer.
1913pub fn writeLeb128(w: *Writer, value: anytype) Error!void {1913pub fn writeLeb128(w: *Writer, value: anytype) Error!void {
1914 const value_info = @typeInfo(@TypeOf(value)).int;1914 const T = @TypeOf(value);
1915 try w.writeMultipleOf7Leb128(@as(@Int(1915 const info = switch (@typeInfo(T)) {
1916 value_info.signedness,1916 .int => |info| info,
1917 @max(std.mem.alignForwardAnyAlign(u16, value_info.bits, 7), 7),1917 else => @compileError(@typeName(T) ++ " not supported"),
1918 ), value));1918 };
1919}
19201919
1921fn writeMultipleOf7Leb128(w: *Writer, value: anytype) Error!void {1920 const BoundInt = @Int(info.signedness, 7);
1922 const value_info = @typeInfo(@TypeOf(value)).int;1921 if (info.bits <= 7 or (value >= std.math.minInt(BoundInt) and value <= std.math.maxInt(BoundInt))) {
1923 const Byte = packed struct(u8) { bits: u7, more: bool };1922 const Bits = @Int(info.signedness, 8);
1924 var bytes: [@divExact(value_info.bits, 7)]Byte = undefined;1923 const byte = switch (info.signedness) {
1925 var remaining = value;1924 .signed => @as(Bits, @intCast(value)) & 0x7F,
1926 for (&bytes, 1..) |*byte, len| {1925 .unsigned => @as(Bits, @intCast(value)),
1927 const more = switch (value_info.signedness) {
1928 .signed => remaining >> 6 != remaining >> (value_info.bits - 1),
1929 .unsigned => remaining > std.math.maxInt(u7),
1930 };1926 };
1931 byte.* = .{1927 try w.writeByte(@bitCast(byte));
1932 .bits = @bitCast(@as(1928 return;
1933 @Int(value_info.signedness, 7),1929 }
1934 @truncate(remaining),1930
1935 )),1931 const Byte = packed struct { bits: u7, more: bool };
1936 .more = more,1932 const Int = std.math.ByteAlignedInt(T);
1933
1934 const max_bytes = @divFloor(info.bits - 1, 7) + 1;
1935
1936 const sign_value = value >> (info.bits - 1);
1937 var val: Int = value;
1938 for (0..max_bytes) |_| {
1939 const more = switch (info.signedness) {
1940 .signed => val >> 6 != sign_value,
1941 .unsigned => val > std.math.maxInt(u7),
1937 };1942 };
1938 if (value_info.bits > 7) remaining >>= 7;1943
1939 if (!more) return w.writeAll(@ptrCast(bytes[0..len]));1944 try w.writeByte(@bitCast(@as(Byte, .{
1945 .bits = @intCast(val & 0x7F),
1946 .more = more,
1947 })));
1948
1949 if (!more) return;
1950
1951 val >>= 7;
1940 } else unreachable;1952 } else unreachable;
1941}1953}
19421954
1955test "serialize signed LEB128" {
1956 // Small values
1957 try testLeb128Encoding(i7, 9, "\x09");
1958 try testLeb128Encoding(i64, 125, "\xFD\x00");
1959
1960 try testLeb128Encoding(i7, -34, "\x5E");
1961 try testLeb128Encoding(i64, -3, "\x7D");
1962
1963 // Random values
1964 try testLeb128Encoding(i16, 19373, "\xAD\x97\x01");
1965 try testLeb128Encoding(i32, 1628839242, "\xCA\xBA\xD8\x88\x06");
1966 try testLeb128Encoding(i64, 3789169920125966546, "\xD2\xB1\xD0\xD5\xF6\xBE\xF5\xCA\x34");
1967 try testLeb128Encoding(i128, 704622239050934257305893323522763588, "\xC4\xD6\x83\xC7\xE3\x91\x95\xC3\x96\x80\x8D\xA5\xF5\xDF\xA3\xDA\x87\x01");
1968
1969 try testLeb128Encoding(i16, -14558, "\xA2\x8E\x7F");
1970 try testLeb128Encoding(i32, -1702738165, "\x8B\x8E\x89\xD4\x79");
1971 try testLeb128Encoding(i64, -1709126996960612298, "\xB6\xE0\x87\xB1\xD3\xC1\xFD\xA3\x68");
1972 try testLeb128Encoding(i128, -113498719181566012704681230050325944039, "\x99\xD2\x80\xBC\xE6\x95\xBC\xC8\xDE\xB4\x9D\x81\x9F\xCA\xC6\xF8\x9C\xD5\x7E");
1973
1974 // {min,max} values
1975 try testLeb128Encoding(i16, std.math.maxInt(i16), "\xFF\xFF\x01");
1976 try testLeb128Encoding(i32, std.math.maxInt(i32), "\xFF\xFF\xFF\xFF\x07");
1977 try testLeb128Encoding(i64, std.math.maxInt(i64), "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00");
1978 try testLeb128Encoding(i128, std.math.maxInt(i128), "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x01");
1979
1980 try testLeb128Encoding(i16, std.math.minInt(i16), "\x80\x80\x7E");
1981 try testLeb128Encoding(i32, std.math.minInt(i32), "\x80\x80\x80\x80\x78");
1982 try testLeb128Encoding(i64, std.math.minInt(i64), "\x80\x80\x80\x80\x80\x80\x80\x80\x80\x7F");
1983 try testLeb128Encoding(i128, std.math.minInt(i128), "\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x7E");
1984
1985 // Specific cases
1986 try testLeb128Encoding(i0, 0, "\x00");
1987 try testLeb128Encoding(i8, 0, "\x00");
1988
1989 try testLeb128Encoding(i2, -1, "\x7F");
1990 try testLeb128Encoding(i8, -1, "\x7F");
1991
1992 try testLeb128Encoding(i2, 1, "\x01");
1993 try testLeb128Encoding(i8, 1, "\x01");
1994
1995 // Encode byte boundaries
1996 try testLeb128Encoding(i7, std.math.maxInt(i7), "\x3F");
1997 try testLeb128Encoding(i8, std.math.maxInt(i7) + 1, "\xC0\x00");
1998 try testLeb128Encoding(i14, std.math.maxInt(i14), "\xFF\x3F");
1999 try testLeb128Encoding(i15, std.math.maxInt(i14) + 1, "\x80\xC0\x00");
2000 try testLeb128Encoding(i49, std.math.maxInt(i49), "\xFF\xFF\xFF\xFF\xFF\xFF\x3F");
2001 try testLeb128Encoding(i50, std.math.maxInt(i49) + 1, "\x80\x80\x80\x80\x80\x80\xC0\x00");
2002 try testLeb128Encoding(i56, std.math.maxInt(i56), "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x3F");
2003 try testLeb128Encoding(i57, std.math.maxInt(i56) + 1, "\x80\x80\x80\x80\x80\x80\x80\xC0\x00");
2004 try testLeb128Encoding(i63, std.math.maxInt(i63), "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x3F");
2005 try testLeb128Encoding(i64, std.math.maxInt(i63) + 1, "\x80\x80\x80\x80\x80\x80\x80\x80\xC0\x00");
2006
2007 try testLeb128Encoding(i7, std.math.minInt(i7), "\x40");
2008 try testLeb128Encoding(i8, std.math.minInt(i7) - 1, "\xBF\x7F");
2009 try testLeb128Encoding(i14, std.math.minInt(i14), "\x80\x40");
2010 try testLeb128Encoding(i15, std.math.minInt(i14) - 1, "\xFF\xBF\x7F");
2011 try testLeb128Encoding(i49, std.math.minInt(i49), "\x80\x80\x80\x80\x80\x80\x40");
2012 try testLeb128Encoding(i50, std.math.minInt(i49) - 1, "\xFF\xFF\xFF\xFF\xFF\xFF\xBF\x7F");
2013 try testLeb128Encoding(i56, std.math.minInt(i56), "\x80\x80\x80\x80\x80\x80\x80\x40");
2014 try testLeb128Encoding(i57, std.math.minInt(i56) - 1, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xBF\x7F");
2015 try testLeb128Encoding(i63, std.math.minInt(i63), "\x80\x80\x80\x80\x80\x80\x80\x80\x40");
2016 try testLeb128Encoding(i64, std.math.minInt(i63) - 1, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xBF\x7F");
2017}
2018
2019test "serialize unsigned LEB128" {
2020 // Small values
2021 try testLeb128Encoding(u7, 12, "\x0C");
2022 try testLeb128Encoding(u64, 201, "\xC9\x01");
2023
2024 // Random values
2025 try testLeb128Encoding(u8, 254, "\xFE\x01");
2026 try testLeb128Encoding(u16, 30241, "\xA1\xEC\x01");
2027 try testLeb128Encoding(u32, 2173531193, "\xB9\xE8\xB5\x8C\x08");
2028 try testLeb128Encoding(u64, 18321125691115744902, "\x86\xDD\xF2\x81\xF2\xD7\xED\xA0\xFE\x01");
2029 try testLeb128Encoding(u128, 122619209508942982841456325819614676193, "\xE1\x89\xF3\xD9\xE3\xAD\xEC\xF4\x98\x95\xF8\xBB\xD7\xB8\xF2\xCC\xBF\xB8\x01");
2030
2031 // Max values
2032 try testLeb128Encoding(u8, std.math.maxInt(u8), "\xFF\x01");
2033 try testLeb128Encoding(u16, std.math.maxInt(u16), "\xFF\xFF\x03");
2034 try testLeb128Encoding(u32, std.math.maxInt(u32), "\xFF\xFF\xFF\xFF\x0F");
2035 try testLeb128Encoding(u64, std.math.maxInt(u64), "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x01");
2036 try testLeb128Encoding(u128, std.math.maxInt(u128), "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x03");
2037
2038 // Specific cases
2039 try testLeb128Encoding(u0, 0, "\x00");
2040 try testLeb128Encoding(u1, 0, "\x00");
2041 try testLeb128Encoding(u8, 0, "\x00");
2042
2043 try testLeb128Encoding(u1, 1, "\x01");
2044 try testLeb128Encoding(u8, 1, "\x01");
2045
2046 // Encode byte boundaries
2047 try testLeb128Encoding(u7, std.math.maxInt(u7), "\x7F");
2048 try testLeb128Encoding(u8, std.math.maxInt(u7) + 1, "\x80\x01");
2049 try testLeb128Encoding(u14, std.math.maxInt(u14), "\xFF\x7F");
2050 try testLeb128Encoding(u15, std.math.maxInt(u14) + 1, "\x80\x80\x01");
2051 try testLeb128Encoding(u49, std.math.maxInt(u49), "\xFF\xFF\xFF\xFF\xFF\xFF\x7F");
2052 try testLeb128Encoding(u50, std.math.maxInt(u49) + 1, "\x80\x80\x80\x80\x80\x80\x80\x01");
2053 try testLeb128Encoding(u56, std.math.maxInt(u56), "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x7F");
2054 try testLeb128Encoding(u57, std.math.maxInt(u56) + 1, "\x80\x80\x80\x80\x80\x80\x80\x80\x01");
2055 try testLeb128Encoding(u63, std.math.maxInt(u63), "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x7F");
2056 try testLeb128Encoding(u64, std.math.maxInt(u63) + 1, "\x80\x80\x80\x80\x80\x80\x80\x80\x80\x01");
2057}
2058
2059fn testLeb128Encoding(comptime T: type, value: T, encoding: []const u8) !void {
2060 const info = @typeInfo(T).int;
2061 const max_bytes = @divFloor(info.bits -| 1, 7) + 1;
2062 var bytes: [max_bytes]u8 = undefined;
2063
2064 var fw: Writer = .fixed(&bytes);
2065 try writeLeb128(&fw, value);
2066
2067 try std.testing.expectEqualSlices(u8, encoding, fw.buffered());
2068}
2069
1943test "printValue max_depth" {2070test "printValue max_depth" {
1944 const Vec2 = struct {2071 const Vec2 = struct {
1945 const SelfType = @This();2072 const SelfType = @This();