Merge reviewed guarded Hex8 packet implementation and evidence
This commit is contained in:
@@ -11,6 +11,9 @@ MKL 경계 수치 소스는 `71e08169ecfc3eee8372cd831b799b87aa47c78b`에서
|
|||||||
후속 병합 `45aac19`에는 root의 공통 finite classifier `47d2a81`이 포함된다.
|
후속 병합 `45aac19`에는 root의 공통 finite classifier `47d2a81`이 포함된다.
|
||||||
이 병합 뒤의 소비자 재빌드와 동적 개발 실행은 문서 commit `decd959`에서 수행했다.
|
이 병합 뒤의 소비자 재빌드와 동적 개발 실행은 문서 commit `decd959`에서 수행했다.
|
||||||
이전 검사 결과를 새 소스의 통과로 대입하지 않는다.
|
이전 검사 결과를 새 소스의 통과로 대입하지 않는다.
|
||||||
|
후속 `3576f3f`는 root가 공통 최하위 bit 검색을 MSVC x64 intrinsic으로 바꾼
|
||||||
|
소스다. 같은 수치 header hash의 root Debug 검사와 T3 Release 실행을 아래에서
|
||||||
|
구별한다. T3가 공통 산술을 직접 수정하지 않았다.
|
||||||
공식 기준 Release 소스와 이 개발용 Debug 대조 소스를 혼용하지 않는다.
|
공식 기준 Release 소스와 이 개발용 Debug 대조 소스를 혼용하지 않는다.
|
||||||
|
|
||||||
## 실제 병목에 대응하는 변경
|
## 실제 병목에 대응하는 변경
|
||||||
@@ -249,7 +252,245 @@ EvaluateHighLow와 EvaluateSolids로 이어지는 경로가 확인된다. 여러
|
|||||||
성능 비율로 비교하지 않는다. 공통 최하위 bit 검색의 정확한 같은 결과를 유지하면서
|
성능 비율로 비교하지 않는다. 공통 최하위 bit 검색의 정확한 같은 결과를 유지하면서
|
||||||
비용을 줄이는 후보를 root 소유 범위의 후속 검토로 전달했다.
|
비용을 줄이는 후보를 root 소유 범위의 후속 검토로 전달했다.
|
||||||
|
|
||||||
|
## 공통 bit-scan 통합 뒤의 개발 실행
|
||||||
|
|
||||||
|
Root는 같은 비영 significand의 최하위 bit 위치를 `_BitScanForward64`로 찾도록
|
||||||
|
공통 private helper를 바꿨다. Exponent 구성과 exact dyadic-tail 거부 기준은
|
||||||
|
유지했다. [독립 source 검토](../../.harness/performance/shared-bitscan/source-numerical-review.md)는
|
||||||
|
header SHA `F231B9C1B0FBF963EBE1AA88D7FE4C7A291CE91C5CE4E28280AEDEB3155B7A71`의
|
||||||
|
수치·계약 범위 PASS이며 실행 검증과 구별한다. 같은 working candidate hash에서
|
||||||
|
root가 Debug 소비자 36개 edge를 재빌드하고 합성 68개 검사를 통과한
|
||||||
|
[native 기록](../../.harness/performance/shared-bitscan/after-native.json)을 보존한다.
|
||||||
|
그 뒤 root가 `3576f3f`로 commit했으며 T3는 동일 Debug 검사를 반복하지 않았다.
|
||||||
|
|
||||||
|
T3는 `3576f3f`를 동기화하고 기존 정상 dependency Release tree의 소비자 8개
|
||||||
|
edge를 재컴파일했다. Configure/build/deps 모두 native 0이며 Hex8 dependencies
|
||||||
|
15개를 확인했다. [Release 기록](../../.harness/performance/t3/bitscan-release-native.json)에
|
||||||
|
실제 argv와 소스를 남겼다. 새 binary SHA는
|
||||||
|
`54fa853d3f16ef63b0b056d1cdd18bb6b28e6e723ade3caa4b0b6b01b9d1d97e`다.
|
||||||
|
|
||||||
|
이 binary의 원본 동적 입력 개발 실행은 워밍업 없이 한 번만 수행했으며
|
||||||
|
150.1015395초 timeout이었다. Target와 측정 wrapper native는 각각 1,
|
||||||
|
execution_valid는 false이며 최종 HDF5가 없어 101-frame U 비교는 미실행이다.
|
||||||
|
[실행 자료](../../.harness/performance/t3/development-dynamic-bitscan.json),
|
||||||
|
[target 기록](../../.harness/performance/t3/development-dynamic-bitscan/run-1/native.json),
|
||||||
|
[wrapper native](../../.harness/performance/t3/dynamic-bitscan-native.json)를 보존한다.
|
||||||
|
원본/scratch 바이트와 source/header/binary/build/runtime DLL/thread/power identity가
|
||||||
|
전후 같았다. AC 연결 상태 1과 전원 scheme GUID
|
||||||
|
`8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c`를 기록했다. 종료 뒤 관련 프로세스가
|
||||||
|
없음을 확인하고 CPU 권한을 반환했다. 이번 실행 뒤 새 profile은 수집하지 않았다.
|
||||||
|
|
||||||
|
이전과 이번 실행은 모두 제한 시간에 중단되었으므로 두 elapsed로 전체 실행의
|
||||||
|
개선 비율을 계산하지 않는다. Bit-scan의 source/Debug 검증 PASS를 동적 100초
|
||||||
|
목표의 합격으로 확대하지 않는다.
|
||||||
|
|
||||||
|
## Bit-scan 소스의 남은 병목
|
||||||
|
|
||||||
|
같은 binary로 별도 승인된 software sampling 30초를 한 번 수집했다. 새 wrapper는
|
||||||
|
ProcessStartInfo의 stdout/stderr를 비동기로 분리하고 collector의 actual native를
|
||||||
|
즉시 기록했다. Collector native는 4, wrapper는 1이며 이 값을 성공으로 바꾸지
|
||||||
|
않는다. Raw result는 finalize 100%를 완료했고 기존 자료의 함수·전체 callstack·
|
||||||
|
summary export는 각각 native 0, 별도 export wrapper도 0이었다. Target native는
|
||||||
|
독립 포착하지 못해 unknown이다. 관측한 owned descendant PID 5개의 시작 ticks를
|
||||||
|
보존했으며 강제 종료 없이 모두 닫혔다. 관련 프로세스도 0개였다.
|
||||||
|
[실제 명령과 종료](../../.harness/performance/t3/vtune-dynamic-bitscan-native.json)를 보존한다.
|
||||||
|
|
||||||
|
[Summary](../../.harness/performance/t3/vtune-dynamic-bitscan-summary.csv)의 구간 elapsed는
|
||||||
|
29.859555초, CPU 227.129758초, effective 185.250598초, spin 36.140758초,
|
||||||
|
overhead 5.738402초다. 수집·finalization을 포함한 collector 경과 41.2746919초와
|
||||||
|
구별하며 어느 값도 전체 시간 목표 판정으로 사용하지 않는다.
|
||||||
|
[함수](../../.harness/performance/t3/vtune-dynamic-bitscan-functions.csv)와
|
||||||
|
[전체 호출 경로](../../.harness/performance/t3/vtune-dynamic-bitscan-callstacks.csv)는
|
||||||
|
TwoSum·Add·Multiply·MultiplyPair를 거쳐 nonlinear EvaluateHighLow로 이어지는
|
||||||
|
병목이 남음을 보여준다.
|
||||||
|
|
||||||
|
| 함수 | 보고된 self CPU초 | 제한된 core 경로의 inclusive CPU초 |
|
||||||
|
| --- | ---: | ---: |
|
||||||
|
| LowestBitExponent | 11.061455 | 11.061455 |
|
||||||
|
| TwoSum | 33.144893 | 33.144893 |
|
||||||
|
| Add | 6.402455 | 33.202178 |
|
||||||
|
| Multiply | 13.868298 | 51.911387 |
|
||||||
|
| MultiplyPair | 9.711280 | 71.850858 |
|
||||||
|
| AddPair | 4.231358 | 16.787786 |
|
||||||
|
|
||||||
|
Self는 같은 함수 이름의 export 행들을 합한 값이다. Inclusive 열은 각 blank로
|
||||||
|
구분한 stack의 첫 양수 weight 중 다른 arithmetic helper leaf만 더한 제한된
|
||||||
|
집계다. 같은 주소로 표시되는 inline finite/normalized/constructor attribution은
|
||||||
|
합산에서 제외했다. 완전한 inclusive 비용이 아니며 중첩 그룹을 서로 합산하지
|
||||||
|
않는다. [집계 근거](../../.harness/performance/t3/vtune-dynamic-bitscan-path-summary.json)에
|
||||||
|
방법과 leaf별 자료를 남겼다.
|
||||||
|
|
||||||
|
LBE/TwoSum의 독립 주소와 호출 경로는 out-of-line 경계 후보를 뒷받침하지만 실제
|
||||||
|
disassembly는 실행하지 않아 call/spill 수를 확정하지 않았다. HighLow는 16-byte
|
||||||
|
사용자 정의 pair이며 out-of-line 기본 MSVC x64 ABI에서는 간접 인자·반환 저장소가
|
||||||
|
필요하다. [Microsoft ABI 문서](https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention?view=msvc-170)가
|
||||||
|
이 구조체의 호출 경계 근거다. Root가 검토할 좁은 MSVC x64 force-inline 후보는
|
||||||
|
식·guard·flags를 유지하며 실제 codegen과 개발 실행으로 효과를 확인해야 한다.
|
||||||
|
|
||||||
|
AddPair 검증 뒤의 Add와 양 low 비zero MultiplyPair 뒤의 Multiply는 normalized
|
||||||
|
입력 guard를 반복한다. 이는 source로 확인한 중복이며 inline finite attribution을
|
||||||
|
별도 비용으로 더해 절감량을 추정하지 않는다. Guard 제거보다 좁은 inline 후보를
|
||||||
|
먼저 검토하도록 전달했으며 이 단계에서 production을 수정하거나 추가 실행하지 않았다.
|
||||||
|
|
||||||
|
[Identity 자료](../../.harness/performance/t3/vtune-dynamic-bitscan-metadata.json)의 최종
|
||||||
|
before/after는 export 경계를 기록한다. 수집 전 준비 metadata는 export wrapper가
|
||||||
|
덮어썼으므로 독립 보존된 전체 수집 전후 대조로 주장하지 않는다. 최종 binary와
|
||||||
|
header/input hash는 고정된 이전 동적 실행과 같으며 이 한계를 함께 보존한다.
|
||||||
|
|
||||||
|
## 좁은 inline 선언 후보 뒤의 개발 실행
|
||||||
|
|
||||||
|
Root의 `8021dee`는 MSVC 호환 x64에서 TwoSum과 LowestBitExponent의 선언만
|
||||||
|
force-inline으로 바꾼 후보다. T3는 이를 `cb25d744`로 병합했다. 고정 arithmetic
|
||||||
|
header SHA는 `4F360FD4D11FCA68863E9FA7C084ED80E70FC4D903B9DF170AC946D19472987C`다.
|
||||||
|
Root의 [source-only 독립 검토](../../.harness/performance/shared-inline/source-numerical-review.md)와
|
||||||
|
fresh Release 205개 edge 및 같은 68개 consumer
|
||||||
|
[검사 native 0 자료](../../.harness/performance/shared-inline/after-native.json)를 보존한다.
|
||||||
|
T3는 같은 Debug 검사를 반복하지 않았다.
|
||||||
|
|
||||||
|
같은 flags·runtime·thread·power 설정으로 T3 Release 소비자 8개 edge를 재빌드했고
|
||||||
|
configure/build/deps native 0 및 Hex8 dependencies 15개를 확인했다.
|
||||||
|
[재빌드 native](../../.harness/performance/t3/inline-release-native.json)를 보존한다.
|
||||||
|
새 binary SHA는 `1532666241ab623854cdc08648fc5485371881aed9b09e1896068422185032ad`다.
|
||||||
|
|
||||||
|
원본 동적 입력을 워밍업 없이 한 번 실행한 결과는 150.0319488초 timeout이다.
|
||||||
|
Target와 측정 wrapper native는 1, execution_valid는 false이며 최종 HDF5가 없어
|
||||||
|
101-frame U 비교는 미실행이다. [실행](../../.harness/performance/t3/development-dynamic-inline.json)과
|
||||||
|
[wrapper native](../../.harness/performance/t3/dynamic-inline-native.json)를 보존한다.
|
||||||
|
이번에는 타이머 밖의 [before identity](../../.harness/performance/t3/development-dynamic-inline-before.json)와
|
||||||
|
[after identity](../../.harness/performance/t3/development-dynamic-inline-after.json)를
|
||||||
|
각각 별도 파일에 저장했다. Source/input/header/exe/DLL/build/runtime/thread/power
|
||||||
|
identity가 전후 같았고 종료 후 관련 프로세스가 없음을 확인해 CPU 권한을 반환했다.
|
||||||
|
|
||||||
|
새 profile이나 disassembly는 실행하지 않았다. Source의 annotation을 실제 모든
|
||||||
|
호출이 inline되었다는 증거로 확대하지 않는다. 이 후보도 100초 목표 미달이며,
|
||||||
|
제한 시간에 중단된 이전 후보들과의 전체 시간 개선 비율을 계산하지 않는다.
|
||||||
|
|
||||||
|
## Inline 후보의 실제 codegen과 후속 profile
|
||||||
|
|
||||||
|
별도 승인으로 같은 binary의 software sampling 30초를 한 번 수집했다. Collector
|
||||||
|
actual native 4와 outer wrapper 1을 유지한다. 기존 raw 자료만 함수·전체 callstack·
|
||||||
|
summary로 export했고 세 report와 export wrapper는 모두 native 0이다. 이번에는
|
||||||
|
collection과 export 각각의 metadata/native/before/after 파일을 분리했다.
|
||||||
|
[Collection native](../../.harness/performance/t3/vtune-dynamic-inline-collection-native.json),
|
||||||
|
[export native](../../.harness/performance/t3/vtune-dynamic-inline-export-native.json),
|
||||||
|
[collection metadata](../../.harness/performance/t3/vtune-dynamic-inline-collection-metadata.json)를
|
||||||
|
보존한다. Target native는 unknown이며 관련 owned PID는 모두 종료했다.
|
||||||
|
|
||||||
|
Source/input/header/exe/build/DLL/thread/power는 전후 같았다. 환경을 수집한 uv의
|
||||||
|
임시 Python Scripts PATH 첫 entry만 달랐으며 나머지 entry는 같았다. 실제 profiler가
|
||||||
|
상속한 parent PATH를 독립 필드로 저장하지 않았으므로 전체 runtime PATH equality는
|
||||||
|
false라는 한계를 유지한다. [Identity 검사](../../.harness/performance/t3/vtune-dynamic-inline-collection-identity-checks.json)는
|
||||||
|
이 차이를 포함하며 개발 profile을 공식 환경 합격으로 표시하지 않는다.
|
||||||
|
|
||||||
|
[Summary](../../.harness/performance/t3/vtune-dynamic-inline-summary.csv)는 elapsed
|
||||||
|
29.871824초, CPU 222.433211초, effective 173.657543초, spin 43.736681초,
|
||||||
|
overhead 5.038986초다. [함수 자료](../../.harness/performance/t3/vtune-dynamic-inline-functions.csv)의
|
||||||
|
Multiply 12.163382초, TwoProduct 11.910763초, MultiplyPair 9.367977초가 남는다.
|
||||||
|
TwoSum은 여러 inline 위치로 보고되어 다른 finite attribution의 같은 주소와
|
||||||
|
별도 비용으로 합산하지 않는다.
|
||||||
|
|
||||||
|
Hex8 paired TU 하나만 read-only dumpbin symbols/disasm으로 확인했으며 두 명령과
|
||||||
|
wrapper native는 0이다. [실제 codegen 명령](../../.harness/performance/t3/inline-codegen-native.json)과
|
||||||
|
[disassembly](../../.harness/performance/t3/inline-codegen-disasm.txt)를 보존한다.
|
||||||
|
이 TU의 direct call TwoSum/LowestBitExponent는 0개이며 TwoProduct 내부에 BSF와
|
||||||
|
TwoSum 식이 펼쳐진 명령을 확인했다. 독립 COMDAT symbol은 남아 있으므로 전체
|
||||||
|
binary의 모든 호출이 inline되었다고 주장하지 않는다. 이전 TU의 동일한 해시 object를
|
||||||
|
별도로 보존하지 않아 정확한 function-byte 크기 변화는 비교하지 않았다.
|
||||||
|
|
||||||
|
TwoProduct에는 실제 `call fma`가 남는다. Profile의 UCRT fma self는 0.975742초,
|
||||||
|
import thunk는 0.044582초이며 fma를 ancestor로 가진 다른 leaf 경로는 없었다.
|
||||||
|
단독 hardware residual dispatch는 좁지만 현재 주 병목에 대한 영향 근거가 작아
|
||||||
|
우선 후보로 선택하지 않았다. Scalar graph나 guard를 제거하는 대안으로 쓰지 않는다.
|
||||||
|
|
||||||
|
libiomp worker self 37.973383초 중 spin은 32.949078초다. Callstack은
|
||||||
|
OpenMP worker→BaseThreadInitThunk→RtlUserThreadStart만 보인다.
|
||||||
|
[FMA·spin 경로 자료](../../.harness/performance/t3/vtune-dynamic-inline-fma-spin-paths.json)는
|
||||||
|
이를 보존한다. Global PARDISO 뒤 dormant worker인지, paired TBB와 실제 시간상
|
||||||
|
겹친 resource 비용인지는 이 stack만으로 확정하지 못한다. Model을 작은 equation
|
||||||
|
system으로 가정하거나 전역 PARDISO threshold/thread 정책을 바꾸지 않았다.
|
||||||
|
|
||||||
|
`geo*point.weight`의 동일 결과를 diagonal 세 곳에서 재사용하는 작은 후보는 원래
|
||||||
|
식·누적 순서를 유지할 수 있는 별도 검토 사항으로만 남겼고 구현하지 않았다.
|
||||||
|
후속 4-column 특수 pair packet도 아직 구현·검증 전이며 이 profile의 통과 결과로
|
||||||
|
표시하지 않는다.
|
||||||
|
|
||||||
84개 실행과 추가 두 검사 실행은 서로 다른 검사 범위다. 이를 하나의 최종 전체
|
84개 실행과 추가 두 검사 실행은 서로 다른 검사 범위다. 이를 하나의 최종 전체
|
||||||
검사 실행으로 표시하지 않는다. 최종 전체 Debug/CTest, Hex8 필수 원본 5개 비교,
|
검사 실행으로 표시하지 않는다. 최종 전체 Debug/CTest, Hex8 필수 원본 5개 비교,
|
||||||
동적 101-frame 비교, 최종 VTune과 공식 단일 시간 측정은 아직 이 작업의 개발
|
동적 101-frame 비교, 최종 VTune과 공식 단일 시간 측정은 아직 이 작업의 개발
|
||||||
검증으로 완료되지 않았다.
|
검증으로 완료되지 않았다.
|
||||||
|
|
||||||
|
## 특수 pair material tangent의 4열 packet과 ISA 경계
|
||||||
|
|
||||||
|
Nonlinear Hex8의 material tangent 네 열을 독립 lane으로 계산하는 private packet을
|
||||||
|
추가했다. 일반 Float64 제품과 선형 해법은 MKL을 유지하며, packet은 normalized
|
||||||
|
high/low 특수 정밀도 예외에만 사용한다. 각 lane의 세 제품, `(first+second)+third`,
|
||||||
|
weight 제품과 기존 누적 Add 순서는 원래와 같다. Low-zero별 기존 MultiplyPair
|
||||||
|
분기 graph를 유지하며 mixed branch와 특수 입력은 원래 scalar graph로 연결한다.
|
||||||
|
Lane reduction, 대칭 복제, 새 모델 실패 gate는 추가하지 않았다.
|
||||||
|
|
||||||
|
최초 생산 `9b8e53`와 검사 `c0e23503` 후보의 Debug·Release 관련 74개 검사는
|
||||||
|
각각 native 0이었다. 실제 AVX2 선택과 4630 packet oracle, 16 uniform low-zero
|
||||||
|
fixture, 4 overflow lane 위치를 확인했다. 원본 whole-response 9804행도 일치했다.
|
||||||
|
Snapshot wrapper의 native null과 저장층 BOM/추가 newline 실패를 원본 실패 자료로
|
||||||
|
유지하고 별도 exact-stream assertion native 0으로 연결했다.
|
||||||
|
[과거 snapshot native](../../.harness/performance/t3/packet-snapshot-repaired-native.json),
|
||||||
|
[과거 exact assertion](../../.harness/performance/t3/packet-snapshot-comparison-native.json)은
|
||||||
|
새 소스의 통과로 소급하지 않는다.
|
||||||
|
|
||||||
|
최초 Release codegen에서 실제 ANDN과 AVX2 TU가 공통 inline math 실행 COMDAT를
|
||||||
|
방출하는 경계 문제를 발견해 해당 후보의 동적 전체 실행을 시작하지 않았다.
|
||||||
|
[MSVC /arch 문서](https://learn.microsoft.com/en-us/cpp/build/reference/arch-x64?view=msvc-170)는
|
||||||
|
AVX2 설정에서 BMI 등의 비벡터 명령이 생성될 수 있음을 설명한다. 지원 gate는
|
||||||
|
FMA·XSAVE·OSXSAVE·AVX·AVX2·OS XCR0와 BMI1/BMI2를 요구하고, 지원하지 않으면
|
||||||
|
기존 scalar backend를 선택한다.
|
||||||
|
|
||||||
|
보강 후보 `446d3fa059fce8141ed30d985dc8a4fa7e26a719`는 private header를 plain
|
||||||
|
C-array POD로 제한하고 공통 math/HighLow/std::array 정의를 AVX2 TU에서 제거했다.
|
||||||
|
기본 ISA out-of-line noinline 연결 함수가 checked Add/MultiplyPair/TwoProduct를
|
||||||
|
lane 0..3 순서로 호출한다. Raw high/low는 field별로 보존한다. AVX2 내부 finite·
|
||||||
|
normalized·least-bit exponent 판정은 원래 body와 guard 순서를 유지한다.
|
||||||
|
POD는 trivial carrier이며 public API·공통 math·CMake·FP flags는 이 보강에서 불변이다.
|
||||||
|
[고정 manifest](../../.harness/performance/t3/packet-boundary-446d3fa/manifest.json)와
|
||||||
|
[독립 source 검토](../../.harness/performance/t3/source-numerical-review-packet-boundary.md)를 보존한다.
|
||||||
|
Source-only PASS는 native 실행이나 ISA 검증을 대체하지 않는다.
|
||||||
|
|
||||||
|
Corrected-prefix Debug·Release build는 각각 8개 edge와 native 0이었다.
|
||||||
|
실제 discovery의 관련 76개 검사는 Debug 10.18초, Release 4.24초로 각각 전부
|
||||||
|
native 0이었다. 새 synthetic capability·frozen scalar bridge raw-bit 검사와 기존
|
||||||
|
Hex8·paired·HighLowArithmetic 검사를 포함한다. 두 binary 모두 실제 AVX2 evaluator를
|
||||||
|
선택했고 packet oracle 4630개·uniform fixture 16개·overflow 위치 4개를 확인했다.
|
||||||
|
[Debug native](../../.harness/performance/t3/packet-boundary-debug-native.json),
|
||||||
|
[Release native](../../.harness/performance/t3/packet-boundary-release-native.json),
|
||||||
|
[Debug backend](../../.harness/performance/t3/packet-boundary-debug-backend.json),
|
||||||
|
[Release backend](../../.harness/performance/t3/packet-boundary-release-backend.json)을 보존한다.
|
||||||
|
이는 내부 helper telemetry가 아니라 실제 backend 선택과 source/codegen에 연결한
|
||||||
|
구체 fixture 검사 범위다.
|
||||||
|
|
||||||
|
두 구성의 전체 symbols/disassembly 명령은 모두 native 0이었다. AVX2 객체의
|
||||||
|
정의된 external 실행 심볼은 전용 EvaluateMaterialPacketAvx2 하나이며 공통 math·
|
||||||
|
HighLow 생성자·std::array 실행 정의는 없었다. Scalar bridge는 UNDEF 외부 참조이며
|
||||||
|
상수 data와 CRT 외부 참조를 실행 정의와 구분했다. Baseline dispatch의 AVX/YMM/ANDN
|
||||||
|
명령은 0개이고 kernel fused instruction은 명시 Product residual VFMSUB 하나다.
|
||||||
|
일반 mul/add contraction은 확인되지 않았다.
|
||||||
|
[경계 assertion](../../.harness/performance/t3/packet-boundary-codegen-assertion.json)은 전체
|
||||||
|
symbol 목록과 disassembly를 연결한다. 기존 precise와 compiler flags는 유지했다.
|
||||||
|
|
||||||
|
새 Debug LIB와 정합한 현재 header로 candidate snapshot을 compile/run한 native는
|
||||||
|
각각 0이다. 성공 native 0과 original header/LIB/probe identity가 연결된 기존 old
|
||||||
|
oracle를 재사용했으며 old 실행은 반복하지 않았다. ProcessStartInfo stdout을 처음부터
|
||||||
|
BOM이나 추가 newline 없이 저장했다. 새 candidate와 원본의 raw response 9804행은
|
||||||
|
바이트 단위로 같고 SHA는 둘 다 `DED8D2C380D15E297A73BDD018B1B840C1ADC0D8372934D9FC2EF2B628A36F63`이다.
|
||||||
|
비교 assertion wrapper native 0을 [새 snapshot native](../../.harness/performance/t3/packet-boundary-snapshot-native.json)와
|
||||||
|
[새 비교](../../.harness/performance/t3/packet-boundary-snapshot-comparison.json)에 연결했다.
|
||||||
|
|
||||||
|
같은 고정 Release binary로 원본 동적 입력을 워밍업 없이 한 번 실행했다.
|
||||||
|
150.0500624초에 제한 시간으로 중단됐고 target/측정 wrapper native는 1이다.
|
||||||
|
Execution_valid는 false이고 최종 HDF5가 없어 101-frame U 비교는 미실행이다.
|
||||||
|
100초 목표는 미달이다. [개발 실행](../../.harness/performance/t3/development-dynamic-packet-boundary.json),
|
||||||
|
[wrapper native](../../.harness/performance/t3/dynamic-packet-boundary-native.json),
|
||||||
|
[before identity](../../.harness/performance/t3/development-dynamic-packet-boundary-before.json),
|
||||||
|
[after identity](../../.harness/performance/t3/development-dynamic-packet-boundary-after.json)를 보존한다.
|
||||||
|
Source/input/header/exe/DLL/build/runtime/thread/power identity가 전후 같았으며 원본과
|
||||||
|
scratch SHA도 같았다. 종료 후 관련 프로세스 0개를 확인하고 CPU 권한을 반환했다.
|
||||||
|
추가 profile/micro 또는 전체 시간 실행은 하지 않았다. 제한 시간에 중단된 후보끼리의
|
||||||
|
개선 비율을 계산하지 않는다. 최종 전체 검사·필수 원본 비교·공식 시간 목표는 미완료다.
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ add_library(
|
|||||||
elements/element_factory.cpp
|
elements/element_factory.cpp
|
||||||
elements/hex8_element.cpp
|
elements/hex8_element.cpp
|
||||||
elements/hex8_high_low.cpp
|
elements/hex8_high_low.cpp
|
||||||
|
elements/hex8_pair_simd_dispatch.cpp
|
||||||
|
elements/hex8_pair_simd_avx2.cpp
|
||||||
elements/hex8_geometry.cpp
|
elements/hex8_geometry.cpp
|
||||||
elements/euler_beam_3d.cpp
|
elements/euler_beam_3d.cpp
|
||||||
elements/finite_rotation.cpp
|
elements/finite_rotation.cpp
|
||||||
@@ -122,6 +124,14 @@ target_compile_options(
|
|||||||
$<$<CXX_COMPILER_ID:MSVC>:/W4 /WX /EHsc>
|
$<$<CXX_COMPILER_ID:MSVC>:/W4 /WX /EHsc>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Keep the CPU/OS dispatch on the baseline ISA; only its private kernel uses AVX2.
|
||||||
|
if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" AND
|
||||||
|
CMAKE_CXX_COMPILER_ARCHITECTURE_ID STREQUAL "x64")
|
||||||
|
target_compile_definitions(fesa_solver PRIVATE FESA_HEX8_PAIR_AVX2=1)
|
||||||
|
set_source_files_properties(elements/hex8_pair_simd_avx2.cpp
|
||||||
|
PROPERTIES COMPILE_OPTIONS /arch:AVX2)
|
||||||
|
endif()
|
||||||
|
|
||||||
add_executable(fesa_cli app/main.cpp)
|
add_executable(fesa_cli app/main.cpp)
|
||||||
set_target_properties(fesa_cli PROPERTIES OUTPUT_NAME fesa)
|
set_target_properties(fesa_cli PROPERTIES OUTPUT_NAME fesa)
|
||||||
target_link_libraries(fesa_cli PRIVATE fesa_solver)
|
target_link_libraries(fesa_cli PRIVATE fesa_solver)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include "hex8_geometry.h"
|
#include "hex8_geometry.h"
|
||||||
#include "hex8_linear_tangent_cache.h"
|
#include "hex8_linear_tangent_cache.h"
|
||||||
#include "hex8_pair_arithmetic.h"
|
#include "hex8_pair_arithmetic.h"
|
||||||
|
#include "hex8_pair_simd.h"
|
||||||
|
|
||||||
namespace fesa {
|
namespace fesa {
|
||||||
namespace {
|
namespace {
|
||||||
@@ -163,6 +164,11 @@ Result<Hex8HighLowResponse> Hex8Element::EvaluateHighLow(
|
|||||||
return Result<Hex8HighLowResponse>::Failure(
|
return Result<Hex8HighLowResponse>::Failure(
|
||||||
Failure("Hex8 high+low constitutive coefficients are unsupported."));
|
Failure("Hex8 high+low constitutive coefficients are unsupported."));
|
||||||
energy = HighLow{0.5} * HighLow{kappa_} * volume * mean_trace * mean_trace;
|
energy = HighLow{0.5} * HighLow{kappa_} * volume * mean_trace * mean_trace;
|
||||||
|
const auto packet_evaluator = nonlinear
|
||||||
|
? hex8_internal::SelectMaterialPacketEvaluator()
|
||||||
|
: hex8_internal::EvaluateMaterialPacketScalar;
|
||||||
|
const bool use_packets =
|
||||||
|
packet_evaluator != hex8_internal::EvaluateMaterialPacketScalar;
|
||||||
for (std::size_t g = 0; g < 8; ++g) {
|
for (std::size_t g = 0; g < 8; ++g) {
|
||||||
const auto& point = points_[g];
|
const auto& point = points_[g];
|
||||||
const auto& gradients = point_gradients[g];
|
const auto& gradients = point_gradients[g];
|
||||||
@@ -238,9 +244,11 @@ Result<Hex8HighLowResponse> Hex8Element::EvaluateHighLow(
|
|||||||
for (std::size_t j = 0; j < 3; ++j)
|
for (std::size_t j = 0; j < 3; ++j)
|
||||||
for (std::size_t l = 0; l < 3; ++l)
|
for (std::size_t l = 0; l < 3; ++l)
|
||||||
gradient_stress[a][j][l] = gradients[a][j] * s[l][j];
|
gradient_stress[a][j][l] = gradients[a][j] * s[l][j];
|
||||||
for (std::size_t a = 0; a < 8; ++a)
|
for (std::size_t a = 0; a < 8; ++a) {
|
||||||
|
std::array<HighLow, 8> dots{};
|
||||||
for (std::size_t b = 0; b < 8; ++b) {
|
for (std::size_t b = 0; b < 8; ++b) {
|
||||||
const auto dot = Dot(gradients[a], gradients[b]);
|
const auto dot = Dot(gradients[a], gradients[b]);
|
||||||
|
dots[b] = dot;
|
||||||
HighLow geo;
|
HighLow geo;
|
||||||
if (nonlinear)
|
if (nonlinear)
|
||||||
for (std::size_t j = 0; j < 3; ++j)
|
for (std::size_t j = 0; j < 3; ++j)
|
||||||
@@ -250,14 +258,44 @@ Result<Hex8HighLowResponse> Hex8Element::EvaluateHighLow(
|
|||||||
for (std::size_t k = 0; k < 3; ++k) {
|
for (std::size_t k = 0; k < 3; ++k) {
|
||||||
const auto row = 3U * a + i;
|
const auto row = 3U * a + i;
|
||||||
const auto column = 3U * b + k;
|
const auto column = 3U * b + k;
|
||||||
const auto mat = deviatoric_fg[a][i] * fg[b][k] +
|
if (!use_packets) {
|
||||||
shear_fg[b][i] * fg[a][k] +
|
const auto mat = deviatoric_fg[a][i] * fg[b][k] +
|
||||||
shear_fft[i][k] * dot;
|
shear_fg[b][i] * fg[a][k] +
|
||||||
material[row][column] += mat * HighLow{point.weight};
|
shear_fft[i][k] * dot;
|
||||||
|
material[row][column] += mat * HighLow{point.weight};
|
||||||
|
}
|
||||||
if (i == k)
|
if (i == k)
|
||||||
geometric[row][column] += geo * HighLow{point.weight};
|
geometric[row][column] += geo * HighLow{point.weight};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (use_packets)
|
||||||
|
for (std::size_t i = 0; i < 3; ++i)
|
||||||
|
for (std::size_t first = 0; first < 24; first += 4) {
|
||||||
|
hex8_internal::MaterialPacketInput input{};
|
||||||
|
const auto raw_pair = [](HighLow pair) {
|
||||||
|
return hex8_internal::PacketPair{pair.high, pair.low};
|
||||||
|
};
|
||||||
|
input.weight = raw_pair(HighLow{point.weight});
|
||||||
|
const auto row = 3U * a + i;
|
||||||
|
for (std::size_t lane = 0; lane < 4; ++lane) {
|
||||||
|
const auto column = first + lane;
|
||||||
|
const auto b = column / 3U;
|
||||||
|
const auto k = column % 3U;
|
||||||
|
input.deviatoric_left.values[lane] = raw_pair(deviatoric_fg[a][i]);
|
||||||
|
input.deviatoric_right.values[lane] = raw_pair(fg[b][k]);
|
||||||
|
input.shear_left.values[lane] = raw_pair(shear_fg[b][i]);
|
||||||
|
input.shear_right.values[lane] = raw_pair(fg[a][k]);
|
||||||
|
input.tensor_left.values[lane] = raw_pair(shear_fft[i][k]);
|
||||||
|
input.gradient_dot.values[lane] = raw_pair(dots[b]);
|
||||||
|
input.accumulated.values[lane] = raw_pair(material[row][column]);
|
||||||
|
}
|
||||||
|
hex8_internal::PairPacket output;
|
||||||
|
packet_evaluator(input, &output);
|
||||||
|
for (std::size_t lane = 0; lane < 4; ++lane)
|
||||||
|
material[row][first + lane] =
|
||||||
|
{output.values[lane].high, output.values[lane].low};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (nonlinear || !paired_linear_tangent_->ready) {
|
if (nonlinear || !paired_linear_tangent_->ready) {
|
||||||
std::array<Triple, 8> bulk_fg{};
|
std::array<Triple, 8> bulk_fg{};
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
#ifndef FESA_SRC_FESA_ELEMENTS_HEX8_PAIR_SIMD_H_
|
||||||
|
#define FESA_SRC_FESA_ELEMENTS_HEX8_PAIR_SIMD_H_
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace fesa::hex8_internal {
|
||||||
|
/// @brief Trivial raw carrier; neither component is normalized or rounded here.
|
||||||
|
struct PacketPair {
|
||||||
|
double high;
|
||||||
|
double low;
|
||||||
|
};
|
||||||
|
/// @brief Four independent lanes with no executable constructor or operator.
|
||||||
|
struct PairPacket {
|
||||||
|
PacketPair values[4];
|
||||||
|
};
|
||||||
|
struct MaterialPacketInput {
|
||||||
|
PairPacket deviatoric_left;
|
||||||
|
PairPacket deviatoric_right;
|
||||||
|
PairPacket shear_left;
|
||||||
|
PairPacket shear_right;
|
||||||
|
PairPacket tensor_left;
|
||||||
|
PairPacket gradient_dot;
|
||||||
|
PairPacket accumulated;
|
||||||
|
PacketPair weight;
|
||||||
|
};
|
||||||
|
using MaterialPacketEvaluator = void (*)(const MaterialPacketInput&, PairPacket*);
|
||||||
|
|
||||||
|
/// @brief Evaluates each lane's original checked material graph in order 0..3.
|
||||||
|
/// @note Invalid arithmetic remains a raw invalid pair for the caller's gates.
|
||||||
|
void EvaluateMaterialPacketScalar(const MaterialPacketInput& input,
|
||||||
|
PairPacket* output);
|
||||||
|
/// @brief Evaluates independent material lanes with the checked vector graph.
|
||||||
|
/// @pre The selector has admitted the CPU/OS ISA and current masked FP state.
|
||||||
|
/// @note No lane reduction or model failure is introduced; special packets bridge
|
||||||
|
/// to original scalar arithmetic, preserving components and operation order.
|
||||||
|
void EvaluateMaterialPacketAvx2(const MaterialPacketInput& input,
|
||||||
|
PairPacket* output);
|
||||||
|
/// @brief Baseline-ISA checked addition bridge in lane order 0..3.
|
||||||
|
/// @note pair_shortcuts chooses the original local AddPair or general Add graph;
|
||||||
|
/// invalid pairs are preserved, with no FP-state mutation or normalization here.
|
||||||
|
void ScalarPacketAdd(const PairPacket& left, const PairPacket& right,
|
||||||
|
bool pair_shortcuts, PairPacket* output);
|
||||||
|
/// @brief Baseline-ISA original checked MultiplyPair bridge in lane order 0..3.
|
||||||
|
/// @note Raw invalid outputs remain invalid; no component is rounded away.
|
||||||
|
void ScalarPacketMultiply(const PairPacket& left, const PairPacket& right,
|
||||||
|
PairPacket* output);
|
||||||
|
/// @brief Baseline-ISA original checked TwoProduct bridge in lane order 0..3.
|
||||||
|
/// @note Keeps finite/zero/underflow/FMA checks and the caller's FP state.
|
||||||
|
void ScalarPacketTwoProduct(const double left[4], const double right[4],
|
||||||
|
PairPacket* output);
|
||||||
|
/// @brief Selects a private backend from CPU/OS and this thread's FP state.
|
||||||
|
/// @note Never mutates FP state or caches a thread's environment decision.
|
||||||
|
MaterialPacketEvaluator SelectMaterialPacketEvaluator();
|
||||||
|
/// @brief Pure instruction/OS-state predicate; does not execute ISA probes.
|
||||||
|
bool SupportsMaterialPacketInstructions(std::uint32_t leaf1_ecx,
|
||||||
|
std::uint64_t xcr0,
|
||||||
|
std::uint32_t leaf7_ebx);
|
||||||
|
} // namespace fesa::hex8_internal
|
||||||
|
|
||||||
|
#endif // FESA_SRC_FESA_ELEMENTS_HEX8_PAIR_SIMD_H_
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
#include "hex8_pair_simd.h"
|
||||||
|
|
||||||
|
#if defined(FESA_HEX8_PAIR_AVX2) && defined(_MSC_VER) && defined(_M_X64)
|
||||||
|
#include <cstring>
|
||||||
|
#include <immintrin.h>
|
||||||
|
#include <intrin.h>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
namespace fesa::hex8_internal {
|
||||||
|
namespace {
|
||||||
|
struct VectorPair {
|
||||||
|
__m256d high;
|
||||||
|
__m256d low;
|
||||||
|
};
|
||||||
|
|
||||||
|
// TU-local predicates mirror the checked scalar premises without publishing
|
||||||
|
// AVX2-compiled common math or HighLow COMDAT definitions to baseline callers.
|
||||||
|
/// @brief Reads binary64 fields without FP classification or comparison.
|
||||||
|
std::uint64_t ScalarBits(double value) {
|
||||||
|
static_assert(sizeof(double) == sizeof(std::uint64_t) &&
|
||||||
|
std::numeric_limits<double>::is_iec559 &&
|
||||||
|
std::numeric_limits<double>::digits == 53);
|
||||||
|
std::uint64_t bits = 0;
|
||||||
|
std::memcpy(&bits, &value, sizeof(bits));
|
||||||
|
return bits;
|
||||||
|
}
|
||||||
|
/// @brief Bitwise finite gate before any scalar FP comparison.
|
||||||
|
bool FiniteScalar(double value) {
|
||||||
|
return (ScalarBits(value) & 0x7ff0000000000000ULL) !=
|
||||||
|
0x7ff0000000000000ULL;
|
||||||
|
}
|
||||||
|
/// @brief Preserves finite, canonical-zero and RN64 normalization gates.
|
||||||
|
bool NormalizedScalar(PacketPair value) {
|
||||||
|
if (!FiniteScalar(value.high) || !FiniteScalar(value.low)) return false;
|
||||||
|
if (value.high == 0 && value.low == 0)
|
||||||
|
return (ScalarBits(value.high) >> 63U) == 0 &&
|
||||||
|
(ScalarBits(value.low) >> 63U) == 0;
|
||||||
|
return value.high + value.low == value.high;
|
||||||
|
}
|
||||||
|
/// @brief Exact least-bit exponent; caller has checked finite/nonzero first.
|
||||||
|
int LowestBitExponentScalar(double value) {
|
||||||
|
const auto bits = ScalarBits(value);
|
||||||
|
const auto encoded_exponent = (bits >> 52U) & 0x7ffU;
|
||||||
|
auto significand = bits & 0xfffffffffffffULL;
|
||||||
|
const int exponent = encoded_exponent == 0U
|
||||||
|
? -1074
|
||||||
|
: static_cast<int>(encoded_exponent) - 1075;
|
||||||
|
if (encoded_exponent != 0U) significand |= 1ULL << 52U;
|
||||||
|
unsigned long trailing = 0;
|
||||||
|
_BitScanForward64(&trailing, significand);
|
||||||
|
return exponent + static_cast<int>(trailing);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Selects each lane without evaluating additional FP arithmetic.
|
||||||
|
__m256d Select(__m256d mask, __m256d selected, __m256d other) {
|
||||||
|
return _mm256_blendv_pd(other, selected, mask);
|
||||||
|
}
|
||||||
|
/// @brief Produces independent finite masks without signaling NaN compares.
|
||||||
|
__m256d Finite(__m256d value) {
|
||||||
|
const auto exponent = _mm256_set1_epi64x(0x7ff0000000000000LL);
|
||||||
|
const auto nonfinite = _mm256_cmpeq_epi64(
|
||||||
|
_mm256_and_si256(_mm256_castpd_si256(value), exponent), exponent);
|
||||||
|
return _mm256_castsi256_pd(_mm256_xor_si256(
|
||||||
|
nonfinite, _mm256_set1_epi64x(-1)));
|
||||||
|
}
|
||||||
|
/// @brief Recognizes both signed zero encodings using integer fields.
|
||||||
|
__m256d Zero(__m256d value) {
|
||||||
|
return _mm256_castsi256_pd(_mm256_cmpeq_epi64(
|
||||||
|
_mm256_and_si256(_mm256_castpd_si256(value),
|
||||||
|
_mm256_set1_epi64x(0x7fffffffffffffffLL)),
|
||||||
|
_mm256_setzero_si256()));
|
||||||
|
}
|
||||||
|
/// @brief Preserves valid lanes and encodes invalid lanes as quiet NaN/+0.
|
||||||
|
VectorPair InvalidOutside(VectorPair value, __m256d valid) {
|
||||||
|
return {Select(valid, value.high, _mm256_castsi256_pd(
|
||||||
|
_mm256_set1_epi64x(0x7ff8000000000000LL))),
|
||||||
|
Select(valid, value.low, _mm256_setzero_pd())};
|
||||||
|
}
|
||||||
|
/// @brief Loads raw high/low fields in lane order 0..3, with no normalization.
|
||||||
|
VectorPair Load(const PairPacket& value) {
|
||||||
|
return {_mm256_setr_pd(value.values[0].high, value.values[1].high,
|
||||||
|
value.values[2].high, value.values[3].high),
|
||||||
|
_mm256_setr_pd(value.values[0].low, value.values[1].low,
|
||||||
|
value.values[2].low, value.values[3].low)};
|
||||||
|
}
|
||||||
|
/// @brief Stores raw high/low fields without rounding or lane reduction.
|
||||||
|
PairPacket Store(VectorPair value) {
|
||||||
|
double high[4];
|
||||||
|
double low[4];
|
||||||
|
_mm256_storeu_pd(high, value.high);
|
||||||
|
_mm256_storeu_pd(low, value.low);
|
||||||
|
return {{{high[0], low[0]}, {high[1], low[1]},
|
||||||
|
{high[2], low[2]}, {high[3], low[3]}}};
|
||||||
|
}
|
||||||
|
/// @brief Retains the original per-lane subtraction/addition association and gates.
|
||||||
|
VectorPair TwoSum(__m256d left, __m256d right) {
|
||||||
|
const auto sum = _mm256_add_pd(left, right);
|
||||||
|
const auto bridge = _mm256_sub_pd(sum, left);
|
||||||
|
const auto restored = _mm256_sub_pd(sum, bridge);
|
||||||
|
const auto error = _mm256_add_pd(_mm256_sub_pd(left, restored),
|
||||||
|
_mm256_sub_pd(right, bridge));
|
||||||
|
const auto valid = _mm256_and_pd(_mm256_and_pd(Finite(sum), Finite(bridge)),
|
||||||
|
_mm256_and_pd(Finite(restored), Finite(error)));
|
||||||
|
const auto exact_zero = _mm256_and_pd(Zero(sum), Zero(error));
|
||||||
|
return InvalidOutside({Select(exact_zero, _mm256_setzero_pd(), sum),
|
||||||
|
Select(exact_zero, _mm256_setzero_pd(), error)}, valid);
|
||||||
|
}
|
||||||
|
/// @brief Routes exceptional packets to baseline checked Add in lane order.
|
||||||
|
VectorPair ScalarAdd(VectorPair left, VectorPair right, bool pair_shortcuts) {
|
||||||
|
const auto a = Store(left);
|
||||||
|
const auto b = Store(right);
|
||||||
|
PairPacket result;
|
||||||
|
ScalarPacketAdd(a, b, pair_shortcuts, &result);
|
||||||
|
return Load(result);
|
||||||
|
}
|
||||||
|
/// @brief Routes exceptional packets to baseline checked MultiplyPair.
|
||||||
|
VectorPair ScalarMultiply(const PairPacket& left, const PairPacket& right) {
|
||||||
|
PairPacket result;
|
||||||
|
ScalarPacketMultiply(left, right, &result);
|
||||||
|
return Load(result);
|
||||||
|
}
|
||||||
|
/// @brief Retains normalized/zero branches and the checked per-lane Add graph.
|
||||||
|
VectorPair Add(VectorPair left, VectorPair right, bool pair_shortcuts) {
|
||||||
|
const auto a = Store(left);
|
||||||
|
const auto b = Store(right);
|
||||||
|
int left_zero = 0;
|
||||||
|
int right_zero = 0;
|
||||||
|
for (std::size_t lane = 0; lane < 4; ++lane) {
|
||||||
|
if (!NormalizedScalar(a.values[lane]) ||
|
||||||
|
!NormalizedScalar(b.values[lane]))
|
||||||
|
return ScalarAdd(left, right, pair_shortcuts);
|
||||||
|
if (a.values[lane].high == 0 && a.values[lane].low == 0) left_zero |= 1 << lane;
|
||||||
|
if (b.values[lane].high == 0 && b.values[lane].low == 0) right_zero |= 1 << lane;
|
||||||
|
}
|
||||||
|
if (pair_shortcuts) {
|
||||||
|
if (left_zero == 15)
|
||||||
|
return {right.high, Select(Zero(right.low), _mm256_setzero_pd(), right.low)};
|
||||||
|
if (right_zero == 15)
|
||||||
|
return {left.high, Select(Zero(left.low), _mm256_setzero_pd(), left.low)};
|
||||||
|
if (left_zero != 0 || right_zero != 0)
|
||||||
|
return ScalarAdd(left, right, true);
|
||||||
|
}
|
||||||
|
const auto head = TwoSum(left.high, right.high);
|
||||||
|
const auto tail = TwoSum(left.low, right.low);
|
||||||
|
const auto middle = TwoSum(head.low, tail.high);
|
||||||
|
const auto joined = TwoSum(head.high, middle.high);
|
||||||
|
const auto remainder = _mm256_add_pd(
|
||||||
|
_mm256_add_pd(joined.low, middle.low), tail.low);
|
||||||
|
const auto valid = _mm256_and_pd(Finite(remainder), _mm256_and_pd(
|
||||||
|
_mm256_and_pd(Finite(head.high), Finite(head.low)), _mm256_and_pd(
|
||||||
|
_mm256_and_pd(Finite(tail.high), Finite(tail.low)), _mm256_and_pd(
|
||||||
|
_mm256_and_pd(Finite(middle.high), Finite(middle.low)),
|
||||||
|
_mm256_and_pd(Finite(joined.high), Finite(joined.low))))));
|
||||||
|
// Original Add returns before its last TwoSum when a prior finite gate fails.
|
||||||
|
return InvalidOutside(TwoSum(
|
||||||
|
Select(valid, joined.high, _mm256_setzero_pd()),
|
||||||
|
Select(valid, remainder, _mm256_setzero_pd())), valid);
|
||||||
|
}
|
||||||
|
/// @brief Checks finite/zero/tail premises before products; neutralizes rejected
|
||||||
|
/// lanes before the explicit residual FMA and final TwoSum.
|
||||||
|
VectorPair Product(__m256d left, __m256d right) {
|
||||||
|
double a[4];
|
||||||
|
double b[4];
|
||||||
|
_mm256_storeu_pd(a, left);
|
||||||
|
_mm256_storeu_pd(b, right);
|
||||||
|
bool special = false;
|
||||||
|
for (std::size_t lane = 0; lane < 4; ++lane)
|
||||||
|
if (!FiniteScalar(a[lane]) ||
|
||||||
|
!FiniteScalar(b[lane]) ||
|
||||||
|
a[lane] == 0 || b[lane] == 0 ||
|
||||||
|
LowestBitExponentScalar(a[lane]) +
|
||||||
|
LowestBitExponentScalar(b[lane]) < -1074)
|
||||||
|
special = true;
|
||||||
|
if (special) {
|
||||||
|
PairPacket result;
|
||||||
|
ScalarPacketTwoProduct(a, b, &result);
|
||||||
|
return Load(result);
|
||||||
|
}
|
||||||
|
const auto product = _mm256_mul_pd(left, right);
|
||||||
|
const auto product_valid = Finite(product);
|
||||||
|
const auto one = _mm256_set1_pd(1.0);
|
||||||
|
// Invalid products never reach the residual FMA in the original helper.
|
||||||
|
const auto error = _mm256_fmsub_pd(Select(product_valid, left, one),
|
||||||
|
Select(product_valid, right, one), Select(product_valid, product, one));
|
||||||
|
const auto valid = _mm256_and_pd(product_valid, Finite(error));
|
||||||
|
return InvalidOutside(TwoSum(
|
||||||
|
Select(valid, product, _mm256_setzero_pd()),
|
||||||
|
Select(valid, error, _mm256_setzero_pd())), valid);
|
||||||
|
}
|
||||||
|
/// @brief Uses uniform low-zero branches with original four-product association;
|
||||||
|
/// mixed or invalid packets preserve the baseline checked path.
|
||||||
|
VectorPair Multiply(VectorPair left, VectorPair right) {
|
||||||
|
const auto a = Store(left);
|
||||||
|
const auto b = Store(right);
|
||||||
|
int branch = -1;
|
||||||
|
for (std::size_t lane = 0; lane < 4; ++lane) {
|
||||||
|
const bool normalized = NormalizedScalar(a.values[lane]) &&
|
||||||
|
NormalizedScalar(b.values[lane]);
|
||||||
|
if (!normalized) return ScalarMultiply(a, b);
|
||||||
|
const int shape = (a.values[lane].low != 0 ? 1 : 0) |
|
||||||
|
(b.values[lane].low != 0 ? 2 : 0);
|
||||||
|
if (branch != -1 && shape != branch) return ScalarMultiply(a, b);
|
||||||
|
branch = shape;
|
||||||
|
}
|
||||||
|
auto product = Product(left.high, right.high);
|
||||||
|
if (branch == 3) {
|
||||||
|
product = Add(product, Product(left.high, right.low), false);
|
||||||
|
product = Add(product, Product(left.low, right.high), false);
|
||||||
|
return Add(product, Product(left.low, right.low), false);
|
||||||
|
}
|
||||||
|
const VectorPair zero{_mm256_setzero_pd(), _mm256_setzero_pd()};
|
||||||
|
if ((branch & 2) != 0)
|
||||||
|
product = Add(product, Product(left.high, right.low), true);
|
||||||
|
else if ((branch & 1) != 0)
|
||||||
|
product = Add(product, zero, true);
|
||||||
|
if ((branch & 1) != 0)
|
||||||
|
product = Add(product, Product(left.low, right.high), true);
|
||||||
|
return Add(product, zero, true);
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void EvaluateMaterialPacketAvx2(const MaterialPacketInput& input,
|
||||||
|
PairPacket* output) {
|
||||||
|
const auto first = Multiply(Load(input.deviatoric_left),
|
||||||
|
Load(input.deviatoric_right));
|
||||||
|
const auto second = Multiply(Load(input.shear_left), Load(input.shear_right));
|
||||||
|
const auto third = Multiply(Load(input.tensor_left), Load(input.gradient_dot));
|
||||||
|
const auto material = Add(Add(first, second, true), third, true);
|
||||||
|
const PairPacket weight{{input.weight, input.weight, input.weight, input.weight}};
|
||||||
|
*output = Store(Add(Load(input.accumulated),
|
||||||
|
Multiply(material, Load(weight)), true));
|
||||||
|
}
|
||||||
|
} // namespace fesa::hex8_internal
|
||||||
|
#else
|
||||||
|
namespace fesa::hex8_internal {
|
||||||
|
void EvaluateMaterialPacketAvx2(const MaterialPacketInput& input,
|
||||||
|
PairPacket* output) {
|
||||||
|
EvaluateMaterialPacketScalar(input, output);
|
||||||
|
}
|
||||||
|
} // namespace fesa::hex8_internal
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
#include "hex8_pair_simd.h"
|
||||||
|
|
||||||
|
#include "hex8_pair_arithmetic.h"
|
||||||
|
|
||||||
|
#if defined(FESA_HEX8_PAIR_AVX2) && defined(_MSC_VER) && defined(_M_X64)
|
||||||
|
#include <float.h>
|
||||||
|
#include <intrin.h>
|
||||||
|
#include <xmmintrin.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace fesa::hex8_internal {
|
||||||
|
#if defined(_MSC_VER)
|
||||||
|
#define FESA_HEX8_BASELINE_NOINLINE __declspec(noinline)
|
||||||
|
#else
|
||||||
|
#define FESA_HEX8_BASELINE_NOINLINE
|
||||||
|
#endif
|
||||||
|
FESA_HEX8_BASELINE_NOINLINE
|
||||||
|
void ScalarPacketAdd(const PairPacket& left, const PairPacket& right,
|
||||||
|
bool pair_shortcuts, PairPacket* output) {
|
||||||
|
for (std::size_t lane = 0; lane < 4; ++lane) {
|
||||||
|
const math_internal::HighLow a{left.values[lane].high, left.values[lane].low};
|
||||||
|
const math_internal::HighLow b{right.values[lane].high, right.values[lane].low};
|
||||||
|
const auto value = pair_shortcuts ? AddPair(a, b) : math_internal::Add(a, b);
|
||||||
|
output->values[lane] = {value.high, value.low};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FESA_HEX8_BASELINE_NOINLINE
|
||||||
|
void ScalarPacketMultiply(const PairPacket& left, const PairPacket& right,
|
||||||
|
PairPacket* output) {
|
||||||
|
for (std::size_t lane = 0; lane < 4; ++lane) {
|
||||||
|
const auto value = MultiplyPair(
|
||||||
|
{left.values[lane].high, left.values[lane].low},
|
||||||
|
{right.values[lane].high, right.values[lane].low});
|
||||||
|
output->values[lane] = {value.high, value.low};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FESA_HEX8_BASELINE_NOINLINE
|
||||||
|
void ScalarPacketTwoProduct(const double left[4], const double right[4],
|
||||||
|
PairPacket* output) {
|
||||||
|
for (std::size_t lane = 0; lane < 4; ++lane) {
|
||||||
|
const auto value = math_internal::TwoProduct(left[lane], right[lane]);
|
||||||
|
output->values[lane] = {value.high, value.low};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#undef FESA_HEX8_BASELINE_NOINLINE
|
||||||
|
|
||||||
|
bool SupportsMaterialPacketInstructions(std::uint32_t leaf1_ecx,
|
||||||
|
std::uint64_t xcr0,
|
||||||
|
std::uint32_t leaf7_ebx) {
|
||||||
|
constexpr std::uint32_t required_leaf1 =
|
||||||
|
(1U << 12) | (1U << 26) | (1U << 27) | (1U << 28);
|
||||||
|
// MSVC /arch:AVX2 permits BMI codegen; ANDN is present in this kernel.
|
||||||
|
constexpr std::uint32_t required_leaf7 =
|
||||||
|
(1U << 3) | (1U << 5) | (1U << 8);
|
||||||
|
return (leaf1_ecx & required_leaf1) == required_leaf1 &&
|
||||||
|
(xcr0 & 6U) == 6U &&
|
||||||
|
(leaf7_ebx & required_leaf7) == required_leaf7;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EvaluateMaterialPacketScalar(const MaterialPacketInput& input,
|
||||||
|
PairPacket* output) {
|
||||||
|
const auto value = [](PacketPair pair) {
|
||||||
|
return math_internal::HighLow{pair.high, pair.low};
|
||||||
|
};
|
||||||
|
for (std::size_t lane = 0; lane < 4; ++lane) {
|
||||||
|
const auto first = MultiplyPair(value(input.deviatoric_left.values[lane]),
|
||||||
|
value(input.deviatoric_right.values[lane]));
|
||||||
|
const auto second = MultiplyPair(value(input.shear_left.values[lane]),
|
||||||
|
value(input.shear_right.values[lane]));
|
||||||
|
const auto third = MultiplyPair(value(input.tensor_left.values[lane]),
|
||||||
|
value(input.gradient_dot.values[lane]));
|
||||||
|
const auto material = AddPair(AddPair(first, second), third);
|
||||||
|
const auto accumulated = AddPair(value(input.accumulated.values[lane]),
|
||||||
|
MultiplyPair(material, value(input.weight)));
|
||||||
|
output->values[lane] = {accumulated.high, accumulated.low};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MaterialPacketEvaluator SelectMaterialPacketEvaluator() {
|
||||||
|
#if defined(FESA_HEX8_PAIR_AVX2) && defined(_MSC_VER) && defined(_M_X64)
|
||||||
|
static const bool hardware_supported = [] {
|
||||||
|
int info[4]{};
|
||||||
|
__cpuid(info, 0);
|
||||||
|
if (info[0] < 7) return false;
|
||||||
|
__cpuidex(info, 1, 0);
|
||||||
|
constexpr int required = (1 << 12) | (1 << 26) | (1 << 27) | (1 << 28);
|
||||||
|
if ((info[2] & required) != required) return false;
|
||||||
|
const auto leaf1 = static_cast<std::uint32_t>(info[2]);
|
||||||
|
const auto xcr0 = _xgetbv(0);
|
||||||
|
__cpuidex(info, 7, 0);
|
||||||
|
return SupportsMaterialPacketInstructions(
|
||||||
|
leaf1, xcr0, static_cast<std::uint32_t>(info[1]));
|
||||||
|
}();
|
||||||
|
if (!hardware_supported || !math_internal::ArithmeticEnvironmentSupported())
|
||||||
|
return EvaluateMaterialPacketScalar;
|
||||||
|
if ((_mm_getcsr() & 0x1f80U) != 0x1f80U)
|
||||||
|
return EvaluateMaterialPacketScalar;
|
||||||
|
unsigned int control = 0;
|
||||||
|
if (_controlfp_s(&control, 0, 0) != 0 ||
|
||||||
|
(control & _MCW_EM) != _MCW_EM)
|
||||||
|
return EvaluateMaterialPacketScalar;
|
||||||
|
return EvaluateMaterialPacketAvx2;
|
||||||
|
#else
|
||||||
|
return EvaluateMaterialPacketScalar;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
} // namespace fesa::hex8_internal
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
#include <limits>
|
#include <limits>
|
||||||
#include <random>
|
#include <random>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <type_traits>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -19,6 +20,7 @@
|
|||||||
#include "../../../src/fesa/math/high_low_arithmetic.h"
|
#include "../../../src/fesa/math/high_low_arithmetic.h"
|
||||||
#include "../../../src/fesa/elements/hex8_geometry.h"
|
#include "../../../src/fesa/elements/hex8_geometry.h"
|
||||||
#include "../../../src/fesa/elements/hex8_pair_arithmetic.h"
|
#include "../../../src/fesa/elements/hex8_pair_arithmetic.h"
|
||||||
|
#include "../../../src/fesa/elements/hex8_pair_simd.h"
|
||||||
#include "../math/high_low_arithmetic_original.h"
|
#include "../math/high_low_arithmetic_original.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@@ -84,6 +86,325 @@ TEST(Hex8HighLow, ScalarCrossProductShortcutMatchesOriginalBits) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fesa::hex8_internal::PacketPair RawPacketPair(HighLow value) {
|
||||||
|
return {value.high, value.low};
|
||||||
|
}
|
||||||
|
|
||||||
|
fesa::hex8_internal::PairPacket FrozenMaterialPacket(
|
||||||
|
const fesa::hex8_internal::MaterialPacketInput& input) {
|
||||||
|
using Original = fesa::high_low_original::HighLow;
|
||||||
|
const auto original = [](fesa::hex8_internal::PacketPair value) {
|
||||||
|
return Original{value.high, value.low};
|
||||||
|
};
|
||||||
|
fesa::hex8_internal::PairPacket result;
|
||||||
|
for (std::size_t lane = 0; lane < 4; ++lane) {
|
||||||
|
const auto first = fesa::high_low_original::Multiply(
|
||||||
|
original(input.deviatoric_left.values[lane]),
|
||||||
|
original(input.deviatoric_right.values[lane]));
|
||||||
|
const auto second = fesa::high_low_original::Multiply(
|
||||||
|
original(input.shear_left.values[lane]), original(input.shear_right.values[lane]));
|
||||||
|
const auto third = fesa::high_low_original::Multiply(
|
||||||
|
original(input.tensor_left.values[lane]), original(input.gradient_dot.values[lane]));
|
||||||
|
const auto value = fesa::high_low_original::Add(
|
||||||
|
fesa::high_low_original::Add(first, second), third);
|
||||||
|
const auto accumulated = fesa::high_low_original::Add(
|
||||||
|
original(input.accumulated.values[lane]),
|
||||||
|
fesa::high_low_original::Multiply(value, original(input.weight)));
|
||||||
|
result.values[lane] = {accumulated.high, accumulated.low};
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ExpectFrozenMaterialPacket(
|
||||||
|
const fesa::hex8_internal::MaterialPacketInput& input) {
|
||||||
|
const auto expected = FrozenMaterialPacket(input);
|
||||||
|
fesa::hex8_internal::PairPacket scalar, selected;
|
||||||
|
fesa::hex8_internal::EvaluateMaterialPacketScalar(input, &scalar);
|
||||||
|
const auto evaluator = fesa::hex8_internal::SelectMaterialPacketEvaluator();
|
||||||
|
evaluator(input, &selected);
|
||||||
|
for (std::size_t lane = 0; lane < 4; ++lane) {
|
||||||
|
SCOPED_TRACE(lane);
|
||||||
|
EXPECT_EQ(PairBits(scalar.values[lane].high), PairBits(expected.values[lane].high));
|
||||||
|
EXPECT_EQ(PairBits(scalar.values[lane].low), PairBits(expected.values[lane].low));
|
||||||
|
EXPECT_EQ(PairBits(selected.values[lane].high), PairBits(expected.values[lane].high));
|
||||||
|
EXPECT_EQ(PairBits(selected.values[lane].low), PairBits(expected.values[lane].low));
|
||||||
|
}
|
||||||
|
return evaluator != fesa::hex8_internal::EvaluateMaterialPacketScalar;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Hex8HighLow, FourColumnMaterialPacketMatchesFrozenScalarBits) {
|
||||||
|
ASSERT_TRUE(fesa::math_internal::ArithmeticEnvironmentSupported());
|
||||||
|
using fesa::hex8_internal::MaterialPacketInput;
|
||||||
|
RecordProperty("private_packet_backend",
|
||||||
|
fesa::hex8_internal::SelectMaterialPacketEvaluator() ==
|
||||||
|
fesa::hex8_internal::EvaluateMaterialPacketScalar ? "scalar" : "avx2");
|
||||||
|
std::size_t packet_checks = 0, avx2_evaluator_checks = 0;
|
||||||
|
const double tiny = std::numeric_limits<double>::denorm_min();
|
||||||
|
const double maximum = std::numeric_limits<double>::max();
|
||||||
|
const std::vector<HighLow> boundary{{}, {-0.0, 0}, {0, -0.0},
|
||||||
|
{1}, {-1}, {1, -0.0}, {1, std::ldexp(1.0, -54)},
|
||||||
|
{-1, std::ldexp(1.0, -54)}, {tiny}, {-tiny},
|
||||||
|
{std::numeric_limits<double>::min()}, {maximum}, {-maximum},
|
||||||
|
{1, tiny}, {1, -tiny}, {std::nextafter(1.0, 2.0)},
|
||||||
|
{1, 1}, {0, tiny}, {std::numeric_limits<double>::infinity(), 0},
|
||||||
|
{std::numeric_limits<double>::quiet_NaN(), 0},
|
||||||
|
{std::numeric_limits<double>::signaling_NaN(), 0},
|
||||||
|
{0, std::numeric_limits<double>::signaling_NaN()}};
|
||||||
|
for (std::size_t first = 0; first < boundary.size(); ++first)
|
||||||
|
for (std::size_t second = 0; second < boundary.size(); ++second) {
|
||||||
|
MaterialPacketInput input{};
|
||||||
|
input.weight = {0.125};
|
||||||
|
for (std::size_t lane = 0; lane < 4; ++lane) {
|
||||||
|
input.deviatoric_left.values[lane] = RawPacketPair(boundary[first]);
|
||||||
|
input.deviatoric_right.values[lane] = RawPacketPair(boundary[second]);
|
||||||
|
input.shear_left.values[lane] = {2, std::ldexp(1.0, -54)};
|
||||||
|
input.shear_right.values[lane] = {-0.5, std::ldexp(1.0, -56)};
|
||||||
|
input.tensor_left.values[lane] = {0.75};
|
||||||
|
input.gradient_dot.values[lane] = {-0.25};
|
||||||
|
input.accumulated.values[lane] = lane == 0 ? fesa::hex8_internal::PacketPair{0, 0}
|
||||||
|
: fesa::hex8_internal::PacketPair{0.125, 0};
|
||||||
|
}
|
||||||
|
avx2_evaluator_checks += ExpectFrozenMaterialPacket(input) ? 1U : 0U;
|
||||||
|
++packet_checks;
|
||||||
|
}
|
||||||
|
for (int exponent = -1074; exponent <= 1023; ++exponent) {
|
||||||
|
MaterialPacketInput input{};
|
||||||
|
input.weight = {1};
|
||||||
|
for (std::size_t lane = 0; lane < 4; ++lane) {
|
||||||
|
input.deviatoric_left.values[lane] = {
|
||||||
|
std::ldexp(lane % 2U == 0 ? 1.0 : -1.0, exponent)};
|
||||||
|
input.deviatoric_right.values[lane] = {lane < 2 ? 0.5 : 2.0};
|
||||||
|
}
|
||||||
|
avx2_evaluator_checks += ExpectFrozenMaterialPacket(input) ? 1U : 0U;
|
||||||
|
++packet_checks;
|
||||||
|
}
|
||||||
|
std::mt19937_64 random(0x4c0116U);
|
||||||
|
for (std::size_t sample = 0; sample < 2048; ++sample) {
|
||||||
|
MaterialPacketInput input{};
|
||||||
|
const auto pair = [&]() {
|
||||||
|
const double high = std::ldexp(
|
||||||
|
1.0 + static_cast<double>(random() % 256U) / 256.0,
|
||||||
|
static_cast<int>(random() % 201U) - 100);
|
||||||
|
const double signed_high = (random() & 1U) != 0 ? high : -high;
|
||||||
|
const double low = sample % 3U == 0 ? 0.0 : std::ldexp(high, -55);
|
||||||
|
return fesa::hex8_internal::PacketPair{
|
||||||
|
signed_high, (random() & 1U) != 0 ? low : -low};
|
||||||
|
};
|
||||||
|
input.weight = sample % 2U == 0
|
||||||
|
? fesa::hex8_internal::PacketPair{0.125, 0} : pair();
|
||||||
|
for (std::size_t lane = 0; lane < 4; ++lane) {
|
||||||
|
input.deviatoric_left.values[lane] = pair();
|
||||||
|
input.deviatoric_right.values[lane] = pair();
|
||||||
|
input.shear_left.values[lane] = pair();
|
||||||
|
input.shear_right.values[lane] = pair();
|
||||||
|
input.tensor_left.values[lane] = pair();
|
||||||
|
input.gradient_dot.values[lane] = pair();
|
||||||
|
input.accumulated.values[lane] = sample % 4U == 0
|
||||||
|
? fesa::hex8_internal::PacketPair{0, 0} : pair();
|
||||||
|
}
|
||||||
|
avx2_evaluator_checks += ExpectFrozenMaterialPacket(input) ? 1U : 0U;
|
||||||
|
++packet_checks;
|
||||||
|
}
|
||||||
|
RecordProperty("packet_oracle_checks", static_cast<int>(packet_checks));
|
||||||
|
RecordProperty("avx2_evaluator_packet_checks",
|
||||||
|
static_cast<int>(avx2_evaluator_checks));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Hex8HighLow, PacketSpecialLaneDoesNotChangeOtherColumns) {
|
||||||
|
fesa::hex8_internal::MaterialPacketInput input{};
|
||||||
|
input.weight = {0.125};
|
||||||
|
for (std::size_t lane = 0; lane < 4; ++lane) {
|
||||||
|
input.deviatoric_left.values[lane] = {1, std::ldexp(1.0, -54)};
|
||||||
|
input.deviatoric_right.values[lane] = {2, std::ldexp(1.0, -55)};
|
||||||
|
input.shear_left.values[lane] = {0.5};
|
||||||
|
input.shear_right.values[lane] = {-0.25};
|
||||||
|
input.tensor_left.values[lane] = {0.75};
|
||||||
|
input.gradient_dot.values[lane] = {0.125};
|
||||||
|
}
|
||||||
|
const auto reference = FrozenMaterialPacket(input);
|
||||||
|
const std::vector<HighLow> special{{}, {-0.0, 0}, {1},
|
||||||
|
{std::numeric_limits<double>::max()},
|
||||||
|
{std::numeric_limits<double>::denorm_min()}, {1, 1},
|
||||||
|
{std::numeric_limits<double>::quiet_NaN(), 0}};
|
||||||
|
for (std::size_t lane = 0; lane < 4; ++lane)
|
||||||
|
for (const auto value : special) {
|
||||||
|
auto changed = input;
|
||||||
|
changed.deviatoric_left.values[lane] = RawPacketPair(value);
|
||||||
|
ExpectFrozenMaterialPacket(changed);
|
||||||
|
fesa::hex8_internal::PairPacket actual;
|
||||||
|
fesa::hex8_internal::SelectMaterialPacketEvaluator()(changed, &actual);
|
||||||
|
for (std::size_t other = 0; other < 4; ++other)
|
||||||
|
if (other != lane) {
|
||||||
|
EXPECT_EQ(PairBits(actual.values[other].high), PairBits(reference.values[other].high));
|
||||||
|
EXPECT_EQ(PairBits(actual.values[other].low), PairBits(reference.values[other].low));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Hex8HighLow, UniformDyadicPacketsExerciseEachLowZeroShape) {
|
||||||
|
std::size_t checks = 0, avx2_checks = 0;
|
||||||
|
for (int shape = 0; shape < 4; ++shape)
|
||||||
|
for (int low_weight = 0; low_weight < 2; ++low_weight)
|
||||||
|
for (int zero_accumulator = 0; zero_accumulator < 2; ++zero_accumulator) {
|
||||||
|
fesa::hex8_internal::MaterialPacketInput input{};
|
||||||
|
input.weight = {0.5, low_weight != 0 ? std::ldexp(1.0, -57) : 0.0};
|
||||||
|
for (std::size_t lane = 0; lane < 4; ++lane) {
|
||||||
|
const bool left_low = (shape & 1) != 0;
|
||||||
|
const bool right_low = (shape & 2) != 0;
|
||||||
|
// Every lane has the same normalized nonzero dyadic inputs. Component
|
||||||
|
// LBE sums stay above -200 and all Add intermediates are bounded by 4.
|
||||||
|
// Thus each Multiply low-shape is uniform and its component products
|
||||||
|
// bypass zero/unsupported-tail fallback. No mixed-zero Add fallback
|
||||||
|
// is possible; the initial zero accumulator is uniform across lanes.
|
||||||
|
input.deviatoric_left.values[lane] = {1, left_low ? std::ldexp(1.0, -54) : 0};
|
||||||
|
input.deviatoric_right.values[lane] = {2, right_low ? std::ldexp(1.0, -54) : 0};
|
||||||
|
input.shear_left.values[lane] = {0.5, left_low ? std::ldexp(1.0, -56) : 0};
|
||||||
|
input.shear_right.values[lane] = {0.25, right_low ? std::ldexp(1.0, -57) : 0};
|
||||||
|
input.tensor_left.values[lane] = {0.75, left_low ? std::ldexp(1.0, -56) : 0};
|
||||||
|
input.gradient_dot.values[lane] = {0.125, right_low ? std::ldexp(1.0, -58) : 0};
|
||||||
|
input.accumulated.values[lane] = zero_accumulator != 0
|
||||||
|
? fesa::hex8_internal::PacketPair{0, 0}
|
||||||
|
: fesa::hex8_internal::PacketPair{0.0625, 0};
|
||||||
|
}
|
||||||
|
avx2_checks += ExpectFrozenMaterialPacket(input) ? 1U : 0U;
|
||||||
|
++checks;
|
||||||
|
}
|
||||||
|
RecordProperty("uniform_low_shape_checks", static_cast<int>(checks));
|
||||||
|
RecordProperty("avx2_uniform_low_shape_checks", static_cast<int>(avx2_checks));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Hex8HighLow, OverflowLaneIsNeutralizedBeforeResidualFma) {
|
||||||
|
const auto evaluator = fesa::hex8_internal::SelectMaterialPacketEvaluator();
|
||||||
|
if (evaluator == fesa::hex8_internal::EvaluateMaterialPacketScalar)
|
||||||
|
GTEST_SKIP() << "AVX2 masked environment is not selected; routing unverified.";
|
||||||
|
std::fenv_t original;
|
||||||
|
ASSERT_EQ(std::fegetenv(&original), 0);
|
||||||
|
for (std::size_t bad = 0; bad < 4; ++bad) {
|
||||||
|
fesa::hex8_internal::MaterialPacketInput input{};
|
||||||
|
input.weight = {0.5};
|
||||||
|
for (std::size_t lane = 0; lane < 4; ++lane) {
|
||||||
|
input.deviatoric_left.values[lane] = {
|
||||||
|
lane == bad ? std::numeric_limits<double>::max() : 1};
|
||||||
|
input.deviatoric_right.values[lane] = {2};
|
||||||
|
input.shear_left.values[lane] = {0.5};
|
||||||
|
input.shear_right.values[lane] = {0.25};
|
||||||
|
input.tensor_left.values[lane] = {0.75};
|
||||||
|
input.gradient_dot.values[lane] = {0.125};
|
||||||
|
input.accumulated.values[lane] = {0.0625};
|
||||||
|
}
|
||||||
|
// All operands are finite/nonzero and max*2 has LBE sum 972, so Product
|
||||||
|
// reaches its vector multiply; only the post-product finite mask rejects it.
|
||||||
|
const auto expected = FrozenMaterialPacket(input);
|
||||||
|
EXPECT_EQ(std::feclearexcept(FE_ALL_EXCEPT), 0);
|
||||||
|
fesa::hex8_internal::PairPacket output;
|
||||||
|
evaluator(input, &output);
|
||||||
|
const bool extra_invalid = (std::fetestexcept(FE_INVALID) & FE_INVALID) != 0;
|
||||||
|
EXPECT_EQ(std::fesetenv(&original), 0);
|
||||||
|
EXPECT_FALSE(extra_invalid);
|
||||||
|
for (std::size_t lane = 0; lane < 4; ++lane) {
|
||||||
|
EXPECT_EQ(PairBits(output.values[lane].high), PairBits(expected.values[lane].high));
|
||||||
|
EXPECT_EQ(PairBits(output.values[lane].low), PairBits(expected.values[lane].low));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RecordProperty("avx2_product_overflow_positions", 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Hex8HighLow, PacketBackendSelectionRecoversAfterEnvironmentRestoration) {
|
||||||
|
const auto initial = fesa::hex8_internal::SelectMaterialPacketEvaluator();
|
||||||
|
const int rounding = std::fegetround();
|
||||||
|
ASSERT_EQ(std::fesetround(FE_UPWARD), 0);
|
||||||
|
EXPECT_EQ(fesa::hex8_internal::SelectMaterialPacketEvaluator(),
|
||||||
|
fesa::hex8_internal::EvaluateMaterialPacketScalar);
|
||||||
|
EXPECT_EQ(std::fesetround(rounding), 0);
|
||||||
|
#if defined(_M_X64) || defined(__SSE2__)
|
||||||
|
const auto control = _mm_getcsr();
|
||||||
|
_mm_setcsr(control | 0x8040U);
|
||||||
|
EXPECT_EQ(fesa::hex8_internal::SelectMaterialPacketEvaluator(),
|
||||||
|
fesa::hex8_internal::EvaluateMaterialPacketScalar);
|
||||||
|
_mm_setcsr(control);
|
||||||
|
_mm_setcsr(control & ~0x80U);
|
||||||
|
const auto unmasked = fesa::hex8_internal::SelectMaterialPacketEvaluator();
|
||||||
|
_mm_setcsr(control);
|
||||||
|
EXPECT_EQ(unmasked, fesa::hex8_internal::EvaluateMaterialPacketScalar);
|
||||||
|
#endif
|
||||||
|
EXPECT_EQ(fesa::hex8_internal::SelectMaterialPacketEvaluator(), initial);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Hex8HighLow, PacketScalarBridgesPreserveOriginalRawBits) {
|
||||||
|
using fesa::hex8_internal::PacketPair;
|
||||||
|
using fesa::hex8_internal::PairPacket;
|
||||||
|
static_assert(std::is_trivial_v<PacketPair> &&
|
||||||
|
std::is_standard_layout_v<PacketPair>);
|
||||||
|
static_assert(std::is_trivial_v<PairPacket> &&
|
||||||
|
std::is_standard_layout_v<PairPacket>);
|
||||||
|
static_assert(std::is_trivial_v<fesa::hex8_internal::MaterialPacketInput>);
|
||||||
|
const double tiny = std::numeric_limits<double>::denorm_min();
|
||||||
|
const std::vector<PacketPair> pairs{{0, 0}, {-0.0, 0}, {0, -0.0},
|
||||||
|
{1, -0.0}, {1, std::ldexp(1.0, -54)}, {tiny, 0}, {1, tiny},
|
||||||
|
{std::numeric_limits<double>::max(), 0}, {1, 1},
|
||||||
|
{std::numeric_limits<double>::infinity(), 0},
|
||||||
|
{std::numeric_limits<double>::signaling_NaN(), 0}};
|
||||||
|
for (const auto left : pairs)
|
||||||
|
for (const auto right : pairs) {
|
||||||
|
PairPacket a{{left, left, left, left}};
|
||||||
|
PairPacket b{{right, right, right, right}};
|
||||||
|
PairPacket result;
|
||||||
|
const fesa::high_low_original::HighLow x{left.high, left.low};
|
||||||
|
const fesa::high_low_original::HighLow y{right.high, right.low};
|
||||||
|
const auto expect = [&](fesa::high_low_original::HighLow expected) {
|
||||||
|
for (std::size_t lane = 0; lane < 4; ++lane) {
|
||||||
|
EXPECT_EQ(PairBits(result.values[lane].high), PairBits(expected.high));
|
||||||
|
EXPECT_EQ(PairBits(result.values[lane].low), PairBits(expected.low));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fesa::hex8_internal::ScalarPacketAdd(a, b, false, &result);
|
||||||
|
expect(fesa::high_low_original::Add(x, y));
|
||||||
|
fesa::hex8_internal::ScalarPacketAdd(a, b, true, &result);
|
||||||
|
expect(fesa::high_low_original::Add(x, y));
|
||||||
|
fesa::hex8_internal::ScalarPacketMultiply(a, b, &result);
|
||||||
|
expect(fesa::high_low_original::Multiply(x, y));
|
||||||
|
const double high_left[4]{left.high, left.high, left.high, left.high};
|
||||||
|
const double high_right[4]{right.high, right.high, right.high, right.high};
|
||||||
|
fesa::hex8_internal::ScalarPacketTwoProduct(high_left, high_right, &result);
|
||||||
|
expect(fesa::high_low_original::TwoProduct(left.high, right.high));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Hex8HighLow, PacketCapabilityRejectsMissingIntegerIsaAndOsState) {
|
||||||
|
constexpr std::uint32_t leaf1 =
|
||||||
|
(1U << 12) | (1U << 26) | (1U << 27) | (1U << 28);
|
||||||
|
constexpr std::uint32_t leaf7 = (1U << 3) | (1U << 5) | (1U << 8);
|
||||||
|
EXPECT_TRUE(fesa::hex8_internal::SupportsMaterialPacketInstructions(
|
||||||
|
leaf1, 6, leaf7));
|
||||||
|
for (const auto missing : {1U << 3, 1U << 5, 1U << 8})
|
||||||
|
EXPECT_FALSE(fesa::hex8_internal::SupportsMaterialPacketInstructions(
|
||||||
|
leaf1, 6, leaf7 & ~missing));
|
||||||
|
for (const auto missing : {1U << 12, 1U << 26, 1U << 27, 1U << 28})
|
||||||
|
EXPECT_FALSE(fesa::hex8_internal::SupportsMaterialPacketInstructions(
|
||||||
|
leaf1 & ~missing, 6, leaf7));
|
||||||
|
for (std::uint64_t state : {0ULL, 2ULL, 4ULL})
|
||||||
|
EXPECT_FALSE(fesa::hex8_internal::SupportsMaterialPacketInstructions(
|
||||||
|
leaf1, state, leaf7));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Hex8HighLow, PacketDoesNotClearPriorMaskedStickyFlags) {
|
||||||
|
std::fenv_t original;
|
||||||
|
ASSERT_EQ(std::fegetenv(&original), 0);
|
||||||
|
const auto evaluator = fesa::hex8_internal::SelectMaterialPacketEvaluator();
|
||||||
|
ASSERT_EQ(std::feraiseexcept(FE_INEXACT), 0);
|
||||||
|
fesa::hex8_internal::MaterialPacketInput input{};
|
||||||
|
input.weight = {1};
|
||||||
|
for (std::size_t lane = 0; lane < 4; ++lane) {
|
||||||
|
input.deviatoric_left.values[lane] = {1, std::ldexp(1.0, -54)};
|
||||||
|
input.deviatoric_right.values[lane] = {2, std::ldexp(1.0, -54)};
|
||||||
|
}
|
||||||
|
fesa::hex8_internal::PairPacket output;
|
||||||
|
evaluator(input, &output);
|
||||||
|
const bool preserved = (std::fetestexcept(FE_INEXACT) & FE_INEXACT) != 0;
|
||||||
|
EXPECT_EQ(std::fesetenv(&original), 0);
|
||||||
|
EXPECT_TRUE(preserved);
|
||||||
|
}
|
||||||
|
|
||||||
fesa::Hex8Element UnitBrick(double offset = 0, double nu = 0) {
|
fesa::Hex8Element UnitBrick(double offset = 0, double nu = 0) {
|
||||||
fesa::ElementDofLayout layout;
|
fesa::ElementDofLayout layout;
|
||||||
layout.node_indices = {0, 1, 2, 3, 4, 5, 6, 7};
|
layout.node_indices = {0, 1, 2, 3, 4, 5, 6, 7};
|
||||||
|
|||||||
Reference in New Issue
Block a user