과거 호환성과 성능 개선
최근 내 트윗 타임라인에 이런 글이 떴다. 미구엘 데 이카자 (@migueldeicaza) 의 트윗이 내게까지 리트윗되는 경우는 흔치 않기 때문에 눈에 띄었다.
굉장히 길고, 여러 주제가 섞여 있으니 정리해 보자면
- 아도비 플래쉬는 겹치는 주소 구역에 memcpy() 를 쓰고 있는 버그가 있다.
- 위 버그는 memcpy() 의 POSIX 세만틱을 위배한다. 카피 대상 주소 영역이 겹칠때는 memcpy() 가 아니라 memmove() 를 써야 한다.
- 하지만 BSD 시절부터 memcpy() 는 저런 경우에도 별일없이 동작해 왔다. 주소를 증가시키면서 복사하니까. 달리 말하자면 BSD 세만틱으로는 버그가 아니다.
- 그런데 최근 버전의 glib 는 주소를 감소하면서 복사하도록 memcpy() 를 바꿨다. POSIX 세만틱으론 문제가 없지만 BSD 세만틱 호환성은 버린 것이다.
- 왜 그랬나 하면, 인텔 아톰 CPU 의 로드/스토어 (Load/Store) 엔진이, 주소 증가 복사 경우 페이지 경계 넘어갈때마다 스토어/로드 충돌이 나는 줄 알고 파이프라인을 정지시켜서 성능이 떨어지기 때문이다. 주소 감소 복사시엔 이런 문제가 없다.
- 그래서 glibc 쪽에선 자신들의 버그가 아니라고 하고, 아도비 쪽에선 “딴 플랫폼은 문제 없는데? 우리 버그 맞음?” 하는 상황. 지금쯤은 상황파악 됐겠지만…
- 한편 사용자들은, 문제가 해결될때까진 LD_PRELOAD 트릭으로 memcpy() 를 바꿔써야 하는 상황.
- 더 무서운건 아도비 플래쉬 말고 다른 프로그램들 중에 이 버그가 몇개나 더 있을지 모른다는 것.
이 버그는 성능을 추구하는 과정에 어떤 일이 벌어질 수 있는지 잘 보여주는 예라서 인상이 깊었다. 참고로 나같은 우분투 사용자들은 glibc 를 쓰지 않으니 상관없다. 위 버그는 페도라 코어에 대한 버그질라에 등록되어 있다.
누구의 잘못이냐를 따지기는 곤란하다. 원칙적으론 아도비 쪽의 버그긴 하지만, 시스템의 가장 기초이며 커널과 가깝게 엮여 있는 libc 레이어에서 과거 호환성을 일부나마 버리는 결정을 쉽게 하는 것도 권장할 만한 일은 아니다. 하긴, 레드햇 쪽이 저런 결정을 내려 줬으니 다른 리눅스 플랫폼들은 주요 아플리케이션들의 버그가 모두 드러나서 고쳐질 때까지 기다렸다가 성능 개선을 할 수 있을 테니, 총대를 메줘서 고맙다고 할 지도. 그 동안 페도라용 브라우저 패키지들은 LD_PRELOAD 트릭을 일단 적용해 둬야 할 것이다.
한가지 재밌는건 리눅스 커널 내부에서 쓰는 memcpy() 는 사실 memmove() 라는 것이다. 즉 약간의 성능을 희생하고 API 를 단순화 시켜 놨다. 버그가 생기면 그냥 죽이면 되는 아플리케이션과는 달리 커널 드라이버 개발자가 실수했다간 큰일이니까. 하지만 커널 memcpy() 성능은 아플리케이션 경우보다 훨씬(!) 중요한데 그걸 알면서도 포기했다는 점은 생각해 볼 만 하다.
그런데, 로드/스토어 엔진은 그렇다 치고, 곧 필요할 데이타를 예측해서 미리 캐쉬에 로드하는 하드웨어 프리페처 (Hardware prefetcher) 가 저렇게 거꾸로 올라가는 경우도 잘 처리해 준다는게 신기하다. 저 패치를 제안한 사람이 인텔 사람이니까 아톰에서도 문제는 없겠지만, AMD 계열 CPU 도 과연 그러할지?
ps. 더 상세히 쓸까 하다가 어차피 누가 관심있겠냐 싶어 이만 총총.
glibc 문제 맞음.
메모리 영역이 겹치는지 체크해서 겹치면 memmove 를 호출하게 만드는게 뭐 어렵다고?
라이브러리 만드는 사람으로써 자세가 아님.
인라이닝 문제까지 결합되서 어쩔수 없을수도 있긴 하겠구먼.
monsa/ 글쎄? memcpy 같은 primitive한 library에서, 매번 호출될때마다 (원래는) 불필요한 체킹을 한번씩 하는 오버헤드를 들이는게 맞는건가? 파이프라인 스톨 한번 막아보겠다고 저짓을 하는 사람들 귀에 들어갈리가 없을 듯 한데?
재미있네요. 관심있는 사람 있으니까 이런거 더 써주셔도 된…
맞아요 관심있는 사람 많은데요? 후후 링크도 잘 봤는데 이렇게 3줄 아니 30줄 요약까지… 감사합니다.
그나저나 우분투는 glibc 안쓰나요? 몰랐네요 ;;; 이거 쓰고 시냅틱 뒤져보니 Embedded glibc 가 깔려있네요. 임베디드에 최적화 된거라는데 이러면 데탑이나 서버에서 퍼포먼스 문제 없나…
cesia// 어차피 스택에 카피해서 오는 변수 범위체크하고 다음 코드를 로컬리티 내에서 처리한다고 하면 페널티 없다고 봐도 됨. 단 memmove 를 호출할경우에는 cache miss 가 발생한다거나, 일종의 mispredicted branch 가 될 확률이 있지만, 프로그램에 잠재적 오류를 발생시키는 것보다 훨씬 낫지.
그리고 embedded glibc 는 그렇게 되어 있었던걸로 기억함.
애독자입니다. 열심히 보고 있습니다. 계속 써주세요 :)