Simplify to_float#179
Conversation
* Use right-sized uint type for bit fiddling ** This removes the need to special casing on endianness * Replace ternary with just shifting the sign at the right place ** This seems to improve codegen (less instructions, no cmov)
b3099da to
6d2fb68
Compare
|
Looks promising !!! |
|
I did some benchmarking using https://github.com/lemire/simple_fastfloat_benchmark main branch (b1d2773): this PR (leni536@6d2fb68): compiler: gcc-12 (Debian 12.2.0-13) 12.2.0 It looks like double conversion throughput increased around 7%, but float conversion throughput decreased around 1%. It might be worth checking the two code changes (right-sized uint and ternary) independently. I'm happy to do that soon. |
|
@leni536 Can you run this benchmark in privileged mode (e.g., with sudo) so that you get the performance counters? |
|
Here is what I get on my macbook (ARM M2, LLVM 14). 64-bit benchmarkMain: Your PR 32-bit benchmarkMain: Your PR |
|
If I use the command Main branch... This PR |
64 bitmain branch (b1d2773): This PR (leni536@6d2fb68): 32 bitmain branch: This PR: compiler: gcc-12 (Debian 12.2.0-13) 12.2.0 |
|
@leni536 Do you understand why your PR adds two instructions per float parsed under GCC/x64? On 64-bit ARM, the instruction count is the same, or one less. The following https://godbolt.org/z/ceYK4crde suggests that you would be saving instructions, not adding instructions. |
I don't have any idea. I might look into the benchmark binaries to track this down. I don't fully trust AMD performance counters (some of them are known to be inaccurate), so I reran the benchmarks on an Intel Pentium Silver N5030. It reproduces this same i/f metric, so it appears to be correct. |
|
We will be merging this for sure, because there is a gain, but it would be wise to figure out where the extra instructions come from before we do. It seems possible to improve this PR further if we have a better understanding of what the compiler does. |
|
Here is a comparison of an instantiated https://godbolt.org/z/hq988T4K6 An inlined call to It seems that there is a knock-on effect on register allocation in the whole surrounding function, which in turn might also affect ordering some blocks of code. Maybe there are a few places where a register needs to be zeroed where in the original it was not needed, adding an instruction, but this is just speculation. So my current hypothesis is that the PR version does not save much in terms of executed instruction count, and after inlining register allocation and maybe some optimization heuristics happen to work slightly worse, adding back instructions in code unrelated to this change. |
|
Fantastic. Ok. So I am merging this because it looks to produce slightly faster code and it is nicer looking. |
|
It will be part of the next release. |
Codegen demonstration:
gcc 12: https://godbolt.org/z/ceYK4crde
clang 15: https://godbolt.org/z/WTcqn8xGv
MSVC 19.14: https://godbolt.org/z/P3d8dv9Eq