~alva/zig-bare

f2533c06dbc0e7698d5fe20e613c41304bc40801 — Siva Mahadevan 4 months ago d22f2ae
remove branches in varint enc/dec, misc improvements

* Use zigzag integer enc/dec optimization idea from Daniel Lemire's
  blog[1]
* Slightly improve code style in some places where comptime assertions
  and validations can be done near the beginning of the functions to get
  over with them sooner
* Remove testing allocator from benchmarking code and replace with arena
  allocator
* Add zig build artifacts to .gitignore

[1] https://lemire.me/blog/2022/11/25/making-all-your-integers-positive-with-zigzag-encoding/
3 files changed, 32 insertions(+), 41 deletions(-)

M .gitignore
M src/bare.zig
M src/benchmark.zig
M .gitignore => .gitignore +5 -0
@@ 1,1 1,6 @@
*.kdev4
zig-cache/
zig-out/
build/
build-*/
docgen_tmp/

M src/bare.zig => src/bare.zig +20 -37
@@ 1,6 1,4 @@
const builtin = @import("builtin");
const std = @import("std");
const fs = std.fs;
const io = std.io;
const math = std.math;
const mem = std.mem;


@@ 54,12 52,7 @@ pub const Decoder = struct {

    fn decodeVarInt(self: *Self, reader: anytype) !i64 {
        const ux = try self.decodeVarUint(reader);
        var x = @intCast(i64, ux >> 1);

        if (ux & 1 != 0)
            x = -1 ^ x;

        return x;
        return @intCast(i64, ux >> 1) ^ -@intCast(i64, ux & 1);
    }

    fn decodeVarUint(self: *Self, reader: anytype) !u64 {


@@ 67,20 60,19 @@ pub const Decoder = struct {

        var x: u64 = 0;
        var s: u6 = 0;
        var i: usize = 0;
        var i: u4 = 0;

        while (true) : (i += 1) {
            const b = try reader.readByte();
            x |= @intCast(u64, b & 0x7f) << s;

            if (b < 0x80) {
                if (9 < i or i == 9 and 1 < b)
                    return DecodeError.Overflow;

                return x | @as(u64, b & 0x7f) << s;
                return x;
            }

            x |= @as(u64, b & 0x7f) << s;

            const ov = @addWithOverflow(s, 7);

            if (0 != ov[1])


@@ 119,11 111,10 @@ pub const Decoder = struct {

    fn decodeStruct(self: *Self, comptime T: type, reader: anytype) !T {
        const ti = @typeInfo(T).Struct;
        var s: T = undefined;

        if (ti.fields.len < 1)
            @compileError("structs must have 1 or more fields");

        var s: T = undefined;
        inline for (ti.fields) |f|
            @field(s, f.name) = try self.decode(f.type, reader);



@@ 145,12 136,12 @@ pub const Decoder = struct {

    fn decodeArray(self: *Self, comptime T: type, reader: anytype) !T {
        const ti = @typeInfo(T).Array;
        var buf = mem.zeroes([ti.len]ti.child);
        var i: usize = 0;

        if (ti.len < 1)
            @compileError("array length must be at least 1");

        var buf: [ti.len]ti.child = undefined;
        var i: usize = 0;

        while (i < ti.len) : (i += 1)
            buf[i] = try self.decode(ti.child, reader);



@@ 181,12 172,11 @@ pub const Decoder = struct {
        if (ti.size != .Slice)
            @compileError("slices are the only supported pointer type");

        var len = try self.decodeVarUint(reader);
        var i: usize = 0;

        const len = try self.decodeVarUint(reader);
        var buf = try self.arena.allocator().alloc(ti.child, len);

        while (i != len) : (i += 1)
        var i: usize = 0;
        while (i < len) : (i += 1)
            buf[i] = try self.decode(ti.child, reader);

        return buf;


@@ 200,19 190,16 @@ pub const Decoder = struct {
            @compileError("unsupported hashmap key type " ++ @typeName(K));

        var i = try self.decodeVarUint(reader);
        var map = T.Unmanaged{};

        if (i != 0) {
            if (std.math.maxInt(T.Size) < i)
                return DecodeError.Overflow;
        if (std.math.maxInt(T.Size) < i)
            return DecodeError.Overflow;

            try map.ensureUnusedCapacity(self.arena.allocator(), @intCast(T.Size, i));
        var map = T.Unmanaged{};
        try map.ensureUnusedCapacity(self.arena.allocator(), @intCast(T.Size, i));

            while (i != 0) : (i -= 1) {
                const key = try self.decode(K, reader);
                const val = try self.decode(V, reader);
                _ = map.putAssumeCapacity(key, val);
            }
        while (i != 0) : (i -= 1) {
            const key = try self.decode(K, reader);
            const val = try self.decode(V, reader);
            _ = map.putAssumeCapacity(key, val);
        }

        return map;


@@ 273,11 260,7 @@ pub const Encoder = struct {
    }

    fn encodeVarInt(self: *Self, value: i64, writer: anytype) !void {
        var ux = @bitCast(u64, value) << 1;

        if (value < 0)
            ux = math.maxInt(u64) ^ ux;

        const ux = @bitCast(u64, (2 * value) ^ (value >> 63));
        return self.encodeVarUint(ux, writer);
    }


M src/benchmark.zig => src/benchmark.zig +7 -4
@@ 7,12 7,15 @@ const time = std.time;
const Timer = time.Timer;

pub fn main() !void {
    const tp = try benchmarkRead(1000000000);
    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    defer arena.deinit();
    const allocator = arena.allocator();
    const tp = try benchmarkRead(allocator, 1000000000);
    const stdout = std.io.getStdOut().writer();
    try stdout.print("throughput: {}\n", .{ tp });
    try stdout.print("throughput: {}\n", .{tp});
}

fn benchmarkRead(iterations: usize) !u64 {
fn benchmarkRead(allocator: std.mem.Allocator, iterations: usize) !u64 {
    const Foo = struct {
        a: f32,
        b: u8,


@@ 22,7 25,7 @@ fn benchmarkRead(iterations: usize) !u64 {
    const start = timer.lap();
    var fbs = io.fixedBufferStream("\x00\x00\x28\x42\x2b\x00");
    var fbs_reader = fbs.reader();
    var bd = bare.Decoder.init(testing.allocator);
    var bd = bare.Decoder.init(allocator);

    var i = iterations;