Compare commits

...
3 Commits
Author SHA1 Message Date
weaselbot a0d64afd6f Fix ARM64 assembly size directive in cpu_work.cpp
The GNU assembler expects `.size symbol, .-symbol`.  The previous
`.size spend_cpu_cycles, spend_cpu_cycles` expression is not a constant
and breaks compilation on AArch64 Linux.  Use the correct form so the
project builds on ARM64.
2026-06-26 11:08:20 -04:00
weaselbot a377772e63 Use AArch64 NEON intrinsics for histogram bucket updates
Replace the scalar ARM fallback in update_histogram_buckets with a NEON
implementation that processes two buckets per iteration, matching the
existing AVX path.  The wrapper now dispatches to the SIMD path on both
x86-64 and AArch64 and falls back to scalar code on other architectures.
2026-06-26 11:08:03 -04:00
weaselbot d7de96ef94 Support building on ARM by providing scalar histogram fallback
The metrics histogram update code unconditionally included <immintrin.h>
and used __attribute__((target("avx"))) SSE/AVX intrinsics, which only
exist on x86-64. This prevented the project from compiling on ARM64.

Guard the x86-64 SIMD implementation and the <immintrin.h> include with
an architecture check, and add a portable scalar fallback for non-x86-64
platforms (e.g., ARM64). A thin wrapper function keeps the call sites
unchanged and preserves the AVX fast path on x86-64.

Closes #3
2026-06-26 10:49:53 -04:00
2 changed files with 61 additions and 5 deletions
+1 -1
View File
@@ -55,7 +55,7 @@ asm(".text\n"
" b.ne .L_loop\n" // Branch back if not zero
".L_end:\n" // End
" ret\n" // Return
".size spend_cpu_cycles, spend_cpu_cycles\n");
".size spend_cpu_cycles, .-spend_cpu_cycles\n");
#endif
#endif
+60 -4
View File
@@ -22,7 +22,11 @@
#include <unordered_set>
#include <vector>
#if defined(__x86_64__) || defined(__amd64__) || defined(_M_X64)
#include <immintrin.h>
#elif defined(__aarch64__)
#include <arm_neon.h>
#endif
#include <simdutf.h>
#include "arena.hpp"
@@ -1398,8 +1402,10 @@ void Gauge::set(double x) {
Histogram::Histogram() = default;
// Vectorized histogram bucket updates with mutex protection for consistency
// AVX-optimized implementation for high performance
// AVX-optimized implementation for high performance on x86-64, NEON-optimized
// implementation on ARM64, and a scalar fallback for other architectures.
#if defined(__x86_64__) || defined(__amd64__) || defined(_M_X64)
__attribute__((target("avx"))) static void
update_histogram_buckets_simd(std::span<const double> thresholds,
std::span<uint64_t> counts, double x,
@@ -1439,6 +1445,56 @@ update_histogram_buckets_simd(std::span<const double> thresholds,
}
}
}
#elif defined(__aarch64__)
static void
update_histogram_buckets_simd(std::span<const double> thresholds,
std::span<uint64_t> counts, double x,
size_t start_idx) {
const size_t size = thresholds.size();
size_t i = start_idx;
// Process 2 buckets at a time with 128-bit NEON vectors
const float64x2_t x_vec = vdupq_n_f64(x);
const uint64x2_t one = vdupq_n_u64(1);
for (; i + 2 <= size; i += 2) {
// Compare x <= thresholds per lane; true lanes are all ones.
float64x2_t thresholds_vec = vld1q_f64(&thresholds[i]);
uint64x2_t cmp_result = vcleq_f64(x_vec, thresholds_vec);
// Convert all-ones/all-zeros masks to per-lane 1/0 increments.
uint64x2_t increments = vandq_u64(cmp_result, one);
// Load current counts, add increments, and store back.
uint64x2_t current_counts = vld1q_u64(&counts[i]);
uint64x2_t updated_counts = vaddq_u64(current_counts, increments);
vst1q_u64(&counts[i], updated_counts);
}
// Handle remainder with scalar operations
for (; i < size; ++i) {
if (x <= thresholds[i]) {
counts[i]++;
}
}
}
#endif
static void update_histogram_buckets(std::span<const double> thresholds,
std::span<uint64_t> counts, double x,
size_t start_idx) {
#if defined(__x86_64__) || defined(__amd64__) || defined(_M_X64) || \
defined(__aarch64__)
update_histogram_buckets_simd(thresholds, counts, x, start_idx);
#else
const size_t size = thresholds.size();
for (size_t i = start_idx; i < size; ++i) {
if (x <= thresholds[i]) {
counts[i]++;
}
}
#endif
}
void Histogram::observe(double x) {
assert(p->thresholds.size() == p->shared.bucket_counts.size());
@@ -1459,15 +1515,15 @@ void Histogram::observe(double x) {
}
// Update shared directly
update_histogram_buckets_simd(p->thresholds, p->shared.bucket_counts, x, 0);
update_histogram_buckets(p->thresholds, p->shared.bucket_counts, x, 0);
p->shared.sum += x;
p->shared.observations++;
p->mutex.unlock();
} else {
// Slow path: accumulate in pending (lock-free)
update_histogram_buckets_simd(p->thresholds, p->pending.bucket_counts, x,
0);
update_histogram_buckets(p->thresholds, p->pending.bucket_counts, x,
0);
p->pending.sum += x;
p->pending.observations++;
}