[HN Gopher] Quantifying pass-by-value overhead
___________________________________________________________________
Quantifying pass-by-value overhead
Author : todsacerdoti
Score : 94 points
Date : 2025-10-28 00:52 UTC (1 days ago)
(HTM) web link (owen.cafe)
(TXT) w3m dump (owen.cafe)
| anonymous908213 wrote:
| > Don't pass around data of size 4046-4080 bytes or 8161-8176
| bytes, by value (at least not on an AMD Ryzen 3900X).
|
| What a fascinating CPU bug. I am quite curious as to how that
| came to pass.
| sgarland wrote:
| Me too, and I hope this article gets more traction.
| jasonthorsness wrote:
| Apparently some sizes are cursed!
|
| It would be great to repeat the author's tests on other CPU
| models
| TuxSH wrote:
| I wonder what the page size is on his system (and what
| effective alignment his pointers have). If it's 4K, the sizes
| look really close to 0x1000 and 0x2000 - maybe crossing page
| boundaries?
| astrange wrote:
| It's because of cache addressing conflicts. If two
| addresses have the same cache key your cache suddenly
| doesn't work anymore. And many CPUs just use the low bits
| instead of hashing it.
| Veserv wrote:
| L1 caches are usually N-way associative, so that should
| only become a consistent problem if you access N distinct
| addresses with the same key (in this case the same offset
| (with likely 64-byte granularity) relative to a 4K
| boundary).
| srcmax wrote:
| That's called 4k aliasing. 4K aliasing occurs when you store
| one memory location, then load from another memory location
| which is 4KB offset from original.
| jmalicki wrote:
| (and if it is not apparent to some readers, most modern
| x86-based systems use 64 byte cache line sizes, which is sort
| of analogous to disk block size - quite a few memory
| operations tend to happen in 64 byte chunks under the covers
| - the ones that don't are "special")
| nabla9 wrote:
| To my knowledge all x86 based systems and ARM and Qualcomm
| designed chips all use 64 byte cache lines.
|
| Apple's M2 uses 128-byte cache line.
| themafia wrote:
| Would you expect different performance with 2M page sizes? Is
| this a TLB issue or just a fundamental hardware issue?
| codedokode wrote:
| I usually use ChatGPT for such microbenchmarks (of course I
| design it myself and use LLM only as dumb code generator, so I
| don't need to remember how to measure time with nanosecond
| precision. I still have to add workarounds to prevent compiler
| over-optimizing the code). It's amazing, that when you get
| curious (for example, what is the fastest way to find an int in a
| small sorted array: using linear, binary search or branchless
| full scan?) you can get the answer in a couple minutes instead of
| spending 20-30 minutes writing the code manually.
|
| By the way, the fastest way was branchless linear scan up to
| 32-64 elements, as far as I remember.
| lurquer wrote:
| In C++, I've noticed that ChatGPT is fixated on unordered_maps.
| No matter the situation, when I ask what container would be
| wise to use, it's always inordered_maps. Even when you tell it
| the container will have at most a few hundred elements (a size
| that would allow you to iterate their a vector to find what
| your are looking for before the unordered_map even has its
| morning coffee) it pushes the map... with enough prodding, it
| will eventually concede that a vector pretty much beats
| everything for small .size()'s.
| bmandale wrote:
| I agree with chatgpt here
| remexre wrote:
| isn't std::unordered_map famously slow, and you really want
| the hashmap from abseil, or boost, or folly, or [...]
| themafia wrote:
| > I still have to add workarounds to prevent compiler over-
| optimizing the code
|
| Yet remembering how to measure time with nanosecond precision
| is the burden?
|
| > By the way, the fastest way was branchless linear scan up to
| 32-64 elements, as far as I remember.
|
| The analysis presented in the article is far more interesting,
| qualified, and useful that what you've produced here.
| jklowden wrote:
| There is no pass-by-value overhead. There are only implementation
| decisions.
|
| Pass by value describes the semantics of a function call, not
| implementation. Passing a const reference in C++ is pass-by-
| value. If the user opts to pass "a copy" instead, nothing
| requires the compiler to actually copy the data. The compiler is
| required only to supply the actual parameter as if it was copied.
| duped wrote:
| Unfortunately "the compiler is required to supply the actual
| parameter as if it was copied" is leaky with respect to the ABI
| and linker. In C and C++ you cannot fully abstract it.
| mattnewport wrote:
| This might be true in the abstract but it's not true of actual
| compilers dealing with real world calling conventions. Absent
| inlining or whole program optimization, calling conventions
| across translation units don't leave much room for flexibility.
|
| The semantics of pass by const reference are also not exactly
| the same as pass by value in C++. The compiler can't in general
| assume a const reference doesn't alias other arguments or
| global variables and so has to be more conservative with
| certain optimizations than with pass by value.
| themafia wrote:
| > Passing a const reference in C++ is pass-by-value.
|
| I can cast the const away. The implementation does not hide
| this detail. The semantics therefore must be understood by the
| programmer.
| layer8 wrote:
| > Passing structs up to size 256 is very cheap, and uses SIMD
| registers.
|
| Presumably this means for all arguments combined? If for example
| you pass four pointers each pointing to a 256-byte struct, you
| probably don't want to pass all four structs (or even just one or
| two of the four?) by value instead.
| pizlonator wrote:
| I would ignore this benchmark because it's not going to predict
| anything for real world code.
|
| In real world code, your caches and the CPU's pipeline are
| influenced by some complex combination of what happens at the
| call site and what else the program is doing. So, a particular
| kind of call will perform better or worse than another kind of
| call depending on what else is happening.
|
| The version of this benchmark that would have had predictive
| power is if you compared different kinds of call across a
| sufficiently diverse sampling of large programs that used those
| calls and also did other interesting things.
___________________________________________________________________
(page generated 2025-10-29 23:01 UTC)