perf: reuse owned shell Jet Hessians in accumulation
This commit is contained in:
@@ -630,6 +630,176 @@ __declspec(noinline) double Consume(const Jet& value) {
|
||||
return result + value.constant_derivatives + value.second.IsImplicitZero();
|
||||
}
|
||||
|
||||
// T3 evidence is separate from both immutable earlier snapshot formats. These
|
||||
// calls also exercise the original overload when built against the T1 source.
|
||||
std::string AccumulationSnapshots(bool reassociate) {
|
||||
const std::array<double, 12> boundary{
|
||||
0.0, -0.0, 1.0, -1.0, 0.1, -0.3, 1e16,
|
||||
std::numeric_limits<double>::denorm_min(),
|
||||
std::numeric_limits<double>::max(),
|
||||
std::numeric_limits<double>::infinity(),
|
||||
std::numeric_limits<double>::quiet_NaN(),
|
||||
std::numeric_limits<double>::signaling_NaN()};
|
||||
std::string out;
|
||||
const std::array<int, 4> rounding{FE_TONEAREST, FE_DOWNWARD, FE_UPWARD,
|
||||
FE_TOWARDZERO};
|
||||
for (const int mode : rounding) {
|
||||
for (const unsigned flush : {0u, 0x8040u}) {
|
||||
for (const unsigned sticky : {0u, 0x21u}) {
|
||||
for (std::size_t kind = 0; kind < boundary.size(); ++kind) {
|
||||
ResetFp();
|
||||
Jet left = Variable(boundary[kind], kind, -0.25);
|
||||
Jet right = Variable(0.5, (kind + 1) % 24, 0.125);
|
||||
if (kind % 3 == 0) left = Constant(boundary[kind]);
|
||||
if (kind % 3 == 1) {
|
||||
left.second.Data()[25] = boundary[kind];
|
||||
right.second.Data()[48] = -0.5;
|
||||
}
|
||||
left.mixed_first[3] = boundary[kind];
|
||||
left.mixed_second[4] = -0.0;
|
||||
const Jet saved_left = left;
|
||||
const Jet saved_right = right;
|
||||
const auto before_left = Snapshot("input", left, 0, 0, 0);
|
||||
const auto before_right = Snapshot("input", right, 0, 0, 0);
|
||||
const auto environment = [&] {
|
||||
ResetFp();
|
||||
std::fesetround(mode);
|
||||
_mm_setcsr((_mm_getcsr() & ~0x8040u) | flush | sticky);
|
||||
};
|
||||
const auto observe = [&](const char* name, auto expression) {
|
||||
environment();
|
||||
const auto before = FpState();
|
||||
const Jet result = expression();
|
||||
const int exceptions = std::fetestexcept(FE_ALL_EXCEPT);
|
||||
const unsigned status = _statusfp(), mxcsr = _mm_getcsr();
|
||||
out += std::to_string(mode) + ":" + std::to_string(flush) + ":" +
|
||||
std::to_string(sticky) + ":" + std::to_string(kind) + " " +
|
||||
before + " " + FpState() + " " +
|
||||
Snapshot(name, result, exceptions, status, mxcsr);
|
||||
};
|
||||
const JetVector3 a{left, right, Constant(-1)};
|
||||
const JetVector3 b{right, Constant(1), left};
|
||||
observe("dot", [&] {
|
||||
if (reassociate)
|
||||
return a[0] * b[0] + (a[1] * b[1] + a[2] * b[2]);
|
||||
return Dot(a, b);
|
||||
});
|
||||
for (std::size_t component = 0; component < 3; ++component)
|
||||
observe("cross", [&] { return Cross(a, b)[component]; });
|
||||
const JetMatrix3 matrix{{a, b, a}};
|
||||
const JetMatrix3 other{{b, a, b}};
|
||||
// Every matrix/vector result is consumed over the whole fixture;
|
||||
// boundary groups rotate the selected entry to keep evidence bounded.
|
||||
observe("matrix", [&] {
|
||||
return Multiply(matrix, other)[kind % 3][(kind / 3) % 3];
|
||||
});
|
||||
observe("matrix-vector", [&] {
|
||||
return Multiply(matrix, b)[kind % 3];
|
||||
});
|
||||
observe("frame", [&] { return FrameComponent(a, matrix, b); });
|
||||
observe("fixed-frame", [&] {
|
||||
return FrameComponent(ConstantVector(Vector3{0.5, -1, 0}), matrix,
|
||||
ConstantVector(Vector3{-0.0, 0.25, 1}));
|
||||
});
|
||||
observe("energy-accumulation", [&] {
|
||||
Jet energy{};
|
||||
for (std::size_t i = 0; i < 3; ++i)
|
||||
energy = std::move(energy) + a[i] * b[i];
|
||||
return energy;
|
||||
});
|
||||
observe("shared-rvalue", [&] {
|
||||
Jet value = left;
|
||||
return std::move(value) + right;
|
||||
});
|
||||
observe("same-object-rvalue", [&] {
|
||||
Jet value = right;
|
||||
return std::move(value) + value;
|
||||
});
|
||||
observe("unique-rvalue", [&] {
|
||||
Jet value = left * right;
|
||||
return std::move(value) + right;
|
||||
});
|
||||
observe("constant-product", [&] { return Constant(-0.5) * left; });
|
||||
observe("scalar-product", [&] { return -0.5 * left; });
|
||||
if (before_left != Snapshot("input", saved_left, 0, 0, 0) ||
|
||||
before_left != Snapshot("input", left, 0, 0, 0) ||
|
||||
before_right != Snapshot("input", saved_right, 0, 0, 0) ||
|
||||
before_right != Snapshot("input", right, 0, 0, 0))
|
||||
throw std::runtime_error("accumulation input alias modified");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ResetFp();
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string AccumulationAliasSnapshots(bool overwrite_input) {
|
||||
std::string out;
|
||||
for (int kind = 0; kind < 4; ++kind) {
|
||||
Jet value = Variable(kind == 0 ? -0.0 : 0.5, 0, -0.25) *
|
||||
Variable(0.75, 1, 0.5);
|
||||
if (kind == 1) value = value - value;
|
||||
if (kind == 2) value.second.Data()[0] =
|
||||
std::numeric_limits<double>::max();
|
||||
const auto before = Snapshot("live-input", value, 0, 0, 0);
|
||||
out += Observe("unique-same-object", [&] {
|
||||
if (overwrite_input) value.first[0] = 17; // Fixture-only negative control.
|
||||
return std::move(value) + value;
|
||||
});
|
||||
if (before != Snapshot("live-input", value, 0, 0, 0))
|
||||
throw std::runtime_error("same-object live operand modified");
|
||||
Jet survivor = value;
|
||||
out += Observe("shared-move", [&] {
|
||||
return std::move(value) + Constant(-1);
|
||||
});
|
||||
if (before != Snapshot("live-input", survivor, 0, 0, 0))
|
||||
throw std::runtime_error("surviving shared operand modified");
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
void AccumulationBench() {
|
||||
const Jet left = Variable(0.5, 0, 0.25) * Variable(0.75, 1, -0.5);
|
||||
const Jet right = Variable(0.25, 2, 0.125) * Variable(-0.5, 3, 0.25);
|
||||
const JetVector3 a{left, right, left}, b{right, left, right};
|
||||
const JetMatrix3 matrix{{a, b, a}}, other{{b, a, b}};
|
||||
const auto e1 = ConstantVector(Vector3{0.5, -1, 0.25});
|
||||
const auto e2 = ConstantVector(Vector3{-0.25, 0.5, 1});
|
||||
volatile double consumed = 0;
|
||||
for (int kind = 0; kind < 6; ++kind) {
|
||||
const auto start = std::chrono::steady_clock::now();
|
||||
for (int i = 0; i < 3000; ++i) {
|
||||
if (kind == 0) consumed = consumed + Consume(Dot(a, b));
|
||||
if (kind == 1) {
|
||||
const auto result = Cross(a, b);
|
||||
for (const auto& value : result) consumed = consumed + Consume(value);
|
||||
}
|
||||
if (kind == 2) {
|
||||
const auto result = Multiply(matrix, other);
|
||||
for (const auto& row : result)
|
||||
for (const auto& value : row) consumed = consumed + Consume(value);
|
||||
}
|
||||
if (kind == 3) {
|
||||
const auto result = Multiply(matrix, b);
|
||||
for (const auto& value : result) consumed = consumed + Consume(value);
|
||||
}
|
||||
if (kind == 4)
|
||||
consumed = consumed + Consume(FrameComponent(e1, matrix, e2));
|
||||
if (kind == 5) {
|
||||
Jet density{};
|
||||
for (std::size_t component = 0; component < 3; ++component)
|
||||
density = std::move(density) + a[component] * b[component];
|
||||
consumed = consumed + Consume(0.5 * density);
|
||||
}
|
||||
}
|
||||
std::cout << "accumulation-bench," << kind << ",3000,"
|
||||
<< std::chrono::duration<double>(
|
||||
std::chrono::steady_clock::now() - start).count()
|
||||
<< ",allocation-included,setup-excluded," << consumed << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
void Bench() {
|
||||
volatile double consumed = 0;
|
||||
for (int kind = 0; kind < 8; ++kind) {
|
||||
@@ -671,6 +841,39 @@ int main(int argc, char** argv) {
|
||||
fesa::Bench();
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::string(argv[1]) == "--accumulation-bench") {
|
||||
fesa::AccumulationBench();
|
||||
return 0;
|
||||
}
|
||||
if (argc >= 3 && (std::string(argv[1]) == "--accumulation-write" ||
|
||||
std::string(argv[1]) == "--accumulation-compare")) {
|
||||
const bool alias = argc > 3 && std::string(argv[3]) == "--unique-alias";
|
||||
auto actual = alias ? fesa::AccumulationAliasSnapshots(
|
||||
argc > 4 && std::string(argv[4]) ==
|
||||
"--overwrite-input")
|
||||
: fesa::AccumulationSnapshots(
|
||||
argc > 3 && std::string(argv[3]) ==
|
||||
"--reassociate");
|
||||
if (std::string(argv[1]) == "--accumulation-write") {
|
||||
if (std::ifstream(argv[2], std::ios::binary))
|
||||
throw std::runtime_error("accumulation snapshot already exists");
|
||||
std::ofstream output(argv[2], std::ios::binary);
|
||||
output << actual;
|
||||
output.close();
|
||||
if (!output) throw std::runtime_error("accumulation snapshot write failure");
|
||||
} else {
|
||||
std::ifstream input(argv[2], std::ios::binary);
|
||||
const std::string expected((std::istreambuf_iterator<char>(input)), {});
|
||||
if (expected != actual)
|
||||
throw std::runtime_error("accumulation snapshot mismatch");
|
||||
}
|
||||
std::cout << argv[1]
|
||||
<< (alias ? " passed; 8 unique/same-object/shared alias cases"
|
||||
: " passed; 192 boundary/environment groups, Dot/"
|
||||
"Cross/matrix/vector/frame/accumulation/constants")
|
||||
<< ", 651 channels and FP state\n";
|
||||
return 0;
|
||||
}
|
||||
if (argc >= 3 && (std::string(argv[1]) == "--product-write" ||
|
||||
std::string(argv[1]) == "--product-compare")) {
|
||||
auto actual = fesa::ProductSnapshots(
|
||||
|
||||
@@ -65,3 +65,30 @@ T2의 무가드 단일 외적 후보는 unmasked 최초 INVALID/OVERFLOW 순서
|
||||
전치 성분과 276개 상삼각 scratch를 재사용한다. Unmasked 환경과 외적 거부는 기존 두 호출,
|
||||
576개 reverse 배열과 row-major 결합을 유지한다. Masked snapshot과 별도 원본/후보 SEH
|
||||
관측을 함께 확인하며, 정상 경로의 임시 쓰기 감소를 전체 stack 예약 감소로 보고하지 않는다.
|
||||
|
||||
T3 누적 검사는 기존 T1/T2 snapshot을 확장하지 않고 별도 파일을 쓴다. 원본 source와
|
||||
보존 archive의 출처를 확인한 실행물에서만 새 경로에 생성한다. 실제 CMake 경로는
|
||||
원본 source staging을 CMake로 configure/build한 실행물과 구분해 연결한다.
|
||||
|
||||
```bat
|
||||
<original-fixture.exe> --accumulation-write <new-immutable-accumulation-snapshot>
|
||||
<candidate-fixture.exe> --accumulation-compare <immutable-accumulation-snapshot>
|
||||
<candidate-fixture.exe> --accumulation-compare <immutable-accumulation-snapshot> --reassociate
|
||||
<original-fixture.exe> --accumulation-write <new-immutable-alias-snapshot> --unique-alias
|
||||
<candidate-fixture.exe> --accumulation-compare <immutable-alias-snapshot> --unique-alias
|
||||
<candidate-fixture.exe> --accumulation-compare <immutable-alias-snapshot> --unique-alias --overwrite-input
|
||||
<candidate-fixture.exe> --accumulation-bench
|
||||
```
|
||||
|
||||
일반 그룹은 12개 경계 × 4 rounding × 2 FTZ/DAZ × 2 초기 sticky의 192개 조합으로
|
||||
Dot·Cross·3×3 matrix/vector·일반/고정 frame·energy 형태 누적·상수 Jet/scalar를
|
||||
검사한다. 별도 8개 alias 그룹은 유일한 materialized Hessian을 가진 동일 객체,
|
||||
surviving copy와 공유 rvalue를 검사한다. `--reassociate`와 `--overwrite-input`는
|
||||
fixture 전용 음성 대조이며 exit 1이 기대값이다. 모든 비교는 651개 채널·provenance·
|
||||
implicit Hessian·FP 상태를 포함한다. 상수 Jet와 scalar를 서로 같은 연산으로 가정하지 않는다.
|
||||
|
||||
`--accumulation-bench`는 여섯 연산 종류마다 3,000회, 전체 결과 채널 checksum을
|
||||
소비한다. Fixture 준비는 제외하고 연산의 할당·해제와 checksum은 포함한다. 이 일반
|
||||
실행물의 시간과 별도 복사 source의 Hessian materialization 계측을 구분한다.
|
||||
Unmasked 최초 trap은 별도 SEH/process 관측으로, 곱셈 단계와 실제 덧셈 단계의
|
||||
검증을 구분한다. 전체 모델 시간·stack 예약 감소를 뜻하지 않는다.
|
||||
|
||||
Reference in New Issue
Block a user