개요

13편에서 좀비가 몇 마리 살아 있는지를 세는 값을 만들었다. 화면에 숫자 하나 띄우려던 거였는데, 그게 이 편에서는 자(尺)로 쓰인다.

원래 계획에 최적화 항목이 하나 있었다. 오브젝트를 매번 새로 만들고 부수는 대신 만들어둔 걸 돌려쓰는 것, 그리고 매 프레임 생기는 쓰레기 메모리를 없애는 것이다. 계획서에서 이 항목만 특별했다. 다른 항목은 “됐다/안 됐다”로 끝나는데 이건 전후를 숫자로 남길 수 있는 유일한 항목이라, 마감에 쫓겨도 이건 지키자고 못을 박아뒀었다.

그래서 오늘은 그 숫자를 재러 갔다. 그리고 세 번 세운 가설이 전부 틀렸다.

이번 글이 답할 질문. 계획한 최적화가 측정에 의해 폐기되면 그건 실패인가?


1. 재기 전에 조건부터 못 박았다

측정을 시작하기 전에 먼저 정한 게 있다. 어떤 상태에서 잴 것인가다.

이게 왜 필요한가. 전과 후를 비교하려면 전과 후가 같은 상황이어야 한다. 그런데 내가 직접 플레이하면서 재면, 매번 잡는 좀비 수가 달라진다. 어제는 100마리 잡고 오늘은 130마리 잡으면 숫자가 달라진 이유가 코드 때문인지 내 손 때문인지 알 수가 없다.

그래서 이렇게 고정했다.

항목 이유
플레이어 체력 9999 죽지 않아야 끝까지 간다
조작 안 함 손이 개입하면 매번 달라진다
시간 300초 전부 좀비가 쌓이는 걸 봐야 한다

체력을 9999로 올려 죽지 않게 해뒀다

그리고 이 조건을 코드가 아니라 문서에 적었다. 코드로 박아두면 나중에 반드시 되돌리는 작업이 생기고, 그 되돌린 기록이 커밋 로그에 남아 지저분해진다.

재기 전에 조건을 정하지 않으면, 재고 나서 나온 숫자가 무엇의 숫자인지 알 수 없다.


2. 첫 측정 — 지키기로 한 축이 첫 판에 흔들렸다

무적으로 300초를 버티게 하고 중간중간 프로파일러를 찍었다. 프로파일러는 한 프레임을 그리는 데 뭐가 얼마나 걸렸는지 뜯어서 보여주는 도구다.

지점 한 프레임 시간 그중 게임 로직 쓰레기 메모리
15~60초 1.7~1.9ms 0.18~0.23ms 0 B
90초 9.66ms 7.30ms 0 B
120초 (2:00) 16.01ms 12.79ms 0 B

60fps로 돌아가려면 한 프레임이 16.6ms 안에 끝나야 한다. 2분 지점에서 16.01ms니까 거의 다 썼다. 여기까지는 예상한 대로다.

문제는 맨 오른쪽 칸이다. 전 구간 0 B.

한 프레임 안을 펼쳐본 화면. 물리가 66.2%, 쓰레기 메모리는 전부 0 B

느려진 프레임을 펼쳐봤더니 이렇게 나왔다.

항목 비중 시간
2D 물리 계산 66.2% 10.60ms
내가 짠 스크립트 전부 9.7% 1.55ms

프레임 시간의 3분의 2가 물리다. 좀비끼리 서로 밀어내는 계산이다. 그리고 내가 없애겠다고 계획했던 쓰레기 메모리는 처음부터 끝까지 0 B였다.

없는 걸 없앨 수는 없다.

내가 줄이겠다고 계획한 비용이 측정에는 나타나지 않았다. 계획서에서 유일하게 숫자로 남길 수 있다던 항목이, 첫 측정에서 잴 게 없다는 답을 받았다.


3. 충돌을 껐더니 — 빨라진 게 아니라 안 쌓였다

물리가 3분의 2라면 답은 뻔해 보였다. 좀비끼리 부딪히는 걸 꺼보자.

유니티에는 어떤 종류끼리 충돌을 검사할지 정하는 표가 있다. 거기서 좀비와 좀비를 껐다. 그리고 다시 쟀다.

  충돌 ON 충돌 OFF
2:00 시점 프레임 시간 16.01ms 2.17ms
물리 비중 66.2% 상위 목록에서 사라짐

일곱 배 넘게 빨라졌다. 그런데 옆에 뜬 숫자를 보고 손이 멈췄다. 좀비 수가 267에서 더 안 올라간다.

왜 안 쌓이는가

좀비끼리 안 밀어내면 같은 자리에 몇 마리든 겹쳐 선다. 그리고 내 무기는 반경 3 안의 대상을 전부 때리는 방식이다.

  • 겹쳐 있음 → 한 번 휘두르면 수십 마리가 동시에 죽는다
  • 죽는 속도가 태어나는 속도를 따라잡는다
  • 267마리에서 평형

