[PATCH 2/3] aarch64: Optimise AdvSIMD asinhf
Adhemerval Zanella Netto
adhemerval.zanella@linaro.org
Thu Dec 4 13:52:58 GMT 2025
On 28/11/25 08:18, James Chesterman wrote:
> Optimise AdvSIMD asinhf by vectorising the special case.
> For values greater than 0x1p64, scale the input down first.
> This is because the output will overflow with inputs greater than
> or equal to this value as there is a squaring operation in the
> algorithm.
> To scale, do:
> 2asinh(sqrt[(x-1)/2])
> Because:
> 2asinh(x) = +-acosh(2x^2 + 1)
> Apply opposite operations in opposite order for x, and you get:
> asinh(x) = 2acosh(sqrt[(x-1)/2]).
> Found that using asinh instead of acosh also very closely
> approximates asinh(x) for a high input x.
>
> R.Throughput difference on V2 with GCC@15:
> 25-58% improvement in special cases.
> 4% regression in fast pass.
I take that special cases are being hit in some workloads to add
such optimization.
> ---
> OK for master? If so, please commit for me as I don't have commit rights.
> Thanks,
LGTM, thanks.
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
> James
> sysdeps/aarch64/fpu/asinhf_advsimd.c | 76 ++++++++++++++++++++--------
> 1 file changed, 55 insertions(+), 21 deletions(-)
>
> diff --git a/sysdeps/aarch64/fpu/asinhf_advsimd.c b/sysdeps/aarch64/fpu/asinhf_advsimd.c
> index 90b2d78b5e..e953c72077 100644
> --- a/sysdeps/aarch64/fpu/asinhf_advsimd.c
> +++ b/sysdeps/aarch64/fpu/asinhf_advsimd.c
> @@ -24,46 +24,80 @@ const static struct data
> {
> struct v_log1pf_data log1pf_consts;
> float32x4_t one;
> - uint32x4_t big_bound;
> + uint32x4_t square_lim;
> + float32x4_t pinf, nan;
> } data = {
> .one = V4 (1),
> .log1pf_consts = V_LOG1PF_CONSTANTS_TABLE,
> - .big_bound = V4 (0x5f800000), /* asuint(0x1p64). */
> + .square_lim = V4 (0x5f800000), /* asuint(sqrt(FLT_MAX)). */
> + .pinf = V4 (INFINITY),
> + .nan = V4 (NAN),
> };
>
> -static float32x4_t NOINLINE VPCS_ATTR
> -special_case (float32x4_t x, uint32x4_t sign, float32x4_t y,
> - uint32x4_t special, const struct data *d)
> +static inline float32x4_t VPCS_ATTR
> +inline_asinhf (float32x4_t ax, uint32x4_t sign, const struct data *d)
> {
> - return v_call_f32 (
> - asinhf, x,
> - vreinterpretq_f32_u32 (veorq_u32 (
> - sign, vreinterpretq_u32_f32 (log1pf_inline (y, &d->log1pf_consts)))),
> - special);
> + /* Consider the identity asinh(x) = log(x + sqrt(x^2 + 1)).
> + Then, for x>0, asinh(x) = log1p(x + x^2 / (1 + sqrt(x^2 + 1))). */
> + float32x4_t t
> + = vaddq_f32 (v_f32 (1.0f), vsqrtq_f32 (vfmaq_f32 (d->one, ax, ax)));
> + float32x4_t y = vaddq_f32 (ax, vdivq_f32 (vmulq_f32 (ax, ax), t));
> +
> + return vreinterpretq_f32_u32 (veorq_u32 (
> + sign, vreinterpretq_u32_f32 (log1pf_inline (y, &d->log1pf_consts))));
> +}
> +
> +static float32x4_t VPCS_ATTR NOINLINE
> +special_case (float32x4_t ax, uint32x4_t sign, uint32x4_t special,
> + const struct data *d)
> +{
> + /* To avoid overflow in x^2 (so the x < sqrt(FLT_MAX) constraint), we
> + reduce the input of asinh to a narrower interval by relying on the
> + identity: 2asinh(t) = +-acosh(2t^2 + 1)
> + If we set t=sqrt((x-1)/2), then
> + 2asinh(sqrt((x-1)/2)) = acosh(x).
> + Found that, for a high input x, asinh(x) very closely approximates
> + acosh(x), so implemented it with this function instead. */
> + float32x4_t r = vsubq_f32 (ax, d->one);
> + r = vmulq_f32 (r, v_f32 (0.5f));
> + r = vbslq_f32 (special, vsqrtq_f32 (r), ax);
> +
> + float32x4_t y = inline_asinhf (r, sign, d);
> +
> + y = vbslq_f32 (special, vmulq_f32 (y, v_f32 (2.0f)), y);
> +
> + /* Check whether x is inf or nan. */
> + uint32x4_t ret_inf = vceqq_f32 (ax, d->pinf);
> + uint32x4_t ret_nan = vmvnq_u32 (vcleq_f32 (ax, d->pinf));
> + y = vbslq_f32 (ret_inf, d->pinf, y);
> + y = vbslq_f32 (ret_nan, d->nan, y);
> + /* Put sign back in for minf, as it doesn't happen in log1pf_inline call. */
> + y = vbslq_f32 (
> + ret_inf,
> + vreinterpretq_f32_u32 (veorq_u32 (vreinterpretq_u32_f32 (y), sign)), y);
> + return y;
> }
>
> /* Single-precision implementation of vector asinh(x), using vector log1p.
> Worst-case error is 2.59 ULP:
> _ZGVnN4v_asinhf(0x1.d86124p-3) got 0x1.d449bep-3
> want 0x1.d449c4p-3. */
> -VPCS_ATTR float32x4_t NOINLINE V_NAME_F1 (asinh) (float32x4_t x)
> +float32x4_t VPCS_ATTR NOINLINE V_NAME_F1 (asinh) (float32x4_t x)
> {
> - const struct data *dat = ptr_barrier (&data);
> + const struct data *d = ptr_barrier (&data);
> float32x4_t ax = vabsq_f32 (x);
> uint32x4_t iax = vreinterpretq_u32_f32 (ax);
> - uint32x4_t special = vcgeq_u32 (iax, dat->big_bound);
> uint32x4_t sign = veorq_u32 (vreinterpretq_u32_f32 (x), iax);
>
> - /* asinh(x) = log(x + sqrt(x * x + 1)).
> - For positive x, asinh(x) = log1p(x + x * x / (1 + sqrt(x * x + 1))). */
> - float32x4_t d
> - = vaddq_f32 (v_f32 (1), vsqrtq_f32 (vfmaq_f32 (dat->one, ax, ax)));
> - float32x4_t y = vaddq_f32 (ax, vdivq_f32 (vmulq_f32 (ax, ax), d));
> + /* Inputs greater than or equal to square_lim will cause the output to
> + overflow. This is because there is a square operation in the log1pf_inline
> + call. Also captures inf and nan. Does not capture negative numbers as we
> + separate the sign bit from the rest of the input. */
> + uint32x4_t special = vcgeq_u32 (iax, d->square_lim);
>
> if (__glibc_unlikely (v_any_u32 (special)))
> - return special_case (x, sign, y, special, dat);
> - return vreinterpretq_f32_u32 (veorq_u32 (
> - sign, vreinterpretq_u32_f32 (log1pf_inline (y, &dat->log1pf_consts))));
> + return special_case (ax, sign, special, d);
> + return inline_asinhf (ax, sign, d);
> }
> libmvec_hidden_def (V_NAME_F1 (asinh))
> HALF_WIDTH_ALIAS_F1 (asinh)
More information about the Libc-alpha
mailing list