대수적 연산의 실행 의미론이 불분명함. 문서에 따르면 컴파일러 최적화의 예측 불가능성 때문에 같은 입력도 한 번의 프로그램 실행 중 서로 다른 결과를 낼 수 있으며, 안전성을 위해 반환값의 어떤 속성에도 의존하면 안 된다고 함
그래도 반환값이 한 번 정해지면 고정된다는 보장은 있어야 하지 않는지 궁금함. 최적화 단계가 코드를 복제할 수 있고 대수적 연산이 비결정적이라면 freeze poison과 비슷하게 동작하는지도 모호함 a의 값이 같더라도 상수에서 왔는지, FMA로 바뀔 수 있는 곱셈에서 왔는지에 따라 a.algebraic_add(b)의 결과가 달라질 수 있음. 이는 부동소수점 값에도 출처(provenance) 가 있는 것처럼 보이며, 값은 같지만 출처가 다를 때 동등한지와 최적화 단계가 이를 고려하는지가 궁금함
let sum = a + b;
let algebraic = sum.algebraic_sub(b);
let exact = sum - b;
if algebraic == exact { // (1)
assert_eq!(algebraic, a); // (2)
}
(1)은 IEEE 754 의미론과 NaN이 없다는 전제에서 true로, (2)는 기호적으로 계산한다는 전제에서 true로 각각 최적화할 수 있지만 둘을 함께 적용하면 잘못된 컴파일이 가능해 보임. LLVM에서 실제 오컴파일을 재현하지는 못했지만 각 최적화 단계가 이를 의도적으로 고려하는지 알기 어렵고, Rust의 대수적 산술 API 논의에서도 답을 찾지 못함
이 코드는 잠재적인 오컴파일이라기보다, 스스로 포기한 최적화 관련 보장에 의존하므로 애초에 작성하면 안 되는 코드로 해석됨. 매우 의외의 결과가 나올 수 있지만 그런 동작을 요청한 셈임
다만 이 기능을 비활성화해야 하거나 문서를 개선해야 할 가능성도 있으므로 개발자에게 직접 문의하는 편이 좋겠음
GCC/Clang의 -fastmath 옵션과 같은 기능인지 궁금함
어느 정도 비슷하지만 더 나은 방식임. 기본 fast-math는 정의되지 않은 동작을 일으킬 수 있는 최적화까지 활성화하지만, 이 기능에는 그런 동작이 없음. 물론 fast-math에서도 문제가 없는 최적화만 골라 활성화할 수는 있음
또한 fast-math가 기껏해야 함수 단위인 데 비해 이 기능은 연산 단위로 적용됨
문제 영역과 Rust의 대수적 연산을 이미 알고 있었지만, 이 글은 간결하면서 핵심을 잘 전달함. 하드웨어 성능 카운터로 수집한 것으로 보이는 SIMD 연산 횟수 통계도 유용한 요소임
실제로 하드웨어 성능 카운터에서 수집한 값임. 책을 위해 간단한 벤치마킹용 Jupyter 매직을 작성했고 글은 Quarto로 렌더링함
다음처럼 실행하면 결과가 Markdown 표로 생성됨