순회 시간을 재기 전에 확인할 것

C++ 순회 코드의 성능을 판단할 때, 나는 측정할 작업, 데이터 접근 조건, 전체 프로그램에서 차지하는 비중을 차례로 확인하려 한다. 짧아진 실행 시간을 실제 개선으로 받아들이려면 이 세 조건이 이어져야 한다. 특히 같은 데이터를 반복해서 읽은 결과에는 캐시가 데워진 상태라는 조건이 붙는다.

1. 무엇의 시간을 줄이려는지 정한다.

먼저 목표가 요청 하나의 지연시간인지, 일정 시간 동안 처리하는 작업량인지 구분한다. 순회 함수의 실행 시간은 그 목표를 설명하는 일부일 수 있다. 함수가 빨라졌다는 수치만으로 사용자가 기다리는 시간도 그만큼 줄었다고 적지는 않겠다.

목표를 정한 다음에는 측정 입력이 그 목표에 대응하는지 확인해야 한다. 입력 크기와 접근 패턴이 달라지면 메모리를 읽는 비용도 달라질 수 있기 때문이다. 편하게 반복할 수 있는 입력이라는 이유만으로 실제 작업을 대표한다고 볼 수는 없다. 무엇을 대표하도록 고른 입력인지부터 명시할 필요가 있다.

2. 반복 측정에서 캐시 조건이 바뀌는지 본다.

입력을 고정해도 실행 조건까지 고정되는 것은 아니다. 앞선 순회에서 읽은 데이터가 캐시에 남으면 다음 순회의 메모리 접근 조건이 달라진다. 따라서 반복 횟수를 늘려 얻은 안정적인 숫자가 어느 상태의 비용인지 살펴야 한다. 수치가 덜 흔들린다는 사실과 필요한 비용을 측정했다는 판단은 별개다.

가령 캐시에 들어갈 만큼 작은 벡터 하나를 계속 순회하는 측정을 생각할 수 있다. 실제 성능 결과가 아니라 조건을 확인하기 위한 가정이다. 뒤쪽 반복에서는 이미 캐시에 올라온 데이터를 읽을 수 있다. 실제 프로그램에서는 매번 다른 데이터를 읽는다면, 이 측정은 그때의 접근 비용을 작게 보여 줄 수 있다.

이 가정에서는 반복 횟수보다 데이터 재사용 조건을 빠뜨리면 안 된다. 같은 벡터를 되풀이해 읽는 작업이 실제 용도라면 따뜻한 캐시의 결과도 유효하다. 반대로 재사용하지 않는 용도에 적용하려면 그 차이를 반영한 측정이 필요하다. 캐시를 무조건 비우는 방식으로 통일할 이유도 없다.

3. 측정한 순회가 전체 실행에서 차지하는 자리를 확인한다.

캐시 조건이 실제 용도와 맞더라도 함수 하나의 수치만으로는 개선 효과를 정할 수 없다. 이제 그 함수가 전체 프로그램의 주요 비용인지 확인한다. 국소 측정은 변경의 효과를 구분하는 데 유용하지만 어느 부분을 먼저 바꿀지는 전체 실행을 측정해 판단해야 한다.

나는 여기서 함수의 단축 비율과 프로그램의 단축 비율을 따로 적고 싶다. 순회가 차지하는 비중을 모르면 둘을 연결할 근거가 부족하다. 대표 작업에서 해당 경로가 실제로 얼마나 실행되는지 확인한 뒤에야, 순회 코드에 더 손댈 가치가 있는지도 판단할 수 있다. 반복 측정을 정밀하게 하는 것만으로 이 비중이 드러나지는 않는다.

4. 결과를 적용할 범위를 남긴다.

전체 실행에서 효과를 확인했다면, 마지막으로 그 결과가 성립한 조건을 남겨야 한다. 입력 크기와 접근 패턴, 데이터 재사용 여부, 컴파일러와 하드웨어 환경이 여기에 포함된다. 다른 환경에서도 같은 차이가 날지는 다시 측정할 문제다. 연속 배치가 캐시에 유리할 수 있다는 설명도 특정 코드의 개선 폭까지 보장하지는 않는다.

적용 전에는 변경한 코드가 같은 결과를 내는지 마지막으로 확인한다. 가독성과 정확성을 훼손하면서 얻은 짧은 순회 시간을 그대로 채택하기는 어렵다. 내가 채택 근거로 삼을 결과에는 올바른 출력과 함께, 어떤 작업과 캐시 조건에서 전체 실행 시간이 줄었는지가 남아 있어야 한다.