You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Arm64: Forward memset/memcpy to CRT implementation #67326
It might be interesting to have two JIT_MemSet helpers - one that uses DC ZVA and another that doesn't. JIT will do the check for DC ZVA availability at compile time and emit a call to corresponding helper.
Few years ago, we already switched to using platform memset/memcpy in linux/arm64 in dotnet/coreclr#17536 and likewise for windows and linux on x64 in dotnet/coreclr#25750. The only left out was windows/arm64 which was still using the hand written assembly code. The platform implementation is highly optimized and has ways to use appropriate routines based on the size of block.
For some benchmarks that involve AnyLocation's init/copy block of 32+ bytes, we have a logic of using vector registers (and that would reduce the number of instructions), but we don't do that optimization if the address is not known, or it is not 16-byte aligned.
Probably, I will experiment to loosen the heuristics if the loads/stores are happening in a loop and if so, ignore the alignment part and just use SIMD registers.
Probably, I will experiment to loosen the heuristics if the loads/stores are happening in a loop and if so, ignore the alignment part and just use SIMD registers.
Probably, I will experiment to loosen the heuristics if the loads/stores are happening in a loop and if so, ignore the alignment part and just use SIMD registers.
@EgorChesakov - Did you experimented this?
@kunalspathak Yes, this was how I implemented the algorithm in the first place. However, during testing I found that some benchmarks regressed due to using SIMD loads/stores with unaligned addresses. Hence, I extended the algorithm to check for src/dst 16-byte alignment. But, if you loose the heuristic you will get even better code size improvement that I had originally.
However, during testing I found that some benchmarks regressed due to using SIMD loads/stores with unaligned addresses.
Thanks for confirming @EgorChesakov . In case you recall, was that from local testing or from the perf lab. If it was local testing, I am inclined to get this change in and see how perf lab reacts.
Thanks for confirming @EgorChesakov . In case you recall, was that from local testing or from the perf lab. If it was local testing, I am inclined to get this change in and see how perf lab reacts.
I did a private run of perf lab pipeline. And when the results came back I could reproduce the regressions locally on Surface Pro X.
Seems like none of the C++ compilers have any such restrictions and does use SIMD register wherever possible. https://godbolt.org/z/eb53xPvYT
Yes C/C++ compilers do mostly always use SIMD for copies unless strict alignment is required (-mstrict-align) in which case they'll punt to memcpy. The reason we can do this without a perf loss in general is because of the padding and alignment of structures in C.
So in your example link, if you look, .bss section is always 16bytes aligned. And the structs are padded to a multiple of 8. See e.g. https://godbolt.org/z/cbn8d98ez and look at the .bss section, particularly the .align and .size directives. So fldTa, fldTb are at an aligned location.
For stack based copies the structure is aligned on the stack https://godbolt.org/z/8fv1eY7va so again unless the user has specified a custom alignment it works out.
Which leaves general heap copies which normally have a bound higher than we would inline, so those get punted to memcopy.
@TamarChristinaArm - any thoughts if we should also use SIMD registers and drop the condition about alignment?
I think @EgorChesakov 's earlier experiment have shown that for the dotnet case this doesn't seem to be a win in general, The behavior here is somewhat dependent on the core, how it handles pairs and the total available memory bandwidth.
I have been meaning to ask what the actual alignment and padding for structs in .NET is.
Activity
kunalspathak commentedon Mar 30, 2022
@dotnet/jit-contrib
kunalspathak commentedon Mar 30, 2022
@a74nh - FYI
kunalspathak commentedon Mar 30, 2022
If we switch to CRT
memsetwe will see some benefits (at least) on linux because LLVM usesDC ZVAfor that.https://github.com/llvm/llvm-project/blob/main/libc/AOR_v20.02/string/aarch64/memset.S
Likewise
memcpyis much better - https://github.com/llvm/llvm-project/blob/main/libc/AOR_v20.02/string/aarch64/memcpy.S and https://github.com/llvm/llvm-project/blob/main/libc/AOR_v20.02/string/aarch64/memcpy-advsimd.SEgorBo commentedon Mar 30, 2022
From my understanding we do use native memset on windows-arm64, it's just *nix-arm64 - should be easy to fix
echesakov commentedon Mar 31, 2022
This is correct, although, I would not expect to see much of the performance improvement (especially, for smaller block sizes) only due to using DC ZVA. Using DC ZVA requires zeroing up to 64 bytes at the beginning and the end of the block manually. It also requires checking for DC ZVA availability on the target platform (e.g. as in https://github.com/llvm/llvm-project/blob/881350a92d821d4f8e4fa648443ed1d17e251188/libc/AOR_v20.02/string/aarch64/memset.S#L81) at runtime.
It might be interesting to have two
JIT_MemSethelpers - one that uses DC ZVA and another that doesn't. JIT will do the check for DC ZVA availability at compile time and emit a call to corresponding helper.kunalspathak commentedon Apr 8, 2022
Few years ago, we already switched to using platform memset/memcpy in linux/arm64 in dotnet/coreclr#17536 and likewise for windows and linux on x64 in dotnet/coreclr#25750. The only left out was windows/arm64 which was still using the hand written assembly code. The platform implementation is highly optimized and has ways to use appropriate routines based on the size of block.
kunalspathak commentedon Apr 13, 2022
All the benchmarks access the array element using
refand currently we don't generate decent addressing mode for them. Tracked in #64819.kunalspathak commentedon Apr 14, 2022
For some benchmarks that involve
AnyLocation's init/copy block of 32+ bytes, we have a logic of using vector registers (and that would reduce the number of instructions), but we don't do that optimization if the address is not known, or it is not 16-byte aligned.runtime/src/coreclr/jit/codegenarmarch.cpp
Lines 2679 to 2685 in e5eee9a
Because of that, here is the diff (left = x64 and right =arm64).
Reference:

kunalspathak commentedon Apr 14, 2022
Probably, I will experiment to loosen the heuristics if the loads/stores are happening in a loop and if so, ignore the alignment part and just use SIMD registers.
kunalspathak commentedon Apr 14, 2022
@EgorChesakov - Did you experimented this?
kunalspathak commentedon Apr 14, 2022
Seems like none of the C++ compilers have any such restrictions and does use SIMD register wherever possible.
https://godbolt.org/z/eb53xPvYT
@TamarChristinaArm - any thoughts if we should also use SIMD registers and drop the condition about alignment?
echesakov commentedon Apr 15, 2022
@kunalspathak Yes, this was how I implemented the algorithm in the first place. However, during testing I found that some benchmarks regressed due to using SIMD loads/stores with unaligned addresses. Hence, I extended the algorithm to check for src/dst 16-byte alignment. But, if you loose the heuristic you will get even better code size improvement that I had originally.
kunalspathak commentedon Apr 15, 2022
Thanks for confirming @EgorChesakov . In case you recall, was that from local testing or from the perf lab. If it was local testing, I am inclined to get this change in and see how perf lab reacts.
echesakov commentedon Apr 16, 2022
I did a private run of perf lab pipeline. And when the results came back I could reproduce the regressions locally on Surface Pro X.
TamarChristinaArm commentedon Apr 16, 2022
Yes C/C++ compilers do mostly always use SIMD for copies unless strict alignment is required (
-mstrict-align) in which case they'll punt tomemcpy. The reason we can do this without a perf loss in general is because of the padding and alignment of structures in C.So in your example link, if you look,
.bsssection is always 16bytes aligned. And the structs are padded to a multiple of 8. See e.g. https://godbolt.org/z/cbn8d98ez and look at the.bsssection, particularly the.alignand.sizedirectives. SofldTa, fldTbare at an aligned location.For stack based copies the structure is aligned on the stack https://godbolt.org/z/8fv1eY7va so again unless the user has specified a custom alignment it works out.
Which leaves general heap copies which normally have a bound higher than we would inline, so those get punted to
memcopy.I think @EgorChesakov 's earlier experiment have shown that for the dotnet case this doesn't seem to be a win in general, The behavior here is somewhat dependent on the core, how it handles pairs and the total available memory bandwidth.
I have been meaning to ask what the actual alignment and padding for structs in .NET is.
kunalspathak commentedon Apr 22, 2022
Fixed via #67788 and #68085