perf(math): scan least significand bit with MSVC x64 intrinsic

This commit is contained in:
KOKO\Mimi
2026-10-14 18:58:00 +09:00
parent 4365f133e1
commit 3576f3f6de
2 changed files with 36 additions and 1 deletions
@@ -163,6 +163,31 @@ trap 관측까지 동등하다고 주장하지 않는다. 원래 분류의 새
[검사 로그](../../.harness/performance/shared-classification/after-checks.txt)를 보존한다.
이 결과는 최종 모델의 시간 목표나 전체 회귀 검사를 대체하지 않는다.
후속 동적 Hex8의 30초 SW raw 자료에서는 `LowestBitExponent`가 51.037592
sampled CPU초로 나타났다. 150초 제한의 개발 실행은 완료되지 않았으며 이 값은
전체 경과 시간이나 개선 배수가 아니다. 공통 산술 담당자는 MSVC 호환 x64 macro
조건에서만 6단계 trailing-bit 검색을 `_BitScanForward64`로 교체했다.
[Microsoft 계약](https://learn.microsoft.com/en-us/cpp/intrinsics/bitscanforward-bitscanforward64?view=msvc-170)의
least-set-bit index를 사용하며 정상 수의 implicit bit와 비영 subnormal fraction이
mask가 0이 아님을 보장한다. Exponent 구성, -1074 exact-tail 거부, FMA와
모든 pair 산술식은 그대로다. Macro 조건 밖에서는 기존 portable 검색을 유지한다.
MSVC 호환 macro를 정의하는 다른 compiler의 실행까지 검증했다고 주장하지 않는다.
[독립 source 수치 검토](../../.harness/performance/shared-bitscan/source-numerical-review.md)는
이 검색값과 downstream gate 보존을 확인했다. 정확한 include dependency를 가진
MSVC Debug에서 unit/integration/CLI consumer를 36개 edge로 재빌드했고,
기존 frozen exponent·pair bit oracle와 통합 Hex8·paired state/kinematics·직렬/TBB
검사 **68/68이 native 0**으로 통과했다.
[실행 기록](../../.harness/performance/shared-bitscan/after-native.json)과
[검사 로그](../../.harness/performance/shared-bitscan/after-checks.txt)를 보존한다.
새 검색의 전체 동적 시간, 최종 reference 및 전체 Debug 판정은 아직 미실행이다.
통합한 Hex8의 ordinary MKL 연산, 원래 누적·진단 순서, private pair의 영항 생략과
선택적 선형 tangent의 지연 준비는 [전용 기록](hex8-performance.md)에 있다.
선택적 준비는 Float64 Create의 필수 검사를 바꾸지 않으며 검증된 현재 trial의
요청에서만 초기화한다. 해당 기록의 1.7391958초는 과거 a08 source의 개발 단일
실행이고 최신 통합 source의 공식 시간 합격으로 소급하지 않는다.
위 검증 중 기존 MSVC/Ninja의 include prefix 인식 오류도 확인했다. 이 PC에는
한국어 compiler resource만 있어 `VSLANG=1033`만 지정한 fresh build도 dependency가
0개였다. 실제 출력 접두사 `참고: 포함 파일: `를 별도 로컬 CMake include로 지정한
+11 -1
View File
@@ -7,6 +7,9 @@
#include <cstring>
#include <initializer_list>
#include <limits>
#if defined(_MSC_VER) && defined(_M_X64)
#include <intrin.h>
#endif
#if defined(_M_X64) || defined(__SSE2__)
#include <xmmintrin.h>
#endif
@@ -114,8 +117,14 @@ inline int LowestBitExponent(double value) {
? -1074
: static_cast<int>(encoded_exponent) - 1075;
if (encoded_exponent != 0U) significand |= 1ULL << 52U;
#if defined(_MSC_VER) && defined(_M_X64)
// The finite, nonzero precondition makes the significand nonzero.
unsigned long trailing = 0;
_BitScanForward64(&trailing, significand);
return exponent + static_cast<int>(trailing);
#else
int trailing = 0;
// Strip large zero groups first without vendor/compiler intrinsics.
// Preserve the portable search outside the supported MSVC x64 path.
for (const int shift : {32, 16, 8, 4, 2, 1}) {
const auto mask = (1ULL << shift) - 1U;
if ((significand & mask) == 0U) {
@@ -124,6 +133,7 @@ inline int LowestBitExponent(double value) {
}
}
return exponent + trailing;
#endif
}
/// @brief Gives a product and its FMA residual when the dyadic tail fits.
/// @note Rejects underflow-risk exact tails, even if the rounded high is finite.