그러니까 나는 2132마리짜리 상황과 267마리짜리 상황을 비교한 것이다. 마리 수가 여덟 배 차이 나는 두 판을 놓고 “빨라졌다”고 한 셈이다.

충돌을 껐더니 빨라진 게 아니라, 충돌을 껐더니 좀비가 안 쌓였다.

숫자만 보면 완벽한 개선인데, 실제로 일어난 일은 게임 규칙이 바뀐 것이었다. 화면으로도 티가 났다. 좀비가 겹쳐서 한 덩어리로 보이고, 여러 마리가 한꺼번에 녹는다. 게임이 아니라 다른 게 됐다.

원복했다.

숫자가 좋아졌다고 개선이 아니다. 무엇과 무엇을 비교했는지부터 봐야 한다.


4. 변수는 마리 수가 아니라 밀집도

두 번 잰 걸 다시 보니 빠진 게 하나 있었다. 각 지점에서 좀비가 몇 마리였는지를 기록하지 않았다.

빠뜨린 게 아니라 못 잰 것이다. 그때는 화면에 좀비 수가 안 나왔다. 그 숫자를 만든 게 바로 13편이고, 그래서 그걸 붙이고 처음부터 다시 돌렸다.

이번엔 좀비 수와 프레임 시간을 같이 적었다.

시각 좀비 수 한 프레임 시간 물리
4:00 343 2.05ms
3:50 513 2.80ms 0.91ms
3:45 603 1.99ms
3:40 682 2.32ms
3:35 753 11.41ms 7.09ms (62.1%)
3:30 833 11.63ms 7.46ms (64.1%)

100마리 늘었는데 프레임 타임은 5배

682에서 753. 100마리도 안 늘었는데 프레임 시간이 다섯 배가 됐다.

마리 수에 비례해서 느려지는 거였다면 682에서 753은 10%쯤 느려져야 한다. 그런데 다섯 배다. 비례하지 않는다.

화면을 보니 답이 있었다

숫자만 보다가 화면을 봤다.

  • 3:40까지 — 좀비가 흩어져 있다
  • 3:35부터 — 플레이어 앞에 한 덩어리로 뭉친다

3:45, 603마리. 흩어져 있고 프레임은 1.99ms다

3:30, 833마리. 한 덩어리로 뭉쳤고 프레임은 11.63ms, 그중 물리가 64.1%다

밀어내기 계산은 겹친 애들끼리만 일어난다. 흩어져 있으면 검사만 하고 끝나고, 뭉치면 서로 밀어내는 계산이 실제로 돌아간다. 그리고 밀려난 애가 또 옆의 애를 밀고, 그게 연쇄된다.

같은 마릿수라도 흩어지면 싸고 뭉치면 비싸다. 비용을 결정한 건 마리 수가 아니라 얼마나 몰려 있느냐였다.

이상한 값 하나는 이상한 채로 남긴다

표를 보면 걸리는 데가 있다. 3:45의 603마리(1.99ms)가 3:50의 513마리(2.80ms)보다 빠르다. 좀비가 90마리 더 많은데 더 빠르다.

이유는 모른다. 프로파일러는 그 순간 한 프레임을 보여주는 거라 어쩌다 가벼운 프레임을 찍었을 가능성이 크다. 여러 프레임을 평균 낸 게 아니다.

모르는 채로 남긴다. 표에서 지우면 그래프는 예뻐지겠지만, 그건 재고 나서 결과에 맞춰 데이터를 고르는 짓이다.

안 맞는 값을 지우면 남은 값들의 신뢰도까지 같이 날아간다.


정리

  • 재기 전에 조건을 문서로 고정한다. 조작이 섞이면 전후 비교 자체가 성립하지 않는다.
  • 측정 조건은 코드가 아니라 문서에 적는다. 코드에 박으면 되돌리는 커밋이 반드시 따라붙는다.
  • 줄이겠다고 계획한 비용이 측정에 안 나올 수 있다. 이번엔 쓰레기 메모리가 전 구간 0 B였다.
  • 프레임 시간의 3분의 2가 2D 물리였다. 내가 짠 스크립트 전부를 합쳐도 9.7%다.
  • 충돌을 껐더니 빨라진 게 아니라 좀비가 안 쌓였다. 2132마리와 267마리를 비교하고 개선이라 부를 뻔했다.
  • 숫자가 좋아졌다고 개선이 아니다. 무엇과 무엇을 비교했는지부터 본다.
  • 비용을 결정한 건 마리 수가 아니라 밀집도다. 682에서 753 사이에 다섯 배가 뛰었다.
  • 안 맞는 값은 지우지 말고 신뢰도를 낮게 적어둔다.
  • 못 잰 것과 안 잰 것은 다르다. 1·2차에 좀비 수가 없는 건 그때 셀 방법이 없었기 때문이고, 그래서 3차를 다시 돌렸다.

참고 자료

한줄 평

  • 개선했다고 좋아할 뻔한 걸 좀비 수 하나가 잡아줬는데, 오늘 아침에 그걸 안 만들었으면 그냥 넘어갔을 것 같아 아찔했다..