Skip to content

Commit 51e9430

Browse files
authored
[libc][math] Improve performance of double precision trig functions. (#111793)
- Improve the accuracy of fast pass' range reduction. - Provide tighter error estimations. - Reduce the table size when `LIBC_MATH_SMALL_TABLES` flag is set.
1 parent 126ed16 commit 51e9430

File tree

14 files changed

+666
-855
lines changed

14 files changed

+666
-855
lines changed

libc/src/__support/FPUtil/double_double.h

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
namespace LIBC_NAMESPACE_DECL {
1919
namespace fputil {
2020

21+
#define DEFAULT_DOUBLE_SPLIT 27
22+
2123
using DoubleDouble = LIBC_NAMESPACE::NumberPair<double>;
2224

2325
// The output of Dekker's FastTwoSum algorithm is correct, i.e.:
@@ -61,7 +63,8 @@ LIBC_INLINE constexpr DoubleDouble add(const DoubleDouble &a, double b) {
6163
// Zimmermann, P., "Note on the Veltkamp/Dekker Algorithms with Directed
6264
// Roundings," https://inria.hal.science/hal-04480440.
6365
// Default splitting constant = 2^ceil(prec(double)/2) + 1 = 2^27 + 1.
64-
template <size_t N = 27> LIBC_INLINE constexpr DoubleDouble split(double a) {
66+
template <size_t N = DEFAULT_DOUBLE_SPLIT>
67+
LIBC_INLINE constexpr DoubleDouble split(double a) {
6568
DoubleDouble r{0.0, 0.0};
6669
// CN = 2^N.
6770
constexpr double CN = static_cast<double>(1 << N);
@@ -73,14 +76,30 @@ template <size_t N = 27> LIBC_INLINE constexpr DoubleDouble split(double a) {
7376
return r;
7477
}
7578

79+
// Helper for non-fma exact mult where the first number is already split.
80+
template <size_t SPLIT_B = DEFAULT_DOUBLE_SPLIT>
81+
LIBC_INLINE DoubleDouble exact_mult(const DoubleDouble &as, double a,
82+
double b) {
83+
DoubleDouble bs = split<SPLIT_B>(b);
84+
DoubleDouble r{0.0, 0.0};
85+
86+
r.hi = a * b;
87+
double t1 = as.hi * bs.hi - r.hi;
88+
double t2 = as.hi * bs.lo + t1;
89+
double t3 = as.lo * bs.hi + t2;
90+
r.lo = as.lo * bs.lo + t3;
91+
92+
return r;
93+
}
94+
7695
// Note: When FMA instruction is not available, the `exact_mult` function is
7796
// only correct for round-to-nearest mode. See:
7897
// Zimmermann, P., "Note on the Veltkamp/Dekker Algorithms with Directed
7998
// Roundings," https://inria.hal.science/hal-04480440.
8099
// Using Theorem 1 in the paper above, without FMA instruction, if we restrict
81100
// the generated constants to precision <= 51, and splitting it by 2^28 + 1,
82101
// then a * b = r.hi + r.lo is exact for all rounding modes.
83-
template <bool NO_FMA_ALL_ROUNDINGS = false>
102+
template <size_t SPLIT_B = 27>
84103
LIBC_INLINE DoubleDouble exact_mult(double a, double b) {
85104
DoubleDouble r{0.0, 0.0};
86105

@@ -90,18 +109,8 @@ LIBC_INLINE DoubleDouble exact_mult(double a, double b) {
90109
#else
91110
// Dekker's Product.
92111
DoubleDouble as = split(a);
93-
DoubleDouble bs;
94112

95-
if constexpr (NO_FMA_ALL_ROUNDINGS)
96-
bs = split<28>(b);
97-
else
98-
bs = split(b);
99-
100-
r.hi = a * b;
101-
double t1 = as.hi * bs.hi - r.hi;
102-
double t2 = as.hi * bs.lo + t1;
103-
double t3 = as.lo * bs.hi + t2;
104-
r.lo = as.lo * bs.lo + t3;
113+
r = exact_mult<SPLIT_B>(as, a, b);
105114
#endif // LIBC_TARGET_CPU_HAS_FMA
106115

107116
return r;
@@ -113,10 +122,10 @@ LIBC_INLINE DoubleDouble quick_mult(double a, const DoubleDouble &b) {
113122
return r;
114123
}
115124

116-
template <bool NO_FMA_ALL_ROUNDINGS = false>
125+
template <size_t SPLIT_B = 27>
117126
LIBC_INLINE DoubleDouble quick_mult(const DoubleDouble &a,
118127
const DoubleDouble &b) {
119-
DoubleDouble r = exact_mult<NO_FMA_ALL_ROUNDINGS>(a.hi, b.hi);
128+
DoubleDouble r = exact_mult<SPLIT_B>(a.hi, b.hi);
120129
double t1 = multiply_add(a.hi, b.lo, r.lo);
121130
double t2 = multiply_add(a.lo, b.hi, t1);
122131
r.lo = t2;
@@ -157,8 +166,8 @@ LIBC_INLINE DoubleDouble div(const DoubleDouble &a, const DoubleDouble &b) {
157166
double e_hi = fputil::multiply_add(b.hi, -r.hi, a.hi);
158167
double e_lo = fputil::multiply_add(b.lo, -r.hi, a.lo);
159168
#else
160-
DoubleDouble b_hi_r_hi = fputil::exact_mult</*NO_FMA=*/true>(b.hi, -r.hi);
161-
DoubleDouble b_lo_r_hi = fputil::exact_mult</*NO_FMA=*/true>(b.lo, -r.hi);
169+
DoubleDouble b_hi_r_hi = fputil::exact_mult(b.hi, -r.hi);
170+
DoubleDouble b_lo_r_hi = fputil::exact_mult(b.lo, -r.hi);
162171
double e_hi = (a.hi + b_hi_r_hi.hi) + b_hi_r_hi.lo;
163172
double e_lo = (a.lo + b_lo_r_hi.hi) + b_lo_r_hi.lo;
164173
#endif // LIBC_TARGET_CPU_HAS_FMA

libc/src/__support/macros/optimization.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,16 @@ LIBC_INLINE constexpr bool expects_bool_condition(T value, T expected) {
4848

4949
#ifndef LIBC_MATH
5050
#define LIBC_MATH 0
51+
#else
52+
53+
#if (LIBC_MATH & LIBC_MATH_SKIP_ACCURATE_PASS)
54+
#define LIBC_MATH_HAS_SKIP_ACCURATE_PASS
55+
#endif
56+
57+
#if (LIBC_MATH & LIBC_MATH_SMALL_TABLES)
58+
#define LIBC_MATH_HAS_SMALL_TABLES
59+
#endif
60+
5161
#endif // LIBC_MATH
5262

5363
#endif // LLVM_LIBC_SRC___SUPPORT_MACROS_OPTIMIZATION_H

libc/src/math/generic/cos.cpp

Lines changed: 51 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,14 @@
1717
#include "src/__support/macros/config.h"
1818
#include "src/__support/macros/optimization.h" // LIBC_UNLIKELY
1919
#include "src/__support/macros/properties/cpu_features.h" // LIBC_TARGET_CPU_HAS_FMA
20+
#include "src/math/generic/range_reduction_double_common.h"
2021
#include "src/math/generic/sincos_eval.h"
2122

22-
// TODO: We might be able to improve the performance of large range reduction of
23-
// non-FMA targets further by operating directly on 25-bit chunks of 128/pi and
24-
// pre-split SIN_K_PI_OVER_128, but that might double the memory footprint of
25-
// those lookup table.
26-
#include "range_reduction_double_common.h"
27-
28-
#if ((LIBC_MATH & LIBC_MATH_SKIP_ACCURATE_PASS) != 0)
29-
#define LIBC_MATH_COS_SKIP_ACCURATE_PASS
30-
#endif
23+
#ifdef LIBC_TARGET_CPU_HAS_FMA
24+
#include "range_reduction_double_fma.h"
25+
#else
26+
#include "range_reduction_double_nofma.h"
27+
#endif // LIBC_TARGET_CPU_HAS_FMA
3128

3229
namespace LIBC_NAMESPACE_DECL {
3330

@@ -42,22 +39,29 @@ LLVM_LIBC_FUNCTION(double, cos, (double x)) {
4239

4340
DoubleDouble y;
4441
unsigned k;
45-
generic::LargeRangeReduction<NO_FMA> range_reduction_large{};
42+
LargeRangeReduction range_reduction_large{};
4643

47-
// |x| < 2^32 (with FMA) or |x| < 2^23 (w/o FMA)
44+
// |x| < 2^16.
4845
if (LIBC_LIKELY(x_e < FPBits::EXP_BIAS + FAST_PASS_EXPONENT)) {
49-
// |x| < 2^-27
50-
if (LIBC_UNLIKELY(x_e < FPBits::EXP_BIAS - 27)) {
51-
// Signed zeros.
52-
if (LIBC_UNLIKELY(x == 0.0))
53-
return 1.0;
54-
55-
// For |x| < 2^-27, |cos(x) - 1| < |x|^2/2 < 2^-54 = ulp(1 - 2^-53)/2.
56-
return fputil::round_result_slightly_down(1.0);
46+
// |x| < 2^-7
47+
if (LIBC_UNLIKELY(x_e < FPBits::EXP_BIAS - 7)) {
48+
// |x| < 2^-27
49+
if (LIBC_UNLIKELY(x_e < FPBits::EXP_BIAS - 27)) {
50+
// Signed zeros.
51+
if (LIBC_UNLIKELY(x == 0.0))
52+
return 1.0;
53+
54+
// For |x| < 2^-27, |cos(x) - 1| < |x|^2/2 < 2^-54 = ulp(1 - 2^-53)/2.
55+
return fputil::round_result_slightly_down(1.0);
56+
}
57+
// No range reduction needed.
58+
k = 0;
59+
y.lo = 0.0;
60+
y.hi = x;
61+
} else {
62+
// Small range reduction.
63+
k = range_reduction_small(x, y);
5764
}
58-
59-
// // Small range reduction.
60-
k = range_reduction_small(x, y);
6165
} else {
6266
// Inf or NaN
6367
if (LIBC_UNLIKELY(x_e > 2 * FPBits::EXP_BIAS)) {
@@ -70,70 +74,51 @@ LLVM_LIBC_FUNCTION(double, cos, (double x)) {
7074
}
7175

7276
// Large range reduction.
73-
k = range_reduction_large.compute_high_part(x);
74-
y = range_reduction_large.fast();
77+
k = range_reduction_large.fast(x, y);
7578
}
7679

7780
DoubleDouble sin_y, cos_y;
7881

79-
generic::sincos_eval(y, sin_y, cos_y);
82+
[[maybe_unused]] double err = generic::sincos_eval(y, sin_y, cos_y);
8083

8184
// Look up sin(k * pi/128) and cos(k * pi/128)
82-
// Memory saving versions:
83-
84-
// Use 128-entry table instead:
85-
// DoubleDouble sin_k = SIN_K_PI_OVER_128[k & 127];
86-
// uint64_t sin_s = static_cast<uint64_t>((k + 128) & 128) << (63 - 7);
87-
// sin_k.hi = FPBits(FPBits(sin_k.hi).uintval() ^ sin_s).get_val();
88-
// sin_k.lo = FPBits(FPBits(sin_k.hi).uintval() ^ sin_s).get_val();
89-
// DoubleDouble cos_k = SIN_K_PI_OVER_128[(k + 64) & 127];
90-
// uint64_t cos_s = static_cast<uint64_t>((k + 64) & 128) << (63 - 7);
91-
// cos_k.hi = FPBits(FPBits(cos_k.hi).uintval() ^ cos_s).get_val();
92-
// cos_k.lo = FPBits(FPBits(cos_k.hi).uintval() ^ cos_s).get_val();
93-
94-
// Use 64-entry table instead:
95-
// auto get_idx_dd = [](unsigned kk) -> DoubleDouble {
96-
// unsigned idx = (kk & 64) ? 64 - (kk & 63) : (kk & 63);
97-
// DoubleDouble ans = SIN_K_PI_OVER_128[idx];
98-
// if (kk & 128) {
99-
// ans.hi = -ans.hi;
100-
// ans.lo = -ans.lo;
101-
// }
102-
// return ans;
103-
// };
104-
// DoubleDouble sin_k = get_idx_dd(k + 128);
105-
// DoubleDouble cos_k = get_idx_dd(k + 64);
106-
85+
#ifdef LIBC_MATH_HAS_SMALL_TABLES
86+
// Memory saving versions. Use 65-entry table.
87+
auto get_idx_dd = [](unsigned kk) -> DoubleDouble {
88+
unsigned idx = (kk & 64) ? 64 - (kk & 63) : (kk & 63);
89+
DoubleDouble ans = SIN_K_PI_OVER_128[idx];
90+
if (kk & 128) {
91+
ans.hi = -ans.hi;
92+
ans.lo = -ans.lo;
93+
}
94+
return ans;
95+
};
96+
DoubleDouble sin_k = get_idx_dd(k + 128);
97+
DoubleDouble cos_k = get_idx_dd(k + 64);
98+
#else
10799
// Fast look up version, but needs 256-entry table.
108100
// -sin(k * pi/128) = sin((k + 128) * pi/128)
109101
// cos(k * pi/128) = sin(k * pi/128 + pi/2) = sin((k + 64) * pi/128).
110102
DoubleDouble msin_k = SIN_K_PI_OVER_128[(k + 128) & 255];
111103
DoubleDouble cos_k = SIN_K_PI_OVER_128[(k + 64) & 255];
104+
#endif // LIBC_MATH_HAS_SMALL_TABLES
112105

113106
// After range reduction, k = round(x * 128 / pi) and y = x - k * (pi / 128).
114107
// So k is an integer and -pi / 256 <= y <= pi / 256.
115108
// Then cos(x) = cos((k * pi/128 + y)
116109
// = cos(y) * cos(k*pi/128) - sin(y) * sin(k*pi/128)
117-
DoubleDouble cos_k_cos_y = fputil::quick_mult<NO_FMA>(cos_y, cos_k);
118-
DoubleDouble msin_k_sin_y = fputil::quick_mult<NO_FMA>(sin_y, msin_k);
110+
DoubleDouble cos_k_cos_y = fputil::quick_mult(cos_y, cos_k);
111+
DoubleDouble msin_k_sin_y = fputil::quick_mult(sin_y, msin_k);
119112

120113
DoubleDouble rr = fputil::exact_add<false>(cos_k_cos_y.hi, msin_k_sin_y.hi);
121114
rr.lo += msin_k_sin_y.lo + cos_k_cos_y.lo;
122115

123-
#ifdef LIBC_MATH_COS_SKIP_ACCURATE_PASS
116+
#ifdef LIBC_MATH_HAS_SKIP_ACCURATE_PASS
124117
return rr.hi + rr.lo;
125118
#else
126119

127-
// Accurate test and pass for correctly rounded implementation.
128-
#ifdef LIBC_TARGET_CPU_HAS_FMA
129-
constexpr double ERR = 0x1.0p-70;
130-
#else
131-
// TODO: Improve non-FMA fast pass accuracy.
132-
constexpr double ERR = 0x1.0p-66;
133-
#endif // LIBC_TARGET_CPU_HAS_FMA
134-
135-
double rlp = rr.lo + ERR;
136-
double rlm = rr.lo - ERR;
120+
double rlp = rr.lo + err;
121+
double rlm = rr.lo - err;
137122

138123
double r_upper = rr.hi + rlp; // (rr.lo + ERR);
139124
double r_lower = rr.hi + rlm; // (rr.lo - ERR);
@@ -144,15 +129,15 @@ LLVM_LIBC_FUNCTION(double, cos, (double x)) {
144129

145130
Float128 u_f128, sin_u, cos_u;
146131
if (LIBC_LIKELY(x_e < FPBits::EXP_BIAS + FAST_PASS_EXPONENT))
147-
u_f128 = generic::range_reduction_small_f128(x);
132+
u_f128 = range_reduction_small_f128(x);
148133
else
149134
u_f128 = range_reduction_large.accurate();
150135

151136
generic::sincos_eval(u_f128, sin_u, cos_u);
152137

153138
auto get_sin_k = [](unsigned kk) -> Float128 {
154139
unsigned idx = (kk & 64) ? 64 - (kk & 63) : (kk & 63);
155-
Float128 ans = generic::SIN_K_PI_OVER_128_F128[idx];
140+
Float128 ans = SIN_K_PI_OVER_128_F128[idx];
156141
if (kk & 128)
157142
ans.sign = Sign::NEG;
158143
return ans;
@@ -172,7 +157,7 @@ LLVM_LIBC_FUNCTION(double, cos, (double x)) {
172157
// https://github.com/llvm/llvm-project/issues/96452.
173158

174159
return static_cast<double>(r);
175-
#endif // !LIBC_MATH_COS_SKIP_ACCURATE_PASS
160+
#endif // !LIBC_MATH_HAS_SKIP_ACCURATE_PASS
176161
}
177162

178163
} // namespace LIBC_NAMESPACE_DECL

libc/src/math/generic/pow.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,7 @@ LLVM_LIBC_FUNCTION(double, pow, (double x, double y)) {
398398
#else
399399
double c = FPBits(m_x.uintval() & 0x3fff'e000'0000'0000).get_val();
400400
dx = fputil::multiply_add(RD[idx_x], m_x.get_val() - c, CD[idx_x]); // Exact
401-
dx_c0 = fputil::exact_mult<true>(COEFFS[0], dx);
401+
dx_c0 = fputil::exact_mult<28>(dx, COEFFS[0]); // Exact
402402
#endif // LIBC_TARGET_CPU_HAS_FMA
403403

404404
double dx2 = dx * dx;

0 commit comments

Comments
 (0)