Uh oh!
There was an error while loading. Please reload this page.
VM performance improvements in function calls - #832
Conversation
93986dc to
d030d1eCompare| func estimateFnArgsCount(program *Program) int { | ||
| // Implementation note: a program will not necessarily go through all | ||
| // operations, but this is just an estimation | ||
| var count int | ||
| for _, op := range program.Bytecode { | ||
| if int(op) < len(opArgLenEstimation) { | ||
| count += opArgLenEstimation[op] | ||
| } | ||
| } | ||
| return count | ||
| } | ||
| var opArgLenEstimation = [...]int{ | ||
| OpCall1: 1, | ||
| OpCall2: 2, | ||
| OpCall3: 3, | ||
| // we don't know exactly but we know at least 4, so be conservative as this | ||
| // is only an optimization and we also want to avoid excessive preallocation | ||
| OpCallN: 4, | ||
| // here we don't know either, but we can guess it could be common to receive | ||
| // up to 3 arguments in a function | ||
| OpCallFast: 3, | ||
| OpCallSafe: 3, | ||
| } |
There was a problem hiding this comment.
I initially used a switch in estimateFnArgsCount but then tried with this table and got a 4% improvement in speed.
However, you can see that I am making an array with 56 elements but I'm using only 6. I preferred it this way because I think the code looks clearer. But if you prefer to make the table use exactly the number of items it needs then we would just need to make the following change (you can just apply this suggestion as it is here if you want, I just tested this exact code and it is also correctly formatted with spaces :) ):
| funcestimateFnArgsCount(program*Program) int { | |
| // Implementation note: a program will not necessarily go through all | |
| // operations, but this is just an estimation | |
| varcountint | |
| for_, op:=range program.Bytecode { | |
| ifint(op) <len(opArgLenEstimation) { | |
| count+=opArgLenEstimation[op] | |
| } | |
| } | |
| returncount | |
| } | |
| varopArgLenEstimation= [...]int{ | |
| OpCall1: 1, | |
| OpCall2: 2, | |
| OpCall3: 3, | |
| // we don't know exactly but we know at least 4, so be conservative as this | |
| // is only an optimization and we also want to avoid excessive preallocation | |
| OpCallN: 4, | |
| // here we don't know either, but we can guess it could be common to receive | |
| // up to 3 arguments in a function | |
| OpCallFast: 3, | |
| OpCallSafe: 3, | |
| } | |
| funcestimateFnArgsCount(program*Program) int { | |
| // Implementation note: a program will not necessarily go through all | |
| // operations, but this is just an estimation | |
| varcountint | |
| for_, op:=range program.Bytecode { | |
| op-=OpCall1// if underflows only becomes bigger so it's ok | |
| ifint(op) <len(opArgLenEstimation) { | |
| count+=opArgLenEstimation[op] | |
| } | |
| } | |
| returncount | |
| } | |
| varopArgLenEstimation= [...]int{ | |
| OpCall1-OpCall1: 1, | |
| OpCall2-OpCall1: 2, | |
| OpCall3-OpCall1: 3, | |
| // we don't know exactly but we know at least 4, so be conservative as this | |
| // is only an optimization and we also want to avoid excessive preallocation | |
| OpCallN-OpCall1: 4, | |
| // here we don't know either, but we can guess it could be common to receive | |
| // up to 3 arguments in a function | |
| OpCallFast-OpCall1: 3, | |
| OpCallSafe-OpCall1: 3, | |
| } |
1e5e3d8 to
2fb1f53Compareantonmedv
commented
Sep 18, 2025
I guess OpCall1, OpCall2 OpCall3 is kind of a same way of avoiding buffer allocation. What is the speedup? Also, probably v1.18 will gonna be refactored to a new architecture ;) |
At first, I also thought that OpCall1, OpCall2, and OpCall3 wouldn't allocate in the heap. But they do allocate in the heap when I run the benchmarks and they run slower. Total speedup is 15%. And reduced to a single allocation per run in most cases.
Nice, can't wait! Ping me if you need some help :) |
23dba4e to
a3d86eeComparediegommm
commented
Sep 29, 2025
@antonmedv I answered above, let me know if you want me to try a different approach or if you think it's ok we could merge it. |
antonmedv
commented
Sep 30, 2025
Let me try to test it again, and run my benches as well. |
diegommm
commented
Oct 25, 2025
Hi @antonmedv! I apologize for bothering, I wanted to know if I can help providing better benchmarks. Or let me know if anything doesn't look good and I can improve it. Thank you! |
antonmedv
commented
Oct 26, 2025
Hi! Sorry I was sick for lats weeks. I will come back to reviewing stuff. |
Uh oh!
There was an error while loading. Please reload this page.
Improve memory handling of function arguments in vm.VM by preallocating a single slice to hold all the arguments for all the function calls. This is based on an estimation made by inspecting the program's bytecode.
There are several points to argue here of course:
program.Argumentsto get the exact number of arguments being passed. Answer: While this is true, this adds a little more computation and an estimation works fairly good for most cases. We gain ~5% speed by making an estimation and it is likely to be good enough in many situations.In general, this optimization works well for many simple and common use cases and doesn't affect other cases.
Benchmark results: