어설픈 최적화는 아예 하지를 마라

옛날 옛적 C 포인터를 처음배울 때, 배열과 포인터의 유사성에 대해서 배웠다. 그리고 배열로 표현된 코드도 결국 다 포인터로 바뀐다는 것으로 배웠다. 맞다. 2차원 배열을 쓰더라도 실제 생성된 x86 코드를 보면 i*width+j와 같이 인덱스를 계산해서 포인터 연산으로 바뀐다. 그런데 여기서 이상한 믿음이 생겨나기 시작한다. 바로 포인터로 코딩하는 것이 배열보다 더 낫다라는 잘못된 믿음이다.

과거 (20년 전 쯤) 컴파일러가 그렇게 뛰어나지 못했을 때는 이런 배열보다 포인터를 더 선호하는 코딩 습관이 좋았을지는 모른다. 그러나 이제는 전혀 그렇지 않다.

특히 DSP를 타겟으로 하는 임베디드 시스템에서는 이런 코딩 습관이 더 심했다고 한다. 그래서 루프안에서 배열을 그대로 쓰기보다는 포인터로 풀어헤쳐서 짜여진 코드가 많다고 한다.

그러나, 이러한 코딩 습관으로 만들어진 코드들은 최신 컴파일러가 컴파일하기 매우 힘들다. 포인터를 많이 쓰면 쓸 수록 C/C++ 컴파일러는 최적화하기 더욱 힘들어진다.

어설픈 최적화는 안 하는 것보다 더 못하다. 괜히 코드를 더 복잡하게 만들어 유지 보수에도 어려울 뿐만 아니라 컴파일러 입장에서도 명확하게 그 의도를 파악하지 못해 최적화 기회를 많이 놓칠 수 있다.

이쯤에서 Knuth 선생님의 말씀 하나를 인용한다.

We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil." (Knuth, Donald. Structured Programming with go to Statements, ACM Journal Computing Surveys, Vol 6, No. 4, Dec. 1974. p.268.)

우리는 조그마한 효율성에 대해 고민할 필요가 없다. 대략 97%의 경우, 어설픈 최적화는 모든 악의 근원이라 말하고 싶다.

30년 전에 하신 이 말씀은 여전히 유효하다.

그래서 어떤 이들은 포인로 짜여진 코드를 배열형태로 다시 복원을 하여 고차원의 최적화를 시도하기도 한다. 또, 많은 사람들이 /4 대신에 >>2와 같은 코딩을 많이 한다. 당연히 정수 나눗셈보다 (경우에따라 수십사이클이 걸림) 비트 연산 (대부분의 경우 한 사이클)이 훨씬 빠르다. 그러나 걱정할 필요가 없다. 컴파일러가 이 정도는 알아서 최적화를 다 해준다. 루프 안에 반복적으로 계산이 되지만 그 값이 바뀌지 않는, loop invariants도 다 알아서 밖으로 빼준다.

쉬운 문제는 아니다. 이 말을 오해해서도 안된다. 하고 싶은 말은 정확하지 않은 최적화에 대한 소문만 듣고 하지 말라는 것이다. 언제나 의심이 되면 직접 컴파일러가 만들어주는 어셈블리를 보면서 확인을 해야할 것이다. 여기서 다시 한번 프로그래머는 쪼잔해질 필요가 있다는 나의 주장을 재차 역설한다.

by object | 2008/06/28 16:50 | 컴퓨터 | 트랙백 | 핑백(1) | 덧글(9)
트랙백 주소 : http://minjang.egloos.com/tb/1953491
☞ 내 이글루에 이 글과 관련된 글 쓰기 (트랙백 보내기) [도움말]
Linked at 미친병아리가 삐약삐약 : 20.. at 2008/06/30 15:36

... 을 해야할지.. 지금 경찰과 정부는 제정신 아니래두.. 내 몸 다치는건 좋은데, 왜 스스로 의사결정 능력이 없는 애들을 데리고 나가냐고.. 지금은 그럴 상황이 아니다.. 어설픈 최적화는 아예 하지를 마라 We should forget about small efficiencies, say about 97% of the time: premature optimizati ... more

Commented by 파파울프 at 2008/06/28 20:07
저 명언은 꼭 컴퓨터 프로그래밍이 아니더라도 적용할 만 하겠는걸요?
Commented by object at 2008/06/29 11:54
미국 쇠고기 문제도 어떻게 보면 어설프지는 않지만 과도한 최적화가 나은 비극이죠. 이건 미국 곳곳에서 볼 수 있습니다. 비용최소라는 오직 하나의 목표를 위해 무수히 많은 최적화를 수행했는데 그 결과 중 부작용이 상당히 많죠. 사료 값 아끼기 위해 적용한 최적화가 vCJD의 결국 원인이죠.
Commented by 세라비 at 2008/06/29 10:47
문제는 역시 보수적이고 나이든 C 프로그래머들... 컴파일러의 발전을 잘 믿으려 하지도 않고, '증거를 내놔라' 하는 식이더군요. 간단한 프로그램으로 증거를 내놔도 자신의 애플리케이션에서는 어떨지 알 수 없다는 식이구요.
Commented by object at 2008/06/29 11:52
14년 마다 컴파일러에 의해 프로그램의 속도가 2배씩 빨라진다는 조사 결과도 있지요. 그렇게 놀라운 일이 아닙니다. 여전히 VC++ 6을 고수하는 분들은 "도대체 컴파일러가 얼마나 좋아졌어?" 라고 반문하기 일쑤죠.

