Determinism과 네트워크 동기화
1. 부동소수점 연산은 Deterministic한가?
Determinism과 네트워크 동기화
1. 부동소수점 연산은 Deterministic한가?
대부분의 현대 CPU와 GPU는 IEEE 754 부동소수점 표준을 따르고 있습니다. IEEE 754는 컴퓨터에서 부동소수점(실수) 숫자를 표현하고 연산하는 방식을 정의하는 국제 표준입니다. 이 표준은 부동소수점의 표현 방식과 연산 규칙을 정의하여 서로 다른 하드웨어 간의 연산 일관성을 기대하게 됩니다. 그렇지만 현실은 그리 간단하지 않습니다. 부동소수점 연산은 근사치 계산으로, 완벽한 정확성을 기대하기 어렵고, 하드웨어마다 미세한 차이가 존재합니다.
표준에서 보장하는 것
- 기본적인 수 표현 형식
- 산술 연산 (덧셈, 뺄셈, 곱셈, 나눗셈, 제곱근) 결과의 근사값 계산 방식
- 반올림 동작
- NaN, 무한대 처리 방법
표준에서 보장하지 않는 것
- 연산의 완전한 결정론성 (예: 연산 순서에 따른 미세한 차이는 발생 가능)
- 삼각 함수, 지수 함수 등의 수학 함수 구현 (각 라이브러리 별로 다름)
- 특정 하드웨어의 내부 확장 정밀도 (x87 80비트 등)
- 병렬 연산에서의 연산 순서 (SSE , AVX, NEON)
2. 표준은 있지만 구현은 다름
- x86(x87 FPU)과 SSE/AVX는 같은 연산이라도 내부 정밀도(80bit vs 64bit)가 다를 수 있습니다. x87 FPU는 80비트 확장 정밀도, SSE는 64비트 정밀도를 사용해서 연산 결과 차이 발생할 수 있습니다.
- ARM의 VFP, NEON, Apple Silicon의 연산 방식도 세부 동작이 상이합니다.
- 컴파일러 최적화 옵션 (
/fp:fast,-ffast-math)이 활성화되면 엄밀한 IEEE 규칙이 깨질 수 있습니다.
즉, 같은 코드를 같은 입력으로 실행하더라도 다음 요소에 따라 결과가 달라질 수 있습니다.
- CPU 아키텍처
- 연산 명령어 (x87 vs SSE2 vs AVX vs NEON)
- 컴파일러 종류 및 플래그
- 최적화 수준
- 연산 순서 (Associativity 문제)
3. Determinism 이 깨지는 사례
부동소수점 연산 관련 칩셋 버그
- Bulldozer 기반 AMD CPU에서 일부 SIMD 명령어(예: AVX) 부동소수점 연산 결과가 미세하게 달라지는 오류 보고.
- Cortex-A53 에서 드물게 부동소수점 명령어가 예기치 않은 동작을 하는 버그
- 펜티엄 CPU의 부동소수점 나눗셈(FDIV) 명령어가 특정 입력에 대해 잘못된 결과를 반환.
컴파일러 최적화가 바꿔버린 연산 순서
수학적으로는 같아 보이지만, 부동소수점 연산의 특성상 연산 순서가 결과에 영향을 줄 수 있습니다. 특히 -ffast-math 같은 플래그가 켜져 있다면, 더 과감한 최적화가 이루어집니다. 컴파일러 버전이나 설정에 따라 값이 달라질 가능성도 있습니다.
float a = x * x * x;
// 위 코드를 컴파일러는 다음처럼 최적화할 수 있습니다:
float a = x * (x * x);
수학 함수는 표준이 아니다
sin, cos, exp, log 같은 수학 라이브러리 함수들은 IEEE 754 표준에 속하지 않습니다. 즉, 플랫폼이나 구현체마다 결과가 달라질 수 있습니다.
float result1 = powf(0.7f, 2.3f); // x86 기반 머신에서 실행
.............................................................
float result2 = powf(0.7f, 2.3f); // ARM 기반 머신에서 실행
.............................................................
assert(result1 == result2); // 네트워크 동기화 후 비교시 실패 가능성 있음
powf()는 내부적으로 로그와 지수 계산을 포함- 구현체(libm, compiler builtins, etc.)가 다르면 값이 미세하게 달라짐
glibc,libm,DirectXMath,Intel SVML등 구현 방식 다양- 심지어 동일 플랫폼의 다른 런타임에서도 결과 불일치 가능
SIMD, 병렬 처리로 인한 연산 순서 변화
멀티스레딩 또는 SIMD(SSE, AVX)로 연산을 병렬화하면, 내부적으로 연산 순서가 달라질 수 있습니다. 동일한 데이터라도 스레드 배치나 스케줄링 순서에 따라 합산 순서가 달라지고, 결과 값이 달라질 수 있습니다.
float sum = 0;
#pragma omp parallel for reduction(+:sum)
for (int i = 0; i < N; ++i) {
sum += array[i];
}
서브노멀 값, 라운딩 모드의 차이
- 일부 CPU는 서브노멀 값(denormals) 을 플러시(0으로 처리)합니다.
- 라운딩 모드(
nearest,zero,down,up)가 다르면 결과 값이 달라질 수 있습니다. flush-to-zero,DAZ(denormals are zero)플래그 여부에 따라 결과가 미묘하게 달라질 수 있음
float vs double: 중간 정밀도의 함정
어떤 환경에선 내부적으로 double로 승격시켜 중간 연산을 하고, 다시 float로 다운캐스트하는 경우도 있습니다. 이 과정에서 정밀도 차이로 인해 결과가 바뀔 수 있습니다.
float a = sqrtf(x); // 일부 플랫폼에선 내부적으로 double로 계산
같은 플랫폼, 다른 런타임
같은 코드라도 런타임 환경에 따라 수학 연산 결과가 달라질 수 있습니다.
- Windows에서 MSVC + CRT vs clang + libc++
- Linux에서 glibc vs musl
- Android에서 NDK 버전 차이
역제곱근 추정과 Newton-Raphson 반복
Newton-Raphson 반복법은 비선형 방정식의 근(해)을 빠르게 근사하는 수치해석 기법입니다. 이런 추정 기법은 빠르지만 매우 부정확하며, 반복 횟수나 방식에 따라 결과가 달라지는 경우가 많습니다. 결정론을 요구하는 환경에서는 반복 횟수와 보정 방식을 고정해야 합니다.
float invSqrt = Q_rsqrt(x); // Quake inverse sqrt
4. 결정론을 확보하기 위해 해야 할 일
정수 기반 고정소수점(Fixed-point) 연산
- float 대신 int32/int64를 사용
- 소수점 위치를 고정하여 연산의 일관성 확보
- 예:
Fixed32 = int32 * 1000→ 3자리 정밀도
커스텀 수학 라이브러리
- SIMD 최적화 비활성화
- 모든 수학 연산을 직접 정의
sin/cos/pow/exp등도 테이블 기반으로 작성
플랫폼 간 유닛 테스트
- 동일한 입력으로 연산 결과 비교
- 입력/출력 로그를 저장하고 재실행 가능하게 구성
연산 결과를 해시하여 검증
- 매 프레임 상태를 해시로 비교 → 동기화 오류 탐지
- 동기화 오류 발생시 가장 최근의 동기화 체크포인트로 Rollback
5. 멀티플레이 게임 네트워크 동기화
네트워크 멀티플레이 게임에서 결정론이 보장되면, 서버와 클라이언트 간에 전체 게임 상태를 매번 주고받을 필요 없이 플레이어 입력(명령)만 주고받아도 모든 클라이언트가 정확히 같은 게임 상태를 갖게 됩니다. 이 방식은 게임 상태를 반복해서 전송하지 않아도 동일한 결과를 재현할 수 있기 때문에 네트워크 부하를 줄이고 지연(Latency)을 감소시키는 데 효과적입니다.
멀티플레이 게임의 동기화 방식은 CAP 이론의 세 가지 속성 — 일관성(Consistency), 가용성(Availability), 네트워크 분할 허용(Partition tolerance) — 을 어떻게 균형 있게 고려하는지에 따라 나뉩니다.
멀티플레이어 게임에서 사용되는 주요 동기화 방식은 다음과 같습니다. 이러한 방식들은 게임 장르에 따라 적절히 선택하거나 조합하여 사용됩니다.
Lockstep (입력 동기화) 방식
- CP (Consistency + Partition Tolerance) ✅ C (일관성): 항상 동일한 결과 ✅ P (분할 허용): 분할은 감지하되, 일관성을 위해 진행 멈춤 ❌ A (가용성): 낮음
- 사례: RTS 장르 (예: Age of Empires, StarCraft)
- 특징: 모든 클라이언트가 동일한 입력을 동시에 수신하고, 이를 로컬에서 처리
- 장점: 완전한 일관성(Consistency) 확보
- 단점: 한 명이라도 네트워크 지연이 발생하면 전체 게임 진행이 지연됨 → 가용성(Availability) 저하
- 보완책: 주기적인 Checksum으로 상태 검증 및 동기화 오류 감지 → 문제가 발생한 시점 이전으로 롤백 후 입력을 재적용하여 재시뮬레이션
Rollback (Rewind & Replay) 방식
- AP (Availability + Partition Tolerance) ✅ A (가용성): 지연되어도 로컬 반응은 계속 ✅ P (분할 허용): 네트워크 일시 중단에도 플레이 가능 ❌ C (일관성): 즉시 보장되지 않음 (Eventually consistent)
- 사례: 격투, 액션, 레이싱 게임 등 빠른 반응이 중요한 장르
- 특징: 입력이 지연된 경우 예측 입력으로 먼저 시뮬레이션을 진행하고, 실제 입력이 도착하면 불일치 여부를 판단해 필요 시 롤백 및 재시뮬레이션
- 장점: 플레이어가 체감하는 지연이 거의 없음 → 가용성 높음
- 단점: 예측과 실제 입력이 크게 다를 경우, 보정 과정에서 화면이 순간적으로 튀는 현상 발생. 완전한 일관성은 나중에 맞춰짐 (Eventually Consistent)
하이브리드 사용 사례
- 입력은 Rollback 기반으로 동기화, 로컬 피드백(사운드, 이펙트)은 클라이언트에서 예측 실행 (Prediction)
- 물리/애니메이션 상태는 Lockstep 으로 강하게 검증
- 각 기능별 재현 가능한 상태 동기화 체크포인트 사용
부동소수점 Determinism 참고 자료
Floating-Point Determinism Understanding Determinism Part 1: Intro and Floating Points How are deterministic games possible in the face of floating-point non-determinism? Floating Point Determinism | Gaffer On Games What Every Computer Scientist Should Know About Floating-Point Arithmetic
멀티플레이 게임 동기화 참고 자료
[embed]
[embed]
메타데이터
- post_id
- 6acbdbdacbb5
- slug
- determinism과-네트워크-동기화-6acbdbdacbb5
- url
- https://medium.com/@3devnote/determinism%EA%B3%BC-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EB%8F%99%EA%B8%B0%ED%99%94-6acbdbdacbb5
- canonical_url
- https://medium.com/@3devnote/determinism%EA%B3%BC-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EB%8F%99%EA%B8%B0%ED%99%94-6acbdbdacbb5
- author_url
- https://medium.com/@3devnote
- status
- ok
- fetched_at
- 2026-07-19 17:36:43