Last week I randomly ran into a V8 behaviour I didn’t expect.
Here is a Primes
prototype:


As you can see, it doesn’t take parameters. You would simply do new Primes()
, not new Primes(2, ['foo', bar()], Math.random() * baz())
.
Here is a main
function using Primes
to generate the n
first prime numbers, putting them in a list:


Now let’s make two versions of this script. The only difference between the first version, normal.js
, and the second version, extraarg.js
, is this one:




Primes
as expected: new Primes()
. In the extraarg version, I pass an extra argument like this: new Primes(null)
. Guess what?


The extraarg version consistently runs ~10% faster on v8
5.8.9
and 5.8.101
:bench/d8 normal.js
lower bound  estimate  upper bound  

OLS regression  xxx  xxx  xxx 
R² goodnessoffit  xxx  xxx  xxx 
Mean execution time  1.2244014127760094  1.2302440249237707  1.2336210836091757 
Standard deviation  0.0  5.241324404964492e3  5.849237223263123e3 
Outlying measurements have moderate (0.18749999999999997%) effect on estimated standard deviation.
bench/d8 extraarg.js
lower bound  estimate  upper bound  

OLS regression  xxx  xxx  xxx 
R² goodnessoffit  xxx  xxx  xxx 
Mean execution time  1.094407024081591  1.0972965331160693  1.0994041288091074 
Standard deviation  0.0  3.19627481050371e3  3.650462822155438e3 
Outlying measurements have moderate (0.18749999999999997%) effect on estimated standard deviation.
(Benchmark done using bench, report generated by criterion.)
As we can see, with 0
argument it’s slow, with 1
it’s fast. What happens as we keep adding more arguments? (List index is number of arguments.)
 fast
 slow
 fast
 fast
 slow
 fast
 fast
 slow
 fast
 slow
 fast
 fast
 slow
 fast
 fast
My attempts at understanding what’s happening here failed. I really don’t know the reason behind these differences.
I ran the two original versions, the one with zero argument and the one with one argument, using d8
built with debug options:


Here are the generated files if you want to analyze them yourself:
Here is a cleaned up diff of the code.asm
and hydrogen.cfg
generated by d8
: https://gist.github.com/vhf/c503a82fa4293a6545edb67abab3904f. I only kept the diff blocks I deemed significant. Most of the diff was only identifiers that change every run.
If anyone can explain what happens here, please ping me on twitter _vhf or send me an email ([email protected]
this domain (draft.li
) ! I’ll link to your explanation or include it here or rephrase it here.
The number of instructions fetched on a given cycle is dependent on multiple factors. For Intel’s fourth generation Core processors, when using the instruction cache rather than the µop cache, an aligned 16 byte block of instructions are fetched each cycle.
To get a better understanding of why and how alignment matters, check out Agner Fog’s the microarchitecture doc, esp. the section about the instructionfetch frontend of various CPU designs.