컴파일러 테크닉 중 대표적으로 SSE3 명령을 자동으로 이용해서 코드를 만들어주는 기능이 있습니다. 이걸 잘 이용하면 그냥 앉아서 성능을 수십퍼센트, 혹은 두배 세배의 성능을 얻어냅니다.
Commented by 몽몽이 at 2008/06/29 19:54
헑.
불학무식한 소인에게 SSE3가 무엇인지 깨달음을 주시옵소서...
수십퍼센트 혹은 두배 세배라니 지옥에 가서라도 찾아와야 할 물건인듯 하옵니다.
Commented by piloteer at 2008/07/02 09:32
SSE3은 요즘의 x86계열 cpu가 지원하곤 하는 명령어셋입니다.
성능 가속에 중요한 역할을 하지요......래봤자 제 컴퓨터엔 들어있지도 않지만 onz
Commented by object at 2008/06/29 20:04
SSE3는 x86에 있는 SIMD 확장이지요. MMX부터 시작해서 SSE, SSE2, SSE3, SSE4, SSE4.1까지 발전되고 있습니다. 일종의 벡터처리라고 보면 됩니다. 예를 들어, 3차원 벡터 덧셈을 한다면 for 루프 돌면서 덧셈을 해야겠죠. 그런데 SIMD 명령을 이용해서 이 덧셈을 동시에 합니다. 왜냐면 벡터 덧셈은 각 스칼라의 합이 모두 데이터 병렬성이 있어서 동시 실행이 가능하죠. 이런 벡터 연산을 빠르게 하기 위해 나온 것이 Single Instruction Multiple Data 명령어셋입니다.

인텔컴파일러는 코드를 보고 SIMD 명령으로 대체 가능한 부분은 자동으로 바꿔주는 최적화 기능이 있습니다. 이걸 automatic-vectorization이라고 합니다. 예로 든 벡터 덧셈이 원래 루프 3번 돌아야 했다면 (물론 루프 풀기로 실제 루프는 대부분 사라지겠습니다만) SIMD SSE를 쓰면 한 방 (한 싸이클)에 처리가 되죠.

일반적인 프로그램에서는 성능 향상이 눈에 띄지는 않을 겁니다. 그러나 행렬 곱셈과 같은 산술 연산이 많은 곳에서는 (부동소숫점 정수연산 가리지 않습니다) SIMD를 이용하는 것이 매우 효과적입니다. 프로그램 성격에 많이 좌우됩니다.
Commented by object at 2008/06/29 20:09
Automatic-vectorization에 대해서 곧 글 하나 올릴 예정입니다. 실제 벤치마크 결과까지 인용해서 조만간 올리죠. http://en.wikipedia.org/wiki/Vectorization#Automatic_vectorization
Commented by nineye at 2008/07/16 16:35
예전에 최적화 관련 글을 봤을 때, 기억에 남는 부분이 있었는데 그것은 어떤 함수에 객체를 인자로 넘길 때, 값을 넘길 것이냐 참조를 넘길 것이냐.. 에 대한 내용이었습니다. 물론 그때까지의 생각은 값을 넘기면 제어가 함수로 넘어갈 때, 그 인자를 함수내에서 독립적으로 사용할 수 있도록 동일한 값을 가지는 임시 객체를 만들어서 사용한다고 알고 있었죠(학교에서 그렇게 배우고 성능 최적화의 기본이라고 강조하니까...ㅋ). 그런데 그 글을 읽으니 함수내에서 그 인자가 값으로 넘어오더라도 그 객체의 값을 변경시키지 않는다면 컴파일러에서 그 인자를 참조로 만들어 사용한다는 사실을 알았습니다. 그 글은 그리 오래 되지 않았지만 컴파일러에 그 부분이 적용된 것은 1997년?(기억이 잘 안남) 정도라고 하니 수년간 잘못 알고 있었다는 것이죠.. 물론 모든 컴파일러가 그렇다는 것은 아니니 지금까지의 코딩 습관대로 값과 참조를 구별하여 넣는 것이 좋은 습관이겠죠.. 그런데 여기서 갈등이 되는게, 과연 모든 컴파일러가 위의 내용처럼 처리를 해준다고 해서 코딩 습관을 바꿔야 하냐? 에 대해서는 애매하더라구요.. 아마 보수적인 프로그래머들도 이런 점에서 갈등을 하겠죠.. 물론 자신이 처리한 방식이 컴파일러가 개선되면서 오히려 더 최적화 되지 않는 방식이라면 당연 수정할 필요가 있을 것 같지만, 이렇게 하든 저렇게 하든 성능에 영향이 없다면 명시적으로 인자를 어떻게 사용할 것인지 코드로서 표현하는게 더 좋은 것 같습니다...

:         :

:

비공개 덧글

<< 이전 페이지 다음 페이지 >>





by object 여기는 공사중....
최근 등록된 덧글
최근 등록된 트랙백
VisualStudio 2005에서 Gui..
by 셈말짓기
SSD와 WD의 벨로시랩터
by 정보와 휴식...그리고 미래

한RSS 구독자수 website counter

한RSS에 추가

Add to Google

rss

skin by 이글루스