- 짝수/홀수 판별을
%없이 비교문 나열만으로 처리하려는 장난스러운 아이디어가 8비트에서 32비트까지 확장되며 컴파일러와 실행 파일 형식의 한계를 드러냄 - Python 코드 생성기로
if (number == n)을 자동 생성하자 8비트와 16비트 범위는 동작했지만, 32비트에서는 비교 대상이 약 42억 개로 폭증함 - 32비트 C 버전은 48시간 뒤 약 330GB C 파일을 만들었고, MSVC는 줄 번호 한계와 힙 공간 부족으로 컴파일에 실패함
- PE 실행 파일의 4GB 제약을 피하려고 x86-64 명령어를 직접 생성해 40GB 바이너리
isEven.bin을 만들고, Windows 메모리 매핑으로 실행 코드처럼 호출함 - 최종 프로그램은
atoi를strtoul로 바꾼 뒤 32비트 큰 값도 올바르게 판별했으며, 큰 입력은 Core i5 12600K·32GB 메모리·M.2 SSD 환경에서 약 10초 안에 반환됨
비교문만으로 짝수/홀수 판별하기
- 출발점은 소셜 미디어에서 본 코드 스크린샷으로, 고전적인 짝수/홀수 판별 문제를 modulus 연산 없이 풀려는 방식이었음
- 숫자마다
if (number == n)을 두고, 해당 숫자가 짝수인지 홀수인지printf로 출력하는 구조임 - 첫 C 예제는
uint8_t number = atoi(argv[1]);를 사용하고, 0부터 10까지 비교문을 직접 작성함 /Od로 최적화를 끄고 컴파일해 컴파일러가 알고리듬을 바꾸지 않도록 함0,4는even3,7은odd50,11,99는 아무 출력도 없었음
- 원인은 마지막
if이후 처리할 비교문이 없다는 점이었고, 더 많은 if 문장이 필요해짐
Python으로 if 문장 생성하기
- 모든 비교문을 손으로 쓰는 대신 Python으로 C 코드를 출력하는 메타 프로그래밍 방식을 사용함
- Python 스크립트는
for i in range(2**8)로 0부터 255까지 비교문을 생성함i % 2 == 0이면printf("even\n");- 아니면
printf("odd\n");
- 생성된 C 프로그램은 8비트 전체 범위에서 동작함
99는odd50은even240은even241은odd
16비트까지는 C 컴파일로 성공
- 같은 방식을
uint16_t와range(2**16)으로 확장함 - 생성된 C 파일은 약 13만 줄 규모였음
- MSVC로 컴파일한 뒤 여러 값에서 정상 동작함
21000은even3475는odd3은odd65001은odd65532는even
- 실행 파일 크기는 약 2MB였고, 31.8GB 메모리 PC에서는 문제가 되지 않았음
32비트 C 파일과 컴파일러 한계
- 다음 목표는
uint32_t와range(2**32)로 32비트 전체 범위를 비교문으로 처리하는 것이었음 - 32비트는 16비트보다 숫자 개수가 65,536배 많음
- Python 생성기를 48시간 실행한 뒤 약 330GB C 파일이 만들어짐
- MSVC 컴파일은 곧 한계에 부딪힘
warning C4049: 컴파일러 줄 번호 한계에 도달해 line number emission을 종료함- line number 한계는
16777215 fatal error C1060: compiler is out of heap space
- Windows의 Portable Executable(.exe) 형식도 4GB를 넘기기 어렵다는 제약이 있어, 40억 개 이상의 비교를 실행 파일에 담는 C 컴파일 경로는 막힘
- 관련 제약으로 PE 파일 최대 크기가 언급됨
직접 기계어를 생성해 실행하기
- 컴파일러와 실행 파일 형식의 한계를 피하기 위해 x86-64 명령어를 직접 바이너리로 출력하는 방식으로 전환함
- 목표 함수는 인자를
ECX로 받고 반환값을EAX로 주는IsEven형태였음XOR EAX, EAX로 기본 반환값을 홀수용 0으로 설정- 각 숫자마다
CMP ECX, i - 짝수면
INC EAX후RET - 홀수면 그대로
RET
- x86-64 assembly와 opcode가 사용됐고, 각 명령어의 opcode는 ChatGPT에 물어봄
- Python 스크립트는
isEven.bin을 바이너리로 열고, 0부터2**32 - 1까지 모든 숫자에 대한 비교 명령을 기록함 - 생성된
isEven.bin은 약 40GB였고, 32비트 숫자 전체에 필요한 약 42억 개 비교를 포함함
Windows 메모리 매핑으로 40GB 코드 호출
- 호스트 C 프로그램은
isEven.bin을 열고, 전체 파일을 읽는 대신 Windows API로 메모리 매핑함 - 실행 흐름은 다음과 같음
CreateFileA로isEven.bin을GENERIC_READ | GENERIC_EXECUTE권한으로 열기GetFileSizeEx로 64비트 파일 크기 확인CreateFileMapping에PAGE_EXECUTE_READ지정MapViewOfFile로 실행 가능·읽기 가능 매핑 생성- 매핑된 포인터를
int (*isEven)(int)함수 포인터로 캐스팅해 호출
- 이 방식은 40GB 파일 전체가 이미 메모리에 있는 것처럼 다루고, 실제 배치는 운영체제의 가상 메모리에 맡김
- 첫 테스트에서는 대부분 정상 동작했지만
4200000000에서odd가 나와 잘못된 결과가 발생함 - 원인은
atoi가 unsigned 큰 값을 제대로 처리하지 못한 점이었고,strtoul(argv[1], NULL, 10)로 바꾼 뒤4200000000은even,4200000001은odd로 출력됨
성능 관찰
- 작은 숫자는 즉시 결과가 나왔고,
2^32한계에 가까운 큰 숫자도 약 10초에 결과가 반환됨 - 테스트 환경은 Core i5 12600K, 32GB 메모리, M.2 SSD였음
- 계산 중 관찰한 SSD 최대 읽기 속도는 약 800MB/s였음
- 40GB 데이터를 디스크에서 읽고 물리 메모리에 매핑한 뒤 CPU가 캐시 이점을 거의 얻기 어려운 상황에서도 이 정도 속도가 나온 점이 놀라운 결과로 남음