Go 1.2부터 1.18까지의 성능
원문은 Ben Hoyt님이 에 게재했습니다. 이 블로그 구독하기
최근에 Go로 만든 AWK 인터프리터인 GoAWK의 성능을 개선했다. 트리 워크 인터프리터에서 바이트코드 컴파일러와 가상 머신 인터프리터 방식으로 전환한 덕분이다.
그 과정에서 Go 자체의 성능이 그동안 얼마나 개선됐는지 살펴보면 흥미로울 것 같다는 생각이 들었다.
Go로 작성된 프로그램이 빨라진 이유는 여러 가지다. Go 팀과 외부 기여자들이 컴파일러를 개선하고 런타임, 가비지 컬렉터, 표준 라이브러리를 최적화해 왔기 때문이다. 아래는 GoAWK를 Go 1.2(다운받을 수 있었던 가장 오래된 버전)부터 현재 베타 버전인 1.18까지 각 릴리스 버전으로 컴파일했을 때의 성능 비교다.
두 개의 AWK 프로그램으로 GoAWK를 실행해 테스트했다. AWK로 할 수 있는 일 중 서로 다른 극단을 대표하는 프로그램으로, 하나는 I/O와 문자열 처리, 다른 하나는 수치 연산이다.
첫 번째는 countwords로, 입력에서 단어 빈도를 세어 단어와 개수를 출력하는 문자열 처리 작업이다. AWK 스크립트에서 흔히 볼 수 있는 유형이다. 입력은 킹제임스 성경을 10번 이어 붙인 파일이며(성능 비교에 예전에 사용한 적이 있다), 코드는 다음과 같다:
{
for (i=1; i<=NF; i++)
counts[tolower($i)]++
}
END {
for (k in counts)
print k, counts[k]
}두 번째 프로그램은 sumloop로, 루프 카운터를 변수에 여러 번 더하는 타이트 루프다. 사실 AWK의 전형적인 쓰임새라고 하기는 어렵다:
BEGIN {
for (i=0; i<10000000; i++)
sum += i+i+i+i+i
}첫 번째 차트의 시간 수치는 x86-64 리눅스 노트북에서 측정한 초 단위 실행 시간이다(세 번 실행 중 가장 좋은 기록). Go 버전별 GoAWK 바이너리 크기를 보여주는 차트도 함께 실었다.
모두 실행하고 시간을 측정하는 데는 Python 스크립트를 사용했다. 차트는 다음과 같다(원하면 표로도 볼 수 있다):


아마 Go 1.3 시절에 쉽게 개선할 수 있는 부분들을 처리했던 것 같다! 릴리스 문서에 따르면 런타임, 가비지 컬렉터, 스택 처리 방식에 큰 변화가 있었다고 한다. 이후 countwords는 Go 1.7까지, sumloop는 1.9까지 꾸준히 개선되다가, 그 이후부터 오늘날의 1.18까지는 아주 점진적인 개선이 이어졌다.
깊이 파고들지는 않았지만, 내 추측으로는 countwords의 개선(적어도 1.3 이후)은 주로 표준 라이브러리 개선 덕분이고, “CPU 바운드”인 sumloop의 개선은 컴파일러 최적화 덕분이다.
최근 개선 중 하나는 1.17 버전에 있었는데, 스택 대신 함수의 인자와 반환값을 레지스터로 전달하도록 바뀐 것이다. 이는 GoAWK의 예전 트리 워크 인터프리터에는 상당한 개선이었다. 한 마이크로벤치마크에서는 38% 빨라졌고, 전체 마이크로벤치마크 평균으로는 17% 빨라졌다는 것을 확인했다.
흥미롭게도 GoAWK의 새로운 가상 머신 구현에서는 인자를 레지스터로 전달하는 변경으로 인한 눈에 띄는 개선이 없었다. 가상 머신이 다양한 옵코드를 디스패치하기 위해 큰 switch 문을 사용하지만 함수 호출은 거의 없기 때문이다. 이는 모든 표현식에 eval에 대한 (재귀) 함수 호출이 필요했던 트리 워크 인터프리터와 대조되며, 그래서 그쪽에서는 큰 성능 향상이 있었다.
펼쳐서 예전 트리 워크 인터프리터의 벤치마크 결과(표)를 확인해보자. 전반적인 추세는 거의 동일하지만, 1.17에서 레지스터 호출 방식 덕분에 실행 시간이 크게 줄어든 것을 볼 수 있다.

Go의 regexp 패키지 성능이 개선되기를 기대하고 있다. 현재는 꽤 느린 편이다. 정규식은 AWK 스크립트에서 많이 사용되므로, 개선된다면 실제 스크립트에서 GoAWK를 사용할 때 큰 차이가 날 것이다. 언젠가 내가 직접 도전해 볼지도 모르겠다.
전체적으로 countwords는 Go 1.2로 컴파일했을 때보다 지금 약 5배 빠르고, sumloop는 14배 빠르다! (다만 내가 GoAWK를 처음 릴리스했을 때는 이미 Go 1.11이었기 때문에, 초기의 큰 성능 향상을 직접 겪지는 못했다.)
Go처럼 활발히 개발되는 컴파일러라면, 그저 기다리기만 해도 다른 사람들이 힘든 일을 다 해줘서 성능 향상을 얻을 수 있다는 점이 멋지다. :-)
글을 무작위로 읽기
댓글
로그인하고 댓글 남기기