These pieces of code calculate different result than scalar paths:
HMM_DotV4:
|
float32x4_t NEONMultiplyResult = vmulq_f32(Left.NEON, Right.NEON); |
|
float32x4_t NEONHalfAdd = vpaddq_f32(NEONMultiplyResult, NEONMultiplyResult); |
|
float32x4_t NEONFullAdd = vpaddq_f32(NEONHalfAdd, NEONHalfAdd); |
HMM_LinearCombineV4M4:
|
Result.NEON = vfmaq_laneq_f32(Result.NEON, Right.Columns[1].NEON, Left.NEON, 1); |
|
Result.NEON = vfmaq_laneq_f32(Result.NEON, Right.Columns[2].NEON, Left.NEON, 2); |
|
Result.NEON = vfmaq_laneq_f32(Result.NEON, Right.Columns[3].NEON, Left.NEON, 3); |
HMM_MulQ:
|
Result.NEON = vmulq_f32(Right3210, vmulq_f32(vdupq_laneq_f32(Left.NEON, 0), FirstSign)); |
|
float32x4_t SecondSign = {1.0f, 1.0f, -1.0f, -1.0f}; |
|
Result.NEON = vfmaq_f32(Result.NEON, Right2301, vmulq_f32(vdupq_laneq_f32(Left.NEON, 1), SecondSign)); |
|
float32x4_t ThirdSign = {-1.0f, 1.0f, 1.0f, -1.0f}; |
|
Result.NEON = vfmaq_f32(Result.NEON, Right1032, vmulq_f32(vdupq_laneq_f32(Left.NEON, 2), ThirdSign)); |
|
Result.NEON = vfmaq_laneq_f32(Result.NEON, Right.NEON, Left.NEON, 3); |
HMM_DotQ:
|
float32x4_t NEONMultiplyResult = vmulq_f32(Left.NEON, Right.NEON); |
|
float32x4_t NEONHalfAdd = vpaddq_f32(NEONMultiplyResult, NEONMultiplyResult); |
|
float32x4_t NEONFullAdd = vpaddq_f32(NEONHalfAdd, NEONHalfAdd); |
|
Result = vgetq_lane_f32(NEONFullAdd, 0); |
Example illustrating difference for HMM_DotV4: https://godbolt.org/z/14aEbqa4v
A few unrelated things (I'm too lazy to create new issue):
-
vmulq_f32(vdupq_laneq_f32(a, n), b) can be simplified to vmulq_laneq_f32(b, a, n)
-
this should not use vld4q_f32:
|
float32x4x4_t Transposed = vld4q_f32((float*)Matrix.Columns); |
As that forces compiler to store registers to memory for loading back. Instead it should do something like this https://developer.arm.com/documentation/102107a/0100/Floating-point-4x4-matrix-transposition
Use vld4q_f32 if you guarantee input comes from memory. Sometimes it can be better. But that very much depends on what exactly code is optimized for. Usually you won't want round-trip through memory in such code.
These pieces of code calculate different result than scalar paths:
HMM_DotV4:
HandmadeMath/HandmadeMath.h
Lines 987 to 989 in 142ba3c
HMM_LinearCombineV4M4:
HandmadeMath/HandmadeMath.h
Lines 1121 to 1123 in 142ba3c
HMM_MulQ:
HandmadeMath/HandmadeMath.h
Lines 2209 to 2214 in 142ba3c
HMM_DotQ:
HandmadeMath/HandmadeMath.h
Lines 2301 to 2304 in 142ba3c
Example illustrating difference for HMM_DotV4: https://godbolt.org/z/14aEbqa4v
A few unrelated things (I'm too lazy to create new issue):
vmulq_f32(vdupq_laneq_f32(a, n), b)can be simplified tovmulq_laneq_f32(b, a, n)this should not use
vld4q_f32:HandmadeMath/HandmadeMath.h
Line 1523 in 142ba3c
As that forces compiler to store registers to memory for loading back. Instead it should do something like this https://developer.arm.com/documentation/102107a/0100/Floating-point-4x4-matrix-transposition
Use vld4q_f32 if you guarantee input comes from memory. Sometimes it can be better. But that very much depends on what exactly code is optimized for. Usually you won't want round-trip through memory in such code.