```
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 ?
The other comment about interpreters being greatly affected by instruction cache is right. The other problem is that PGO in CPython's test suite could be improved and when you have the full thing as a switch-case and trigger the profiling mode, PGO then mistakenly thinks that the RECORD_INST_X section is hot (when it is clearly not). This causes a pessimization in the interpreter loop leading to the 6% slowdown.
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.