몇 일 전에 썼던 글에서 테스트를 해본 내용을 바탕으로 4×4 matrix multiply 연산을 mmx 를 이용해서 구현해봤습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
#include <stdio .h> // A matrix short s1[16] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, }; // Transpose(B matrix) short s2[16] = { 17, 21, 25, 29, 18, 22, 26, 30, 19, 23, 27, 31, 20, 24, 28, 32 }; // Destination matrix short d[16]; short t[4]; int i, j; long start, end; int main( int argc, char** argv ){ int k; for( j = 0 ; j < 4 ; j++ ){ for( i = 0 ; i < 4 ; i++ ){ d[j*4+i] = 0; for( k = 0 ; k < 4 ; k++ ){ d[j*4+i] += s1[j*4+k] * s2[i*4+k]; } } } fprintf( stderr, "c version\n\n" ); for( j = 0 ; j < 4 ; j++ ){ for( i = 0 ; i < 4 ; i++ ){ fprintf( stderr, "\t%3d", d[j*4+i] ); } fprintf( stderr, "\n" ); } return 0; } |
위와 같은 c version 의 코드를 작성한 후 아래와 같은 asm version 으로 컨버팅을 해봤는데, 100000 번 반복해서 연산을 하도록 해본 결과 mmx 버젼이 c 버젼보다 3배 정도 빠르게 연산을 하는 것을 확인할 수 있었습니다. (-O0 옵션과 함께 컴파일 했을 경우)
하지만 -O3 옵션과 함께 컴파일하게 되면 asm 버젼은 무한룹에 빠진 듯한 모습을 보여줬고, c 버젼의 수행속도가 -O0 로 컴파일한 asm 버젼보다 빠른 현상이 발생했습니다. 이유는 알 수 없음 -_-;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
#include <stdio.h> #include <asm /mmx.h> // A matrix short s1[16] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, }; // Transpose(B matrix) short s2[16] = { 17, 21, 25, 29, 18, 22, 26, 30, 19, 23, 27, 31, 20, 24, 28, 32 }; // Destination matrix short d[16]; short t[4]; int i, j; int main( int argc, char** argv ){ int loop; for( loop = 0 ; loop < 10000; loop++ ){ for( j = 0 ; j < 4 ; j++ ){ for( i = 0 ; i < 4 ; i++ ){ __asm__("mov j, %eax"); __asm__("movq s1(,%eax,8), %mm0" ); __asm__("mov i, %eax"); __asm__("movq s2(,%eax,8), %mm1" ); __asm__("pmullw %mm1, %mm0"); __asm__("movq %mm0, (t)" ); d[j*4+i] = t[0] + t[1] + t[2] + t[3]; } } } for( j = 0 ; j < 4 ; j++ ){ for( i = 0 ; i < 4 ; i++ ){ fprintf( stderr, "\t%3d", d[j*4+i] ); } fprintf( stderr, "\n" ); } return 0; } |
8×8 matrix 는 뭔가 좀 더 생각해야할 것 같으니 나중에 정말 필요한 일 있을 때 구현을 해봐야겠습니다. -_-;
inline asm 작업을 하면서 eax 레지스터 값을 백업하지 않고 저렇게 사용해도 되는지는 잘 모르겠지만 –;; 하여튼 저 코드에 한해서는 별 문제 없으니 패스~ 꺄홋!!
gcc 컴파일러에서 callee save register들은 인라인 어셈에서도 알아서 처리해 준다고 합니다. vc.exe도 아마 그러리라고 봅니다.
compiler 수업에서 배운 것 중에 register allocation 에 의하면 변수들을 레지스터로 매핑하는 것이 있어서 변수가 매핑되어 있는 레지스터가 있을 경우 문제가 생기지 않을까 하는 생각을 했었는데… -S 옵션으로 확인해보니 load add store 식의 과정을 거치는 것으로 보아 별 문제가 없을거 같긴 하네요 😉
정정:
Optimize2, 3 level 이 되면 저런식으로 코드 중간에서 레지스터값을 변경시킬 경우 문제가 발생하네요. 아래는 위의 c code 를 -O2 -S 옵션을 이용해서 asm 코드도 변경시켜본 것입니다. edx, eax, esi 등을 번갈아가면서 이용해서 메모리에 접근하는 것을 최소화하면서 연산을 하는 것을 볼 수 있네요. (수업 시간에 배웠던 live range 를 이용한 register allocation 을 실제로 보니 재밌네요. 🙂
게다가 loop unrolling 까지 되어있네요. (이건 반칙인데!) 나주엥 여유 있을때 stdc_call 인터페이스를 이용해서 외부 함수형태로 loop unrolling 등을 활용해서 다시 테스트를 해봐야겠네요.
movzwl s2(,%ecx,8), %edx
movl -56(%ebp), %eax
movl -72(%ebp), %esi
imull %eax, %edx
movzwl s2+2(,%ecx,8), %eax
imull %esi, %eax
movl -88(%ebp), %esi
addl %eax, %edx
movzwl s2+4(,%ecx,8), %eax
imull %esi, %eax
addl %eax, %edx
movzwl s2+6(,%ecx,8), %eax
incl %ecx
imull %edi, %eax
addl %eax, %edx
movw %dx, (%ebx)
addl $2, %ebx
cmpl $4, %ecx
아.. 인라인 구문과 아닌 구문이 한 함수에 있군요. 이런 경우 인라인 어셈에서도 C의 변수를 가져다 쓸 수 있는데 방법이 생각이 안 나네요.. 어언 4년 전의 일… ㅠㅠ