정태영

몇 일 전에 썼던 글에서 테스트를 해본 내용을 바탕으로 4x4 matrix multiply 연산을 mmx 를 이용해서 구현해봤습니다.

C:
  1. #include <stdio .h>
  2.  
  3. // A matrix
  4. short s1[16] = {
  5.      1234,
  6.      5678,
  7.      9, 10, 11, 12,
  8.     13, 14, 15, 16,
  9. };
  10.  
  11. // Transpose(B matrix)
  12. short s2[16] = {
  13.     17, 21, 25, 29,
  14.     18, 22, 26, 30,
  15.     19, 23, 27, 31,
  16.     20, 24, 28, 32
  17. };
  18.  
  19.  
  20. // Destination matrix
  21. short d[16];
  22. short t[4];
  23.  
  24. int i, j;
  25.  
  26. long start, end;
  27.  
  28. int main( int argc, char** argv ){
  29.  
  30.     int k;
  31.  
  32.     for( j = 0 ; j <4 ; j++ ){
  33.         for( i = 0 ; i <4 ; i++ ){
  34.             d[j*4+i] = 0;
  35.             for( k = 0 ; k <4 ; k++ ){
  36.                 d[j*4+i] += s1[j*4+k] * s2[i*4+k];
  37.             }
  38.         }
  39.     }
  40.  
  41.     fprintf( stderr, "c version\n\n" );
  42.     for( j = 0 ; j <4 ; j++ ){
  43.         for( i = 0 ; i <4 ; i++ ){
  44.             fprintf( stderr, "\t%3d", d[j*4+i] );
  45.         }
  46.         fprintf( stderr, "\n" );
  47.     }
  48.     return 0;
  49.  
  50. }

위와 같은 c version 의 코드를 작성한 후 아래와 같은 asm version 으로 컨버팅을 해봤는데, 100000 번 반복해서 연산을 하도록 해본 결과 mmx 버젼이 c 버젼보다 3배 정도 빠르게 연산을 하는 것을 확인할 수 있었습니다. (-O0 옵션과 함께 컴파일 했을 경우)

하지만 -O3 옵션과 함께 컴파일하게 되면 asm 버젼은 무한룹에 빠진 듯한 모습을 보여줬고, c 버젼의 수행속도가 -O0 로 컴파일한 asm 버젼보다 빠른 현상이 발생했습니다. 이유는 알 수 없음 -_-;

C:
  1. #include <stdio.h>
  2. #include <asm /mmx.h>
  3.  
  4. // A matrix
  5. short s1[16] = {
  6.      1234,
  7.      5678,
  8.      9, 10, 11, 12,
  9.     13, 14, 15, 16,
  10. };
  11.  
  12. // Transpose(B matrix)
  13. short s2[16] = {
  14.     17, 21, 25, 29,
  15.     18, 22, 26, 30,
  16.     19, 23, 27, 31,
  17.     20, 24, 28, 32
  18. };
  19.  
  20.  
  21. // Destination matrix
  22. short d[16];
  23. short t[4];
  24.  
  25. int i, j;
  26.  
  27. int main( int argc, char** argv ){
  28.     int loop;
  29.  
  30.     for( loop = 0 ; loop <10000; loop++ ){
  31.         for( j = 0 ; j <4 ; j++ ){
  32.  
  33.             for( i = 0 ; i <4 ; i++ ){
  34.  
  35.                 __asm__("mov j, %eax");
  36.                 __asm__("movq s1(,%eax,8), %mm0" );
  37.  
  38.                 __asm__("mov i, %eax");
  39.                 __asm__("movq s2(,%eax,8), %mm1" );
  40.  
  41.                 __asm__("pmullw %mm1, %mm0");
  42.                 __asm__("movq %mm0, (t)" );
  43.  
  44.                 d[j*4+i] = t[0] + t[1] + t[2] + t[3];
  45.             }
  46.         }
  47.     }
  48.     for( j = 0 ; j <4 ; j++ ){
  49.         for( i = 0 ; i <4 ; i++ ){
  50.             fprintf( stderr, "\t%3d", d[j*4+i] );
  51.         }
  52.         fprintf( stderr, "\n" );
  53.     }
  54.  
  55.     return 0;
  56.  
  57. }

