Skip to content

Commit e12860f

Browse files
excelle08facebook-github-bot
authored andcommitted
Convert feature-extractor helper math to integer domain (cut scalar-FP)
Reviewed By: charles-typ Differential Revision: D115652573
1 parent b0b35d6 commit e12860f

1 file changed

Lines changed: 99 additions & 78 deletions

File tree

packages/feedsim/third_party/src/workloads/ranking/feature_extractors/generated/extractor_helpers.cpp

Lines changed: 99 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
2243
static 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))
3961
static 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

152172
float 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

189206
float 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

Comments
 (0)