#comptime Equivalent to the `__COUNTER__ `macro?

1 messages · Page 1 of 1 (latest)

rancid needle
#

If you'd rather read code than my explanation, here's the C++ code I'm trying to emulate.

I've written a simple profiler that times blocks of code. It looks very roughly like this:

pub const Profiler = struct {
  anchors: [num_anchors]Anchor,
  pub fn startBlock(self *Profiler, label: []const u8, index: usize) void {
    ...
  }

  pub fn endBlock(self *Profiler) void {
    ...
  }
}

When I have some Zig code that I want to time, I import it into the file and create a global Profiler. Here's how it's used when I want to time a function:

fn foo() void {
  profiler.startBlock("foo", 1);
  defer profiler.endBlock();
}

The index parameter of startBlock is used to index the anchors field of the profiler. So every time foo is called, I want to update the Anchor at index 1 of anchors with the additional time that foo took. For a different function the timing data might be stored at index 2, and so on. The thing is, I don't want to manually have to put in different indices for every instance of profiler.startBlock cuz it's both tedious and error-prone.
The profiler I'm basing this on uses the __COUNTER__ macro to automatically create a new index at compile time for every block of code to be timed.
It seems like something similar should be possible with comptime, but I haven't been able to create a comptime variable that can be stored in a Profiler and incremented only at compile time. I've tried some different permutations and gotten only compile errors.

If there's no clean equivalent, how would you solve this in Zig?

GitHub

Source code for the https://computerenhance.com programming series - cmuratori/computer_enhance

#

comptime Equivalent to the __COUNTER__ macro?

rancid needle
#

This question was coincidentally just asked and answered on ziggit! Turns out there's not a close equivalent. I might try some of the alternatives linked in the post, but first I'm going to experiment with ComptimeStringMap with the anchor labels as keys.

high harbor
#

I don't know if you're still interested in this. I'm now working on the exact same problem because I'm also following Casey's course 😄
I did actually find a simple and seemingly stable solution for the __COUNTER__. It is a loophole which is planned to be closed, but if you want to use it anyway, this is it:

    if (!@inComptime()) @panic("getIndex can only be called from comptime.");
    _ = label; // passing the label makes this function compile once for each label, thus returning one unique index for each label
    defer static.countPtr().* += 1;
    return static.countPtr().*;
}

const static = blk: {
    comptime var count: usize = 0;
    break :blk struct {
        fn countPtr() *usize {
            return &count;
        }
    };
};```
#

so the rest of the code could look something like this:

    // usage example:
    var block = profiler.startBlock("some block");
    defer profiler.endBlock(block);
}

fn startBlock(comptime label: []const u8) ProfilerBlock {
    const index = comptime getIndex(label);
    return .{
        // ...
    };
}

fn endBlock(block: *ProfilerBlock) void {
    // ...
}```
#

where I got stuck was when I wanted to store the labels at comptime as well. I'm still looking into it, but didn't find a solution yet — not even using loopholes

rancid needle
#

@high harbor Thanks for the response, I found a similar solution! The comptime label does work for me, though. Are you getting a compile time error?

#

Also, I found the @src built-in function. Try passing @src().fn_name for the label!

#

I had trouble with the next part of the course, though. I wasn't sure what was the best way to turn the profiler on or off. I ended up implementing it through a build option that swaps out the profiler file for a mock profiler that doesn't do anything, but that seems pretty clumsy.

high harbor
#

@rancid needle I also just use a build option and am happy enough with it ^^
Describing my problem with storing the labels at comptime would be a bit much, but I might not need to if you can share your solution, I can check if it works for me. I will try to find my failing thing and post it though. The behavior was very weird: It worked for up to 8 labels, but from then on, they would just not be stored 🤷‍♂️

#

I'm not swapping the entire profiler file though. I can show you how I apply the build option

#

the main part with my build option is this:


pub const beginBlock = if (enabled) impl.beginBlock else no_op.beginBlock;
pub const endBlock = if (enabled) impl.endBlock else no_op.endBlock;

const no_op = struct {
    fn beginBlock(comptime label: []const u8) ProfileBlock {
        _ = label;
        return undefined;
    }

    fn endBlock(block: ProfileBlock) void {
        _ = block;
    }
};

const impl = struct {
    fn beginBlock(comptime label: []const u8) ProfileBlock {
        // real implementation
    }

    fn endBlock(block: ProfileBlock) void {
        // real implementation
    }
};
#

Thank's for pointing out @src! I didn't know about that one

rancid needle
#

@high harbor sorry for the late reply. In case you haven't got something working yet with the labels, here's what I had:

    pub fn beginBlock(self: *Profiler, comptime label: []const u8, index: usize, byte_count: u64) Block {
        const parent_index = self.global_parent_index;
        self.global_parent_index = index;
        self.anchors[index].processed_byte_count += byte_count;
        return Block{
            .start = metrics.readCPUTimer(),
            .anchor_index = index,
            .parent_index = parent_index,
            .old_elapsed_inclusive = self.anchors[index].elapsed_inclusive,
            .label = label,
        };
    }
#

(the byte_count is for throughput testing from a little later in the course)

#

I checked with quite a few labels and it seems to work for me