exec_do_range
is also taking more time than all selection and variation combined.
Now that I think about it, is this a little weird? Even if exec_y
and exec_do_range
are running tons of times because they appear in a bunch of programs and run a lot, shouldnât each individual execution be short? Does your profiler show you how many times each function is called, or alternatively how long each runs on average, and if so, is the average time higher than other instructions, or is it about the same and theyâre just getting called a lot more often?
Good questions. I did 1 run of substitution-cipher with a population of 500 and 30 generations using 200 training cases and a step limit of 500. exec_do_range
seemed less prevalent here than in some of the other runs I have done in the past, but it still shows the comparison.
- Whole run took 4320 seconds
- 1.5% of which was doing
exec_do_range
.
- 1.5% of which was doing
exec_do_range
was called 5.5 million times.- Average of 1.8 calls per program execution.
- 1.5% of the runtime.
- Time spent in instruction, not including sub-functions
- Total 19.51s, Average per call 3.519e-06s
- Time spent in instruction, including sub-functions
- Total 63.45, Average per call 1.144e-05s
- There were 10,697,946 calls to the
shove_*
instructions (all types reported together).- Average of 3.5 calls per program execution.
- 0.73% of the runtime.
- Time spent in instruction, not including sub-functions
- Total 13.51s, Average per call 1.263e-06s
- Time spent in instruction, including sub-functions
- Total 31.48, Average per call 2.942e-06s
exec_when
was called 2.1 million times.- Average of 0.7 calls per program execution.
- 0.04% of the runtime
- Time spent in instruction, not including sub-functions
- Total 0.9964s, Average per call 4.664e-07s
- Time spent in instruction, including sub-functions
- Total 1.938, Average per call 9.073e-07s
These stats imply the issue is somewhat about exec_do_range
being called a lot (which is backed up by the fact that when I lower the step limit time spent in exec_do_range
drops proportionally) but also that exec_do_range
is one of the most expensive instructions to call a single time.
The breakdown of time spent in exec_do_range
is:
- 40% direct logic of
exec_do_range
(comparing loop counters, building next iteration, etc.) - 20% looking up stack from state
- 15% enforcing limits when pushing to stack.
- 15% Peaking at top stack item
- 10% manipulating the stack.
This gives me a few threads to pull on.
I mean, unless there is something super weird about the implementation that is hard to see, the reason exec_do_range
runs often is almost certainly about how integer constants arise, not the instruction itself. Itâs re-pushed to the :exec
stack once for ever integer between the to popped from the stack originally. And in my experience, integers get big very quickly.
Long ago in the earliest days of the Klapaucius fork, the fact that :integer
values are so huge just via fiddling led me to force iteration and other bounds that want to be âmodestâ to use values mod 1000 or 100, internally. I think I named it :exec_do_some
at that point? I canât recall.
Further, if exec_do_range
executes code that modifies its own bounds, itâs basically just sparkling exec_y
at that point.
True, but the average time per call of exec_do_range
is 3-10x larger than any other instruction I looked at. In the case of pyshgp, it seems like this is primarily because exec_do_range
has to instantiate a few objects and collections to create the block of code that needs to be pushed to the exec stack.
I did 1 run of GCD with a population size of 500 for 10 generations and 200 training cases. Results are even more dramatic than the previous example.
- Whole run took 3390 seconds
-
exec_do_range
was called 23.5 million times.- Average of 23 calls per program execution.
- 12.55% of the runtime.
- Time spent in instruction, not including sub-functions
- Total 104s, Average per call 4.424e-06s
- Time spent in instruction, including sub-functions
- Total 425.7s, Average per call 1.811e-05s
- There were 5.9 million calls to
yank_*
instructions (all types reported together).- Average of 6 calls per program execution
- 1.2% of the runtime
- Time spent in instruction, not including sub-functions
- Total 14.62s, Average per call 2.452e-06s
- Time spent in instruction, including sub-functions
- Total 41.4s, Average per call 6.942e-06s
- Average of 6 calls per program execution
In this run, each call to exec_do_range
took 3x the time compared to a simple yank_
instruction. Also, exec_do_range
was called 4X more frequently than all three yank_
instructions in the genetic sources combined.