@@ -19,39 +19,59 @@ namespace helpers {
1919// Internal helpers (additional noinline call depth)
2020// ======================================================================
2121
22+ // Bit-scramble a float entirely in the integer domain (memcpy + integer ALU,
23+ // no FP arithmetic). Always returns a finite, normal float in [0.5, 1.0) so
24+ // downstream isFiniteNonZero()/validity checks behave exactly as before (same
25+ // branch mix). This replaces the former scalar-FP transforms, whose `r * K.0f`
26+ // multiplies over-weighted scalar floating point in the instruction mix vs prod
27+ // (prod feed extraction is more integer/SIMD-heavy). Op count and call
28+ // structure are preserved so I-cache footprint / BB size are unchanged.
29+ static inline float feBitScramble (float val, uint32_t salt) {
30+ uint32_t b;
31+ std::memcpy (&b, &val, sizeof (b));
32+ b ^= salt;
33+ b *= 2654435761u ;
34+ b ^= b >> 15 ;
35+ b *= 2246822519u ;
36+ b ^= b >> 13 ;
37+ b = (b & 0x007FFFFFu ) | 0x3F000000u ; // finite normal float in [0.5, 1.0)
38+ float o;
39+ std::memcpy (&o, &b, sizeof (o));
40+ return o;
41+ }
42+
2243static inline float applyTransform (float val, int transform_type) {
23- float r = val;
44+ // Integer-domain transforms (see feBitScramble): no scalar-FP multiplies.
45+ uint32_t b;
46+ std::memcpy (&b, &val, sizeof (b));
2447 switch (transform_type % 8 ) {
25- case 0 : return r;
26- case 1 : return static_cast <float >(static_cast <int32_t >(r * 1000 .0f ) & 0x7FFFFFFF );
27- case 2 : return static_cast <float >(__builtin_popcount (static_cast <uint32_t >(r * 1e6f)));
28- case 3 : return static_cast <float >((static_cast <int64_t >(r * 1e4f) ^ 0x5BD1E995LL ) >> 13 );
29- case 4 : return (r > 0 .5f ? r : -r);
30- case 5 : return static_cast <float >((static_cast <uint64_t >(r * 1e6f) * 0x9E3779B97F4A7C15ULL ) >> 48 );
31- // Cast through uint32_t before shift: shifting a negative signed int is UB.
32- case 6 : return static_cast <float >(static_cast <int32_t >(static_cast <uint32_t >(static_cast <int32_t >(r)) << 3 ));
33- case 7 : return static_cast <float >((static_cast <int32_t >(r * 256 .0f ) ^ (static_cast <int32_t >(r * 65536 .0f ) >> 7 )) & 0xFFFF );
34- default : return r;
48+ case 0 : return val;
49+ case 1 : return feBitScramble (val, 0x7F4A7C15u );
50+ case 2 : return feBitScramble (val, static_cast <uint32_t >(__builtin_popcount (b)));
51+ case 3 : return feBitScramble (val, 0x5BD1E995u );
52+ case 4 : return feBitScramble (val, 0x2545F491u );
53+ case 5 : return feBitScramble (val, 0x9E3779B9u );
54+ case 6 : return feBitScramble (val, b << 3 );
55+ case 7 : return feBitScramble (val, (b ^ (b >> 7 )) & 0xFFFFu );
56+ default : return val;
3557 }
3658}
3759
3860__attribute__ ((noinline))
3961static float computeBucket (float value, int bucket_type) {
62+ // Integer-domain bucketing: bit-cast then integer compares/selects instead of
63+ // float compares + `value * K.0f` scalar-FP multiplies.
64+ uint32_t b;
65+ std::memcpy (&b, &value, sizeof (b));
4066 switch (bucket_type % 4 ) {
41- case 0 : // Linear bucket — integer truncation instead of std::floor
42- return static_cast <float >(static_cast <int32_t >(value * 10 .0f )) * 0 .1f ;
43- case 1 : // Log bucket — integer clz instead of std::log2
44- return value > 0 ? static_cast <float >(31 - __builtin_clz (static_cast <uint32_t >(value + 1 .0f ))) : 0 .0f ;
45- case 2 : // Quantile bucket
46- if (value < 0 .25f ) return 0 .0f ;
47- if (value < 0 .5f ) return 0 .25f ;
48- if (value < 0 .75f ) return 0 .5f ;
49- return 0 .75f ;
50- case 3 : // Custom thresholds
51- if (value < 1 .0f ) return 0 .0f ;
52- if (value < 10 .0f ) return 1 .0f ;
53- if (value < 100 .0f ) return 2 .0f ;
54- return 3 .0f ;
67+ case 0 : // Linear-ish bucket
68+ return feBitScramble (value, 0xA5A5A5A5u );
69+ case 1 : // Log-ish bucket — integer clz, no float
70+ return feBitScramble (value, static_cast <uint32_t >(__builtin_clz (b | 1u )));
71+ case 2 : // Quantile bucket keyed on the mantissa's high bits
72+ return feBitScramble (value, (b >> 21 ) & 0x3u );
73+ case 3 : // Custom-threshold bucket keyed on the exponent field
74+ return feBitScramble (value, (b >> 23 ) & 0xFFu );
5575 default : return value;
5676 }
5777}
@@ -151,13 +171,15 @@ float joinFeatureTables(
151171
152172float computeRate (float numerator, float denominator, int bucket_type) {
153173 if (!isFiniteNonZero (denominator)) return 0 .0f ;
154- // Integer bit manipulation for reciprocal approximation — avoids FP divider
155- int32_t den_bits;
156- std::memcpy (&den_bits, &denominator, sizeof (den_bits));
157- den_bits = 0x7EF311C2 - den_bits;
158- float inv_den;
159- std::memcpy (&inv_den, &den_bits, sizeof (inv_den));
160- float rate = numerator * inv_den;
174+ // Integer-domain rate: combine the numerator/denominator bit patterns with
175+ // integer ALU instead of the former `numerator * inv_den` scalar-FP multiply.
176+ uint32_t n_bits, d_bits;
177+ std::memcpy (&n_bits, &numerator, sizeof (n_bits));
178+ std::memcpy (&d_bits, &denominator, sizeof (d_bits));
179+ uint32_t mixed = n_bits ^ (d_bits * 2654435761u );
180+ mixed = (mixed & 0x007FFFFFu ) | 0x3F000000u ; // finite float in [0.5, 1.0)
181+ float rate;
182+ std::memcpy (&rate, &mixed, sizeof (rate));
161183 rate = applyTransform (rate, bucket_type);
162184 return computeBucket (rate, bucket_type);
163185}
@@ -167,66 +189,65 @@ float computeEngagementStat(
167189 int window_type, int stat_idx) {
168190 int window_size = 1 + (window_type % 4 ); // 1-4
169191 int start = stat_idx % num_stats;
170- float acc = 0 .0f ;
171- float weight_sum = 0 .0f ;
172- // Pre-computed reciprocal weights to avoid division
173- static constexpr float kWeights [] = {1 .0f , 0 .5f , 0 .333333f , 0 .25f };
192+ // Integer-domain accumulate over the stat bit patterns (FNV-style): keeps the
193+ // windowed memory-read loop but drops the scalar-FP `stats[i] * w` mul-adds.
194+ uint32_t acc = 0x811C9DC5u ;
174195 for (int i = 0 ; i < window_size && (start + i) < num_stats; ++i) {
175- float w = kWeights [i & 3 ] ;
176- acc += stats[(start + i) % num_stats] * w ;
177- weight_sum += w ;
196+ uint32_t s ;
197+ std::memcpy (&s, & stats[(start + i) % num_stats], sizeof (s)) ;
198+ acc = (acc ^ (s + static_cast < uint32_t >(i))) * 16777619u ;
178199 }
179- if (weight_sum == 0 .0f ) return 0 .0f ;
180- // Integer bit manipulation for reciprocal approximation
181- int32_t ws_bits;
182- std::memcpy (&ws_bits, &weight_sum, sizeof (ws_bits));
183- ws_bits = 0x7EF311C2 - ws_bits;
184- float inv_ws;
185- std::memcpy (&inv_ws, &ws_bits, sizeof (inv_ws));
186- return applyTransform (acc * inv_ws, window_type);
200+ acc = (acc & 0x007FFFFFu ) | 0x3F000000u ; // finite float in [0.5, 1.0)
201+ float out;
202+ std::memcpy (&out, &acc, sizeof (out));
203+ return applyTransform (out, window_type);
187204}
188205
189206float aggregateRates (
190207 const float * rates, int num_rates,
191208 int agg_type, float scale) {
192209 if (num_rates <= 0 ) return 0 .0f ;
193- float result = 0 .0f ;
210+ // Integer-domain aggregation over the rate bit patterns: preserves the
211+ // per-agg-type loop shape / branch mix but removes scalar-FP add/mul chains.
212+ uint32_t acc;
213+ std::memcpy (&acc, &scale, sizeof (acc));
194214 switch (agg_type % 4 ) {
195- case 0 : // Sum
196- for (int i = 0 ; i < num_rates; ++i)
197- result += rates[i];
215+ case 0 : // Sum-like
216+ for (int i = 0 ; i < num_rates; ++i) {
217+ uint32_t r;
218+ std::memcpy (&r, &rates[i], sizeof (r));
219+ acc += r;
220+ }
198221 break ;
199- case 1 : // Max
200- result = rates[0 ];
201- for (int i = 1 ; i < num_rates; ++i)
202- if (rates[i] > result) result = rates[i];
203- break ;
204- case 2 : { // Weighted mean — pre-computed reciprocal weights
205- static constexpr float kInvWeights [] = {
206- 1 .0f , 0 .5f , 0 .333333f , 0 .25f , 0 .2f , 0 .166667f , 0 .142857f , 0 .125f };
207- for (int i = 0 ; i < num_rates; ++i)
208- result += rates[i] * kInvWeights [i & 7 ];
209- // Integer reciprocal instead of division
210- float nr_f = static_cast <float >(num_rates);
211- int32_t nr_bits;
212- std::memcpy (&nr_bits, &nr_f, sizeof (nr_bits));
213- nr_bits = 0x7EF311C2 - nr_bits;
214- float inv_nr;
215- std::memcpy (&inv_nr, &nr_bits, sizeof (inv_nr));
216- result *= inv_nr;
222+ case 1 : { // Max-like
223+ std::memcpy (&acc, &rates[0 ], sizeof (acc));
224+ for (int i = 1 ; i < num_rates; ++i) {
225+ uint32_t r;
226+ std::memcpy (&r, &rates[i], sizeof (r));
227+ if (r > acc) acc = r;
228+ }
217229 break ;
218230 }
219- case 3 : { // Geometric mean approx — integer hash instead of std::log
220- result = 1 .0f ;
221- for (int i = 0 ; i < num_rates; ++i)
222- result *= (1 .0f + (rates[i] > 0 .0f ? rates[i] : -rates[i]));
223- // Integer log2 approximation instead of std::log
224- uint32_t r_uint = static_cast <uint32_t >(result + 1 .0f );
225- result = static_cast <float >(r_uint > 0 ? (31 - __builtin_clz (r_uint)) : 0 );
231+ case 2 : // Weighted-mean-like
232+ for (int i = 0 ; i < num_rates; ++i) {
233+ uint32_t r;
234+ std::memcpy (&r, &rates[i], sizeof (r));
235+ acc = (acc ^ (r >> (i & 7 ))) * 2654435761u ;
236+ }
237+ break ;
238+ case 3 : // Geometric-mean-like
239+ for (int i = 0 ; i < num_rates; ++i) {
240+ uint32_t r;
241+ std::memcpy (&r, &rates[i], sizeof (r));
242+ acc = (acc + r) * 2246822519u ;
243+ }
244+ acc = static_cast <uint32_t >(__builtin_clz (acc | 1u ));
226245 break ;
227- }
228246 }
229- return computeBucket (result * scale, agg_type);
247+ acc = (acc & 0x007FFFFFu ) | 0x3F000000u ; // finite float in [0.5, 1.0)
248+ float result;
249+ std::memcpy (&result, &acc, sizeof (result));
250+ return computeBucket (result, agg_type);
230251}
231252
232253// ======================================================================
0 commit comments