evomassiny 3 hours ago

Could you duplicate `RECORD_INST` before each `INSTRUCTION_N`, so that:

```

   INSTRUCTION_1:
     // subroutine 1
     ip++;
     goto *dispatch_var[*ip];

   INSTRUCTION_2:
     // subroutine 2
     ip++;
     goto *dispatch_var[*ip];

```

becomes: ```

   RECORD_INST_1:
     // record logic
   INSTRUCTION_1:
     // subroutine 1
     ip++;
     goto *dispatch_var[*ip];

   RECORD_INST_2:
     // record logic
   INSTRUCTION_2:
     // subroutine 2
     ip++;
     goto *dispatch_var[*ip];

```

This way you could directly swap `dispatch_var` with an array populated with the `RECORD_INST_*` labels, and remove one step at runtime.

Or maybe this is what you are trying to avoid to reduce the binary size ?

  • kzrdude 3 hours ago

    That sounds smart, maybe worth testing, but I think it bloats the instruction cache even when not tracing.

  • drob518 1 hour ago

    Typically, the inner loop of an interpreter is all about keeping the branch predictor happy and trying to fit in L1 cache as much as possible. So, falling through makes sense. I’m also curious whether a simple check of a flag and a conditional branch (which is highly predictable for a mode flag like whether you’re tracing or not) wouldn’t be faster than one of the indirect branches. That would keep the tracing code out of L1 when not in use (you can locate it in a remote function).

  • monster_truck 19 minutes ago

    The centralized approach they're using does this without exploding in size. I think what you want would be thread local tables to avoid locks/syncing across threads? Would only help for mt, though.

    Writing to a ring buffer and processing in batches should be a relatively big W for everything. You could then filter for repeated instructions to compress, which would lend itself to prefetching the next instruction and buffer with hints. Mixed precision record storage might help too, but you're plucking hairs at that point. Making the sampling adaptive based on the hw profiling counters might eliminate some 'useless' work.

    The overhead is already so low that it really should not matter, though. Used to spend a lot of time trying to find wins like this but caches have gotten so large it basically doesn't matter. Even in pathologically memory or io bandwidth bound cases I've found it's usually faster to just run 2 or 4 smaller instances over trying to coordinate all of the threads in a single big one.

haeseong 4 hours ago

A `bool profile` branch stays expensive even when it predicts perfectly, because it bloats every opcode handler, and on a computed goto interpreter that extra code messes with the branch predictor history each dispatch site builds up, which is the whole reason dispatch is fast. Swapping the entire table dodges that. The part I like is fanning every opcode into one recording instruction and then back out through the real table, which is what keeps the second table from turning into a whole second interpreter like the earlier approach did.

  • achierius 25 minutes ago

    Reading through this accounts other comments, it feels rather Claude-like. I wouldn't mention this if it weren't a consistent pattern