1P by GN⁺ | ★ favorite | 댓글 1개
  • 1993년 고성능 PC급인 IBM PS/1 486-DX2 66MHz에서 원본 DOOM은 demo1 기준 21.5fps였고, fastDOOM은 같은 조건에서 30.1fps까지 올라 DOS 포트 최적화의 차이를 보여줌
  • fastDOOM은 Linux DOOM 코어, Heretic I/O, APODMX, DOOM.EXE 역어셈블 기반 Mode Y 그래픽 I/O를 조합한 PCDOOM v2에서 출발함
  • 52개 릴리스와 3,042개 커밋을 빌드·벤치마크한 결과, 성능 향상은 현대 컴파일러 하나가 아니라 작은 최적화의 누적에서 나왔음
  • 큰 폭의 개선에는 상태 표시줄 렌더링 생략, FixedDiv 인라인화, BSP 순회 최적화, visplane 렌더링 생략, 포인터 간접 참조 제거, 렌더러별 실행 파일 분리가 포함됨
  • 그래픽 모드는 CPU와 버스에 따라 유불리가 갈리며, 느린 CPU에서는 Mode Y가 유리하고 빠른 CPU·VLB/PCI 환경에서는 Mode 13h나 VESA direct가 나을 수 있지만 VESA 2.0 같은 제약이 남음

486-DX2에서 드러난 성능 차이

  • IBM PS/1 486-DX2 66MHz “Mini-Tower” 모델 2168에서 원본 DOOM을 doom.exe -timedemo demo1로 실행하면 1710 gametics in 2783 realtics가 나옴
    • DOOM은 fps를 직접 출력하지 않아 1710/2783*35로 계산해야 하며, 결과는 21.5fps
  • 같은 머신에서 fdoom.exe -timedemo demo1을 실행하면 1710 gametics in 1988 realtics, 30.1fps가 확인됨
  • doom2demo1처럼 더 무거운 맵에서는 원본 16.8fps에서 fastDOOM 24.9fps로 올라 48% 빨라짐
  • 다만 joystick과 network gameplay 지원은 제거되어, 모든 기능을 그대로 보존한 포트는 아님

fastDOOM이 나온 코드 계보

  • DOOM은 원래 NeXT Workstation에서 개발됐고, 대부분의 코어 코드와 작은 I/O 하위 시스템으로 나뉘어 포팅하기 쉬운 구조를 가졌음
  • 상용 DOS 버전은 id Software가 작성한 DOS I/O를 사용했지만, proprietary sound library인 DMX 의존성 때문에 1997년에 그대로 오픈소스화될 수 없었음
  • 공개된 코드는 Bernd Kreimeier가 엔진 설명용 책 프로젝트를 진행하며 정리한 Linux 버전임
  • DOS 버전 PCDOOM v2는 다음 조합으로 재구성됨
    • Linux DOOM의 코어
    • Heretic의 I/O
    • DMX를 흉내 내기 위한 APODMX
    • DOOM.EXE 역어셈블에서 역공학한 i_ibm.c 그래픽 I/O
  • fastDOOM은 이 PCDOOM v2를 출발점으로 삼음

릴리스와 커밋으로 추적한 성능 변화

  • Victor “Viti95” Nieto는 fastDOOM을 자주 릴리스하고 각 릴리스에 태그를 붙였으며, 하나의 커밋이 하나의 일을 하도록 관리함
  • fastDOOM의 Git 이력은 3,042개 커밋으로 구성되어 커밋별 성능 변화를 추적할 수 있었음
  • 52개 fastDOOM 릴리스, PCDOOM v2, 원본 DOOM.EXE를 내려받아 -timedemo demo1을 실행하는 RUN.BAT을 Go 프로그램으로 생성하고, mTCP NETDRIVE로 마운트해 테스트함
    • 조건은 DOOM.WAD, sound on, screen size 10임
    • 전체 스위트를 5번 실행해 평균 fps를 그래프로 그림
  • PCDOOM v2는 OpenWatcom 2로 빌드됐는데도 원본 DOOM.EXE 대비 개선 폭이 크지 않아, fastDOOM의 향상은 현대 컴파일러 사용만으로 설명하기 어려움
  • 파일 크기 그래프에서는 초기 작업이 코드 정리와 삭제를 통해 더 가볍게 만드는 방향이었음
    • bf0e983: sound recording 제거
    • 5f38323: error code 문자열 삭제
    • 8b9cac5: TASM을 NASM으로 교체

성능을 끌어올린 릴리스별 변경

  • 전체 3,042개 빌드를 모두 timedemo하면 약 9일이 걸릴 수 있어, 속도 향상이 컸던 v0.1, v0.6, v0.8, v0.9.2, v0.9.7을 중심으로 커밋 단위 벤치마크가 진행됨
  • fastDOOM v0.1

    • v0.1220개 커밋으로 구성됨
    • 가장 큰 패치는 build 36의 e16bab8
      • “Crispy optimization”은 상태 표시줄 퍼센트가 바뀌지 않았을 때 렌더링을 no-op으로 만들어 scrap buffer 렌더링과 화면 blit을 피함
      • 이 변경 하나로 2fps 상승이 확인됨
    • build 167의 a9359d5FixedDiv를 매크로로 인라인화함
    • build 207의 9bd3f20는 PSX Doom 최적화를 가져와 BSP 순회 방식을 개선함
    • build 212의 dc0f48e는 수평 표면 렌더링 함수 R_MakeSpans를 인라인화함
    • 전체 커밋 중 삭제 커밋이 100개로, 약 절반이 코드 삭제였음
  • fastDOOM v0.6

    • v0.6은 33개 커밋으로 구성됨
    • build 342의 22819fd는 불필요한 visplane 렌더링을 건너뜀
    • build 359의 40e0d4b와 build 360의 ccd296f는 player pointer 간접 참조를 줄임
    • build 369의 f29e665는 screenspace line splitter를 인라인화함
  • fastDOOM v0.8

    • v0.8282개 커밋으로 구성됨
    • sound system이 불안정해 sound 없이 timedemo를 수행한 뒤 fps를 보정함
    • 이 릴리스는 text-mode renderer에 초점이 있었고, build 670과 build 730에서 Crispy optimization이 빠지며 회귀가 발생함
    • 주요 개선은 다음과 같음
      • f279b7d: FDOOM.EXE, FDOOM13H.EXE 등 렌더러별 실행 파일 분리
      • 1874ee8: 컴파일러 디버깅 비활성화
      • 6aae724: Crispy optimization 복구
      • 1366ebf: 가능한 경우 더 적은 코드만 컴파일
  • fastDOOM v0.9.2와 v0.9.7

    • v0.9.2는 110개 커밋이며, skyflatnum 비교 최적화, Mode Y용 R_DrawColumn 최적화, R_DrawSpan 코드 정리가 주요 변경임
    • v0.9.7은 293개 커밋으로 소개됐지만 명령 출력은 294개를 보임
    • 이 릴리스는 여러 번 벤치마크해도 노이즈를 줄이지 못함
    • 주요 변경은 x86 ASM 변경 테스트, 386SX용 CPU 선택과 CR2 최적화, R_DrawSpan386SX용 ESP 최적화, ASM fuzz column 렌더링 기반 코드, 루프당 CMP 비교 제거 등임

Mode 13h, Mode Y, VESA direct의 선택 기준

  • fastDOOM은 386, 486, Pentium, Cyrix와 ISA, VLB, PCI 같은 다양한 CPU·비디오 버스 조합을 대상으로 여러 최적화를 탐색함
  • IBM PS/1 486-DX2 66MHz에서는 Mode Y 대신 Mode 13h를 쓰는 최적화가 더 느렸음
  • Mode 13h

    • VGA의 네 VRAM bank로 데이터를 분배하는 작업을 하드웨어가 처리해, CPU에는 단일 선형 320x200 framebuffer처럼 보임
    • VRAM에서 double buffering을 할 수 없어 RAM에서 버퍼링한 뒤 VRAM으로 다시 복사해야 하므로 바이트를 두 번 씀
    • 엔진은 VSYNC에서 block되어야 함
  • Mode Y

    • VGA bank를 개별적으로 접근할 수 있어 VRAM triple buffering이 가능하고, 바이트를 VRAM에 한 번만 직접 쓸 수 있음
    • bank 선택에는 느린 OUT 명령이 필요함
    • latch를 통해 두 VGA bank에 동시에 쓰면 수평 픽셀 복제가 가능해 low-detail mode를 얻을 수 있음
    • invisible Specter 렌더링은 VRAM readback이 필요해 훨씬 느림
    • John Carmack의 설명에서 DOOM은 320×200×256 VGA 모드에서 Mode X와 유사한 interleaved planar mode를 사용했고, 세 개의 display page를 순환함
    • video memory에 직접 texture mapping을 하면 많은 비디오 카드에서 10%~15% 추가 속도를 얻을 수 있었음
    • main memory buffering보다 tearing 없는 page flip도 가능했음
    • Heretic은 1994년에 출시됐고, 당시 하드웨어 변화로 Mode 13h가 더 매력적인 선택이 되어 Raven은 DOOM 엔진을 이 방향으로 수정함
    • fastDOOM은 사용자에게 여러 실행 파일을 제공함
    • FDOOM.EXE
    • FDOOM13H.EXE
    • FDOOMVBD.EXE
  • fastDOOM의 Mode 13h와 VESA direct

    • fastDOOM의 Mode 13h는 RAM의 단일 framebuffer에 렌더링한 뒤 장면 전체가 끝나면 VRAM으로 복사함
    • VSYNC를 강제하지 않아 flickering이 생길 수 있음
    • 느린 8-bit ISA bus에서는 변경된 픽셀만 전송하는 differential copy가 사용됨
    • 16-bit ISA, VLB, PCI 등 더 빠른 bus에서는 REP MOVS로 전체 backbuffer를 복사함
    • Viti95의 테스트 기준 486 CPU에 가장 좋은 모드는 320×200용 VESA direct modeFDOOMVBD.EXE
      • Mode Y의 장점과 Heretic의 최적화된 렌더링 코드를 결합함
      • 버퍼 전환 시 프레임당 한 번의 OUT을 제외하면 OUT 명령을 피함
      • LFB가 활성화된 VLB 또는 PCI 그래픽 카드와 VESA 2.0 지원이 필요하며, low-detail과 potato-detail mode에서는 느림
    • IBM 2168은 VESA 2.0을 지원하지 않아 FDOOMVBP.EXEFDOOMVBD를 실행하면 오류가 발생함

작동하지 않은 시도와 전체 인상

  • OpenWatcom의 프로세서별 플래그인 4r/4s3r/3s도 시도됐지만 중단됨
    • wcc386의 386 플래그와 486 플래그가 모두 시도됐고, 결과적으로 386 버전이 항상 더 빨라 보였음
  • Viti95는 fastDOOM의 컴파일러를 OpenWatcom v2에서 DJGPP, 즉 GCC로 바꾸는 목표도 갖고 있음
    • 같은 소스에서 GCC가 더 빠른 코드를 생성하는 것으로 나타났기 때문임
    • 또는 OpenWatcom v2가 성능 격차를 줄이도록 개선되는 것도 바람직한 선택으로 남아 있음
  • fastDOOM의 성능은 Crispy, PSX, GBA, Lee Killough의 기존 개선을 활용하고 새 최적화를 대량으로 추가한 결과임
  • Ken Silverman의 아이디어와 코드 일부도 UMC Green CPU용 렌더링 함수에 들어가 해당 하드웨어에서 큰 속도 향상을 냄
  • fastDOOM은 하나의 마법 같은 변경이 아니라, 수천 개의 작은 최적화가 쌓여 원본 DOOM보다 빠른 DOS 포트가 된 사례임

댓글과 토론

Hacker News 의견들
  • 병목은 대개 예상한 곳에 없으니 프로파일링과 측정이 필요하다는 좋은 예임
    상태 표시줄의 퍼센트 렌더링이라니, Doom 구조를 잘 아는 전문가에게는 비교적 뻔했을 수도 있지만 사전에는 절대 병목이라고 짐작 못 했을 것 같음

    • 예를 들면 앱이 알 수 없이 CPU 60%와 GPU 25% 를 쓰고 있었는데, 알고 보니 작은 CSS 애니메이션 때문이었던 사례가 있음
      https://www.granola.ai/blog/dont-animate-height
    • SNES용 Super Mario World에서도 플레이어 점수 표시가 매우 비효율적이라 프레임 시간의 약 1/6을 잡아먹던 최적화 사례가 떠오름
      최악의 경우 상태 표시줄에 점수를 표시하는 데 전체 프레임의 1/6 가까이를 써서 느려질 임계점이 낮아졌고, 패치는 점수 저장·표시 방식을 거의 상수 시간에 가깝게 바꿔 최선의 기존 경우보다도 조금 더 빠르게 만들었음
      https://www.smwcentral.net/?p=section&a=details&id=35746
    • 게임 개발자 입장에서는 이런 느려짐이 흔함
      투명도, 레이어링, 다시 그리기, 특히 메모리 할당 유발 때문에 UI 렌더링이 치명적일 수 있고, 다시 그리기 전에 이전 값과 새 값을 비교하는 방식이 큰 도움이 됨
      CSS에서도 레이어와 투명도가 병목이었던 프로젝트가 있었고, 그때는 레이어 수를 줄이는 쪽이 핵심이었음
    • 가장 좋아하는 예는 GTA Online 로딩 시간 문제로, 결국 10MB JSON 파일을 잘못 처리한 탓이었고 조직 밖의 사람이 추적해냈음
      6분짜리 로딩을 2분 미만으로 줄였음
      https://nee.lv/2021/02/28/How-I-cut-GTA-Online-loading-times...
    • npm이 화려한 터미널 진행 표시줄 때문에 원래보다 2배 느렸던 사건이 떠오름
      https://news.ycombinator.com/item?id=10974929
  • 실제 대상 독자는 아닐지도 모르지만, mTCP의 NETDRIVE가 흥미로웠음
    그렇게 오래전에도 쓸 만한 네트워크 스토리지 선택지가 있었을 줄은 몰랐는데, 찾아보니 https://www.brutman.com/mTCP/mTCP_NetDrive.html가 나와서 정말 멋짐
    NetDrive는 다른 머신이 호스팅하는 원격 디스크 이미지를 DOS에서 드라이브 문자로 붙은 로컬 장치처럼 접근하게 해주는 장치 드라이버라고 함

    • 90년대 초 학교의 컴퓨터실에서는 Mac Plus 약 25대가 AppleTalk으로 Mac II에 데이지체인 연결되어 있었고, 모든 Plus가 Mac II에서 파일시스템을 마운트했음
      엄청 느려서 학생들이 수업 시작 때 워드프로세서를 띄우느라 5~10분씩 날렸고, Xerox Alto도 드라이브에 네트워크 마운트를 썼음
      네트워킹이 있으면 누군가는 곧 파일 복사를 원하고, 가장 편한 방식은 로컬 파일시스템처럼 보이게 만드는 것임
      DOS는 내장 네트워킹이 없어서 이런 부분에서 뒤처졌고 직접 해야 할 일이 많았음
    • iPXE로 iSCSI 대상에서 DOS를 네트워크 부팅하는 깔끔한 트릭이 있음
      DOS 쪽 드라이버나 설정 없이 네트워크 블록 장치에 읽기·쓰기 접근이 가능해지는데, 공유처럼 쓰면 금방 망가지고 블록 장치에 가까움
      마법처럼 느껴지지만 iPXE가 BIOS를 패치해서 디스크 접근을 iSCSI로 보내는 것 같음
    • DOS 시절에도 NAS나 WebDAV 마운트가 있었는지 궁금함
      FTP와 telnet 같은 건 당연히 있었겠지만, 원격 마운트가 실제로 쓰였는지 아니면 낮은 대역폭 때문에 불가능했는지 궁금함
  • Ken Silverman이 참여한 GitHub 스레드는 보물 같음
    FastDOOM 작성자와 Ken이 난해한 486 레지스터와 클럭 사이클 효율을 파고드는 과정을 보는 게 놀라움
    Doom이 아직도 성능 개선을 받도록 챙기는 사람이 있어 반가움

    • KenS를 정말 오랜만에 떠올렸는데, 90년대에는 Duke3D 모딩 장면에서 아주 활발히 활동했음
      그 스크립팅이 말 그대로 첫 “코딩”이었고, 어떤 의미에서는 커리어와 재산 전체를 KenS에게 빚진 셈이라 멋짐
    • 작년에 비슷한 2.5D 렌더링 엔진을 만들다가 Build Engine의 잘 알려지지 않은 부분에 대해 Ken Silverman에게 이메일을 보냈는데, 마치 어제도 그걸 작업한 사람처럼 답해줬음
    • 그 안에는 진짜 보석 같은 내용이 있음
      특히 메모리 접근이 매우 느린 386SX와 캐시 없는 386DX에서 CR2와 CR3를 스크래치패드 레지스터처럼 쓰는 아이디어가 좋았음
      인터럽트를 끄지 않고 ESP를 루프 카운터로 쓰는 기법도 천재적인데, 항상 유효한 스택 위치를 가리키게 보장하는 방식임
  • FastDOOM에서 여기서 많이 다뤄지지 않은 기능은 온갖 이상한 비디오 모드들임
    IBM MDA 텍스트 모드: https://www.youtube.com/watch?v=Op2tr2lGK6Y
    EGA & Plantronics ColorPlus: https://www.youtube.com/watch?v=gxx6lJvrITk
    고전적인 파랑·분홍 CGA: https://youtu.be/rD0UteHi2qM
    ‘ANSI from Hell’ 해킹을 쓴 CGA 320x200x16: https://www.youtube.com/watch?v=ut0V1nGcTf8
    Hercules: https://www.youtube.com/watch?v=EEumutuyBBo
    대부분은 색상 재매핑 같은 작업 때문에 VGA보다 더 느리게 도는 듯함

    • Tandy Graphics Adapter도 지원해주면 좋겠음
      CGA로 돌려야 한다면 싫을 것 같고, 아직 살아 있었다면 Tandy 1000 TL/2용으로 286 빌드가 필요했을 듯함
    • 정말 멋지고, 게임의 이런 측면들이 왜 분리되어야 하는지 잘 보여줌
      백엔드 애플리케이션의 “현대적” Clean Architecture가 떠오름
    • IBM MDA 텍스트 모드는 끔찍함... 그래서 좋음
  • “IBM PS/1 486-DX2 66Mhz, ‘Mini-Tower’, model 2168. 십대 때 늘 갖고 싶었지만 살 수 없었던 컴퓨터”라는 부분이 인상적임
    1992년쯤에는 직접 조립한 네 번째 PC를 쓰고 있었고, Marlborough MA의 KCS 컴퓨터 쇼는 땜장이들에게 엄청난 자원이었다고 봄
    부품을 사서 PC를 만들고 한동안 쓰다가 팔고, 다시 부품을 사는 식이었음
    1992년 말에는 ULSI 487 수학 보조 프로세서가 붙은 486-DX3 100을 돌리고 있었고, 한동안은 캠퍼스에서 가장 빠른 PC, 어쩌면 가장 빠른 컴퓨터였다고 할 수 있었음
    여러 Pentium 모델보다 빨랐고 수학 오류도 내지 않았음
    마지막 빌드는 우등 논문용으로 가스/디젤 열병합 발전소를 21쪽짜리 Excel 스프레드시트로 시뮬레이션하느라 재계산 시간이 너무 길어서 정당화했음
    전공은 환경과학이었지만 커리어는 전부 컴퓨터가 됨

    • “와우”라고 할 일인가 싶음
      1992년에 그런 컴퓨터를 살 수 없었던 사람에게 굳이 힘들게 할 필요가 있었을까
      게다가 “DX3”라는 건 없고, 첫 100MHz 486인 DX4는 1994년 3월에 나왔으니 1992년 말에 돌렸다는 건 맞지 않아 보임
      우리 집 첫 컴퓨터는 1992년쯤 받은, 이미 너무 낡은 XT를 빼면 1995년 초에 산 66MHz 486-DX2였음
      수십 년이 지난 지금도, 3년의 핸디캡이 있었는데도 내 컴퓨터보다 빨랐다는 불가능한 컴퓨터를 이상하게 자랑하는 걸 보면 괜히 자존심이 상함
    • 확실히 추억이 떠오름
      1992년쯤 가난한 대학생이었고 신용조합에서 약 2,000달러를 대출받아 486 DX2-50을 샀음
      요즘 돈으로는 꽤 기본적인 컴퓨터에 4,000달러 이상을 쓴 셈이고, 그 녀석에 DOS와 Linux를 듀얼 부팅했음
    • 486DX에 487이라니, 487은 SX 칩에만 유용한 줄 알았음
      찾아보니 표준 487은 원래 486SX를 비활성화하고 대체하는 완전한 486DX였다고 함
      혹시 내가 몰랐던 다른 굉장한 보조 프로세서였던 걸까
    • “여러 Pentium 모델보다 빨랐고 수학 오류도 내지 않았다”면 완전한 자랑거리임
      제대로 압도한 것임
  • 자주 릴리스한 것뿐 아니라, Viti95는 한 커밋이 한 가지 일을 하고 각 릴리스에 태그를 붙이는 뛰어난 Git 규율을 보여줬음
    https://fabiensanglard.net/fastdoom/#:~:text=one%20commit%20...

  • “fastDOOM을 알기 전까지는 Ibuprofen을 먹으며 플레이해야 한다고 체념했다”에서 Ibuprofen이 무슨 뜻인지 모르겠음

    • 낮은 프레임률 때문에 생기는 두통이라는 뜻 아닐까
  • 작성자가 본다면, 문서 전반에서 John Carmack의 성이 “Carnmack”으로 오타가 나 있음

  • 현대 소프트웨어가 느리고 이런 최적화가 들어가지 않는 이유를 냉소적이지 않게 보면 표준화/최적화 가설로 설명할 수 있음
    어떤 것이 표준이 되면 최적화가 뒤따름
    표준의 테스트를 모두 통과하면서 가장 빨라지고 싶어지기 때문임
    Doom도 이제는 새 CPU든 토스터든 어디에나 포팅하는 표준 게임이 됐고, 이메일 프로토콜이나 브라우저 표준인 WebRTC, QUIC 등도 비슷함
    최신 웹 앱이나 Electron 앱이 빠르지 않은 이유는 탐색 단계에 있기 때문임
    매일 새 사용자 요구에 맞춰 업데이트되고, 성능 면에서는 방해되지 않을 정도로만 빠르면 충분함
    그래서 IRC 앱은 매우 빠르지만 Slack과 Teams는 늘 느릴 수밖에 없음

  • 버전을 되돌아가며 개선과 회귀를 확인하는 건 사소하지 않음
    어떤 최적화는 나중에 발견되는 버그를 만들 수 있고, 꼭 필요한 기능이 성능을 떨어뜨릴 수도 있음
    그래서 각 릴리스 전에 자동으로 도는 성능 테스트를 두면 삶이 쉬워지고, 성능 문제가 발견되면 평소처럼 회귀 테스트를 작성하면 됨
    하고 싶은 말은 결국 성능 테스트를 하라는 것임