코세가 자꾸 unaligned access 갖고 시비를 걸어서 이 참에 정리를 좀 해본다.
1. 서론
왜 c/c++에서 unaligned access가 좋지 않은가? 일단 기본적으로 c/c++ 표준에 따르면 unaligned access는 undefined behavior야. 자세한 설명은 http://stackoverflow.com/questions/32589328/unaligned-access-through-reinterpret-cast 를 참고.
unaligned access에 대해 컴파일러의 입장에서 가능한 행동은
1) 모든 포인터가 align되어 있다고 가정하고 코드를 생성한다.
2) 포인터가 align되어 있다는 확신이 없으면 성능을 희생해 unaligned access가 발생하지 않는 코드를 생성한다.
이 두가지 정도가 reasonable한 행동이 될텐데, arm-gcc에서는 -mno-unaligned-access 옵션으로 지정할 수 있어.(https://gcc.gnu.org/onlinedocs/gcc-5.3.0/gcc/ARM-Options.html#ARM-Options)
x86은 cpu가 unaligned access를 지원하니까 관련 옵션이 없을 듯 한데, 지금 슬쩍 gcc 메뉴얼을 찾아봤는데 역시 없는것 같다.(내가 x86은 안다뤄봐서 확실히는 잘 모르겠다) x86에서의 unaligned access에 대한 내용은 Intel® 64 and IA-32 Architectures Optimization Reference Manual(http://www.intel.com/content/www/us/en/architecture-and-technology/64-ia-32-architectures-optimization-manual.html) 의 3.6.4 Alignment에 설명되어 있는데 좆나 그냥 첫줄부터 'Misaligned data access can incur significant performance penalties.'라고 써놨네.
2. 실험 방법
간단하게 memcpy를 짜서 테스트해본다. 단순히 byte-to-byte 버전과 aligned access만 사용하는 버전, 그리고 unaligned access를 그냥 쓰는 버전으로 작성하고, glibc와 musl libc의 memcpy랑 성능 비교를 해볼거야. 글고 시스템에서 제공하는 std::memcpy랑도 비교해보자.
3. 실험 환경
하도 인텔인텔 그래서 적당히 굴러다니는 리눅스 머신으로 세팅했어. 커널 버전 4.1.20의 젠투 리눅스 시스템이고, Intel(R) Core(TM) i7-4770K CPU @ 3.50GHz, 램은 12G야. 진짜 암것도 없이 딱 커널만 올라갔고 X윈도는 설치도 안했다. gcc 버전은 5.3.0이고, clang 버전은 3.5.2야.
4. 기타사항
4.1. endianness
aligned access만을 이용하는 버전은 리틀엔디언인지 빅엔디언인지 구분할 필요가 있는데, 런타임 체크는 귀찮기도 하고 크리티컬한 부분도 아니고 해서 그냥 __BYTE_ORDER__ 매크로 썼어. 평소엔 표준표준거리다가 왜 엔디언은 __BYTE_ORDER__ 쓰냐!!라고 물으면 뭐 역시 귀찮아서... 어차피 런타임에 바이트오더가 바뀌는것도 아니고 이 부분은 걍 넘어가자.
4.2. -ftree-loop-distribute-patterns
gcc는 기본적으로 memcpy, memmove, memset, memcmp를 요구해.(심지어는 freestanding 환경에서조차도!) https://gcc.gnu.org/onlinedocs/gcc-5.3.0/gcc/Standards.html#Standards
그래서 최적화를 하다가 저 네가지 함수를 사용할 만한 상황이다 싶으면 저 함수를 콜하는 코드를 생성하는데, 지금 하려는데 memcpy를 구현하는거잖아? 근데 컴파일러가 코드를 딱 보고 '이건 memcpy부르면 되겠네' 라고 판단하면 memcpy가 memcpy를 콜하게 돼서 무한루프에 빠지게 돼. 리포트된지 좀 된 버그인데(https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56888), 이 현상을 막기 위해 -fno-tree-loop-distribute-patterns 옵션을 썼어.
4.3. glibc & musl libc
glibc 소스코드는 https://sourceware.org/git/?p=glibc.git;a=blob;f=string/memcpy.c;h=e4aa4dd2089cb8627f37fdc1f5c839a8b458fd80;hb=HEAD에서 가져왔고,
musl libc 소스코드는 http://git.musl-libc.org/cgit/musl/tree/src/string/memcpy.c에서 가져왔다.
c++로 컴파일하려고 헤더파일 몇가지랑 glibc 내부적으로 사용하는 throw attribute 매크로 같은거 손 좀 봤어.
5. 결과
5.1. g++ march=native -O2 -NDEBUG -fno-tree-loop-distribute-patterns
간단하게 O2 레벨 최적화에 디버그 루틴 끄고 memcpy 관련 옵션 준게 다야.
nice -n -20 ./compare $((100*1024*1024) 1 2 500 으로, 100메가 복사하는데, dest는 1byte misalign, src는 2byte misalign나도록 해서 500번 반복하도록 했다.
byte-to-byte 복사는 역시 좀 느리지? musl이나 glibc나 my_memcpy나 unaligned 버전이나 성능은 뭐 비슷비슷해. 일단 std::memcpy가 왜 압도적으로 높은가하면, c/c++에서의 unaligned access를 비교하는게 목적이라 musl이나 glibc나 generic 버전으로 가져온건데, 실제 glibc는 memcpy와 같은 루틴은 어셈블리로 직접 최대한 높은 성능을 뽑아내도록 따로 구현돼. glibc 소스코드의 sysdeps/x86_64/memcpy.S 나 sysdeps/arm/memcpy.S 를 보면 어케 구현되어있는지 볼 수 있다.
여기서 'aligned 버전이나 unaligned 버전이나 성능은 거의 비슷비슷한데? 그럼 걍 unaligned 버전 쓰는게 코드도 깔끔하고 좋지 않음?'이라고 생각할 수 있는데, 워드를 쪼개고 비트쉬프팅해서 합치는 걸 cpu 레벨에서 해주기 때문에 memcpy에서의 성능은 비슷한게 맞아. 근데 내가 하고자 하는 얘기는 뒤에 나오니까 뒤에서 설명할게.
5.2. clang -march=native -O2 -NDEBUG
clang도 같은 레벨의 최적화 옵션으로 빌드했어.
clang이 좆나 웃긴게 byte-to-byte 버전 성능이 다른 버전과 비슷하게 나왔는데, 깜짝 놀라서 코드 열어보니 대충 loop unrolling까지 되어 있네.. gcc가 memcpy를 콜하는 것마냥 clang도 똑같은 짓을 하는데 얘는 그냥 지가 구현한 memcpy를 그냥 인라이닝시켜버리는게 아닌가 의심스럽다. clang은 그냥 비교군으로 쓰는 편이었는데 생각보다 최적화가 잘 되는것 같네?
5.3. g++ march=native -O3 -NDEBUG -fno-tree-loop-distribute-patterns
그럼 이제 최적화 레벨을 높여보자. 사실 여기서 조마조마했는데 unaligned access가 컴파일러에 따라서 별 영향이 없을 줄 알았는데 역시나 segfault가 뜨더라구. 일단 비교부터.
gcc도 최적화 레벨을 최대로 주니까 byte_only 버전도 최적화해주는것 같네.
근데 unaligned 버전은 그냥 세그폴트가 떠. 간단하게 gdb를 떠보면 vmovdqa 인스트럭션에서 뜨는데, 컴파일러한테 얼라인먼트를 무시하고 엑세스하라고 코드를 넘겼고, 그래서 컴파일러는 얼라인먼트가 되어 있다고 가정하고 코드를 생성했고, 실제로는 얼라인먼트가.. ㅠㅠ.
내가 undefined behavior니까 unaligned access는 쓰지 말자고 하는 이유가 바로 이거야. 컴파일러마다 동작이 다르거나 옵션에 따라서도 행동이 달라질 수 있어. 그야말로 'undefined' behavior니까.
6. 결론
gcc가 구려서 그렇다는 개소리를 할까봐 노파심에 하는 소린데, undefined behavior는 컴파일러가 어떠한 행동을 해도 표준에 어긋나는 것이 아니기 때문에 gcc의 문제가 아냐.
UB는 앵간하면 쓰지 말자. 물론 특정 컴파일러가 어떻게 코드를 생성하는지 좆나 완벽하게 알고 있으면 써도 되는데, 논리적인 결함이 아닌 버그가 생길 가능성이 높고 그런 버그는 디버깅하기도 좆나 힘들어.
성능은 내가 잘못 알고 있었던것 같다. 난 unaligned access 뜨면 좆나게 느려질 줄 알았는데 그건 아니었네. 근데 코세가 말한것처럼 비트쉬프트해서 잘라 붙인다고 unaligned보다 느려지는것도 아니니. atomic operation으로 빡세게 테스트해보면 어떨까 싶은데 테스트방법이 떠오르질 않는다. 근데 어찌됐든 UB니까 쓰지 마.
7. 참고
gcc나 clang은 undefined behavior sanitizer를 제공하는데, 런타임에 UB가 발생할때마다 에러 메시지로 알려줘. 그니까 괜히 디버깅하느라 고생하지 말고 자주 사용해주는게 좋음. (g++ -fsanitize=undefined이나 clang++ -fsanitize=undefined로 컴파일)
memcpy.cpp:78:18: runtime error: load of misaligned address 0x7ff8a29d7012 for type 'const word', which requires 8 byte alignment
0x7ff8a29d7012: note: pointer points here
00 00 00 00 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b
. ^
memcpy.cpp:78:18: runtime error: store to misaligned address 0x7ff8a8dd8011 for type 'word', which requires 8 byte alignment
0x7ff8a8dd8011: note: pointer points here
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
. ^
뭐야 씨발 그림 다 어디갔냐
그림이 없는데 뭘 잘 읽었다는거야 ㅋㅋㅋㅋ
오 올라갔다 그림
소스는 어디?
소스코드를 깜박했네
https://github.com/kukyakya/aligned_access
근데 워낙 대충 짜서 버그가 있을지는 모르겠다. 대충 assert 걸리는거는 없는것 같은데.
ㅋㅋㅋ 지랄 똥싸고 있네. 있다가 정리해 털어주마.
애초에 UB라는데 뭘 정리해서 털어준다는건지 ㅋㅋㅋ 기대하마
첨부터 말했다. 니가 짠 aligned 랑 내가 짠 unaligned 비교라고.
애초에 선택이라는데 말도안된다며 개소리한게 너지. ㅋㄷ
니가 짠 aligned 코드만 딱 올려놔. 이거 함수이름 다 왜 이따위야?
musl_memcpy? 뭐하자는거?
해온대로 존나 아가리만 털고 앉았네 ㅉㅉ
왜 저 이름인지 생각이나 좀 해보고 물어봐 ㅋㅋㅋㅋ 'UB가 아닌 unaligned access'라는게 가능하기나 한지 그 돌대가리로 생각을 해봐라.
이 새끼는 ㅂㅅㅅ랑 같이 보라고 표준 드래프트까지 알려줬구만 쳐 보질 않네 진짜.
난 뭐 strict aliasing rule 갖고 반론이라도 할 줄 알았는데 함수 이름갖고 쳐 지랄하는거 보소 ㅋㅋㅋ
하 이샛기 니 코드 핵심만 남겨서 비교해준다. 모가지 씻고 기다려라. ㅋㄷ
그래 그리고 니 코드도 올려라.
물론 당연히 성능비교할텐데 올려야지 : )
분명히 문제 정의 단계에서 이야기 했다. "호환성이 필요없을때" 최적화 이슈에서 unaligned 는 선택이고, aligned 가 오히려 느릴 수 있다고. 그걸 부정한게 너임.
존나 말 길게 돌려 쳐 할 필요가 없음. 즉 이 글은 대부분 전혀 문제와 상관없는 글. 결론도 마찬가지.
ㅋㅋㅋㅋㅋ 야 좆나 논지 돌리네. 첨에 시작이 unaligned access에 대한 kldp 글 아녀? 뭐 또 저 글은 ㅂㅅㅅ가 쓴거라고 도망가겠지만. 내가 애초에 첨부터 얘기한건 딱 두개야. 'UB니까 쓰지 마라. 제대로 코드 생성해도 성능저하 있을 수 있다.' 니 맘대로 "호환성이 필요없을때" 갖다 붙이지 마라. 게다가 같은 컴파일러라도 세그폴트 뜨는 예까지 친절하게 올려줬잖아?
테스트 케이스에 대해서도 미리 이야기 했다. memcpy( dst + 1, src +3 , size ), dst, src 는 char* ( memcpy 는 최적화를 위해 이미 케이스에 따라 몇 블럭으로 정의되어 있음 )
웃기지마. 내가 글 쓴 첫 글을 니가 부정한거야. 이 샛기 말장난 계속하네.
그니까 내 소스 코드 감상평은 쓰던 말던 상관안할건데, 니가 말한것처럼 '컴파일러 최적화에 상관없이 항상 유효한 unaligned access' 코드나 가져와봐. 진짜 이 새끼는 뭐 가져오라면 가져오진 않고 좆나 빙빙 말만 돌리고 앉았어. 그거 가져오면 내가 아닥하겠다니까?
호환성이 필요없을 때 란건 첫 글에 명시했으니 니가 니말을 부정하는 것 뿐이고.
컴파일러 최적화에 상관없이란게 내가 이미 한 말을 바꾸는 조건임. 넌 지금 이미 어제 합의한 조건을 바꾸고 있음.
글을 좀 읽어 이 리신새끼야. dest랑 src에 misalignment 옵션 준거 안보이냐? 이 새끼는 진짜 내가 가져오라는건 도통 가져올 생각을 안해요. 못가져오겠으면 못가져오겠다고 해 등신새끼야. 내가 뭐 많이 갖다 달래니? UB가 아닌 unaligned access 코드 딱 하나만 가져오라니까?
이동 후 니코드 테스트 하고 내 코드랑 비교해서 새 글로 쓴다.
하 진짜.. 너 c/c++ 쓰는거 맞냐? 뭐 자꾸 딴소리야. 애초에 전제 자체가 c/c++아냐? 진짜 딱 UB 하나 얘기하고 있는데 호환성 얘기가 왜 나와?
넌 이미 시작전부터 졌고 꼴랑 30줄 짤 코드를 하루 지나 들고와 말로 뻐대겨볼려고 하는거고 최적화 이슈에서 호환성 들고 나오고 있는거임 멍청하고 비겁한 새끼야.
UB 이야기는 니가 이제 하는거고 어제 대화는 그대로 남아 있음. 은근 슬쩍 말 바꾸지 마라.
내가 시작부터 한 얘기가 UB얘기였다 등신아. KLDP에 내가 뭐라고 썼나 봐. 관심법 쓰더니 이제는 지가 보고 싶은거만 쓱 보네.
다시 말해준다. 분명히 문제 정의 단계에서 이야기 했다. "호환성이 필요없을때" 최적화 이슈에서 unaligned 는 선택이고, aligned 가 오히려 느릴 수 있다고. 그걸 부정한게 너임. <- 맞아 틀려?
니가 말하는 '호환성'이라는게 내가 생각하는 호환성이랑 다른 모양인데, 호환성이 필요없다는게 UB도 막 쓴다는거냐?
맞아? 아니면 틀려? 분명히 말해라.
portability가 필요없는거랑 UB인거랑 완전 다른 얘긴데 왜 맘대로 섞고 앉았어 진짜 대화를 못하겠네.
당연히 컴파일러에서 돌고 x86 x64 에서 정상동작하는 코드면 다른 호환성은 문제될게 없다. 실제 게임들도 그렇게 만들어지고. 각각의 플랫폼용으로 최적화되니 별도의 코드를 짜고 있으니 어디까지나 선택이지.
맞냐고 틀리냐고 병신새끼야.
그래 맞다. 그니까 부탁인데 호환성 필요없을 때 UB가 아닌 unaligned access만 좀 가져와봐. 진짜 딱 저거 하나 갖다 달라는데 말 좆나게 많네.
아 이 등신은 '정상동작'하면 제대로 된 코드라고 생각하는 레벨이었네... 에휴 진짜 이런 새끼랑 표준 갖고 얘기를 하려는 내가 병신이지
난 내가 정의한 문제에 니가 부정했으니 그것만 정리하면 된다. 어차피 다른 케이스는 별도의 코드로 짤테니까, "하나로 통합해야 된다" 이딴건 말도 안됨.
i++ + ++i 가 정상동작한다고 괜찮다고 할 새끼네.
내가 말했지 memcpy 역시 이미 여러개의 블럭 케이스로 나뉘어져 있다고. 병신아.
최적화를 위해서 unaligned 로 처리한 코드가 있으면 내 말이 맞는거고, memcpy 는 이미 그렇게 구현되어 있다고 병신아.
야 그럼 다 됐고, 최적화하려고 unaligned access 쓰는 memcpy 구현체나 알려줘봐.
https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/x86_64/multiarch/memcpy-ssse3.S;h=1ca88c0758863f818376098f8648e4a0b2e042c5;hb=HEAD
glibc의 sse3 버전 memcpy인데, 여기서도 movaps로 aligned instruction 쓴다.
뭔 구현체 나발이야 vc++ memcpy 자체가 그런데 ㅋㄷ 병신인가 이새끼는.
major libc 구현체 중에 최적화를 위해 unaligned access를 사용하는 예 딱 하나만 가져와라. 어셈이든 c/c++이든 상관안할게.
니코드 정리해서 성능비교해서 올려준다. 기다려 지금 이동중.
개소리 치우고 vc++ memcpy 자체가 구현체라니깐?
memcpy(dst + 3, src + 1, size), src, dst 는 char* 란 조건에서 unaligned 내가 짠게 aligned 로 짠 니코드 보다 빠르면 게임 셋이야.
여기서 src, dst 는 이미 4 바이트로 정렬된 주소라고 어제 몇 번이고, 몇 번이고 테스트 환경으로 정의했다.
야 vs에서 memcpy 구현체 가져오느라 늦었다. crt에 intel에 memcpy.s인데, 여기도 movdqa로 alignment 맞춰서 가져오잖아 병신 새끼야. 어디에 무슨 vs를 갖다 쓰는거야?
vc++ 도 케이스별로 정리되어 있다는거지 병신아. "하나로 통합" 하는게 말도 안된다고.
아 진짜 시발 나도 dst랑 src에 misalign 줬다는데 뭔 자꾸 개소리여. 난 뭐 dst랑 src를 얼라인 맞춰서만 테스트했겠냐? 이 등신은 코드를 읽기는 하는건지 모르겠네.
뭔 하나로 통합같은 소리여. 이 새끼는 unaligned access가 더 빠를 수도 있다면서 vc memcpy가 그렇게 구현되어 있다더니 뭔 또 하나로 통합같은 개소리여
어제도 분명히 말했지만 니 코드랑 내 코드 이미 언급한 케이스에서 내가 빠르면 끝.
니가 호환성 개소리 자꾸 들고 오니까 하는소리지 멍청한 새끼야. ㅉㅉ
하.. 그래 가져와봐. 기다릴게.
두어시간 걸릴테니 딴짓 하고 기다리렴 ㅋㄷ
그래 일단 가져오기나 해 벽보고 얘기하는것 같아서 지친다 나도.
http://gall.dcinside.com/board/view/?id=programming&no=571643
셀프디스 잘 보고 간다.