8x8 matrix 는 뭔가 좀 더 생각해야할 것 같으니 나중에 정말 필요한 일 있을 때 구현을 해봐야겠습니다. -_-;

inline asm 작업을 하면서 eax 레지스터 값을 백업하지 않고 저렇게 사용해도 되는지는 잘 모르겠지만 --;; 하여튼 저 코드에 한해서는 별 문제 없으니 패스~ 꺄홋!!

정태영

요새 matrix 연산을 이용한 프로그램 조각 몇 가지를 짜보고 있는데, mmx 같은 SIMD instruction 을 사용하면 matrix 연산의 속도를 확 올릴 수 있지 않을까 싶은 생각이 들길래 inline asm 을 이용해서 간단한 mmx 코드를 만들어보았습니다.

C:
  1. #include &lt;stdio .h&gt;
  2.  
  3. short s1[4] = { 1, 2, 3, 4 };
  4. short s2[4] = { 5, 6, 7, 8 };
  5. short d[4];
  6.  
  7. int main( int argc, char** argv ){
  8.  
  9.     d[0] = s1[0] * s2[0];
  10.     d[1] = s1[1] * s2[1];
  11.     d[2] = s1[2] * s2[2];
  12.     d[3] = s1[3] * s2[3];
  13.  
  14.     fprintf( stderr, "c: %d %d %d %d\n", d[0], d[1], d[2], d[3] );
  15.  
  16.     d[0] = d[1] = d[2] = d[3] = 0;
  17.  
  18.     asm("movq (s1), %mm0");
  19.     asm("movq (s2), %mm1");
  20.     asm("pmullw %mm1, %mm0");
  21.     asm("movq %mm0, (d)");
  22.  
  23.     fprintf( stderr, "asm: %d %d %d %d\n", d[0], d[1], d[2], d[3] );
  24.  
  25.     return 0;
  26.  
  27. }

위와 같은 코드를 작성하고, gcc mmx.c 를 통해 컴파일해서 돌려보니 간단히 성공 -_-v

c 코드를 사용할 경우 s1[0] load, s2[0] load, multiply, save to d[0] 와 같은 인스트럭션을 네 번 반복해서 실행하는 반면 mmx 를 사용할 경우 movq 를 통해 연속된 WORD 네 개를 mmx register 로 복사하고, pmullw 를 이용 4 개의 값을 한 인스트럭션에 연산을 하는 것을 통해 속도를 확 끌어올릴 수 있는거죠. ;)

다만 헷갈리는게 인텔의 메뉴얼에 나와있는 인자 순서와, AT&T 방식이 달라서 좀 헷갈리는군요.

  • Intel: movq mm0, [s1]
  • AT&T: movq (s1), %mm0

Intel 메뉴얼에서 설명하는 바에 의하면 첫번째 operland 가 destination 이 되고, 두번째 operland 가 destination 이 되는 반면 AT&T 방식에서는 거꾸로 첫번째 operland 가 src, 두번째 operland 가 dst 가 됩니다.

또한 주소값을 넘겨줄 때 intel 방식은 [] 로 감싸주면 되지만, AT&T 에서는 () 로 감싸줘야하고, 레지스터 이름 앞에 %를 붙여줘야 하는 규칙도 있어서 뭔가 대빵 귀찮네요. -_-;

참고로 gcc 에서 -masm=intel 옵션을 사용하면 intel 방식으로 어셈블리 명령어를 작성하는 것도 가능합니다.

p.s) movq 는 4개의 WORD 를 mmx register 로 복사하는 명령인데 --;; mm0 ~ mm7 식으로 64bit register name 을 써줘야 하는데 xmm0~xmm7 같은 sse 용 register 이름을 쓰는 바람에 잘못된 인스트럭션 사용이라고 계속 에러가나서 한참 헤맸네요;