4要素の分岐しないソート (GCC用)


(こちらの記事の続き、おまけです)
分岐しないソート」の、VC++向けのコードをGCC+Linux向けに修正してみました。


実はC言語によるソートである sort4l() のほうも相当速いので、私の手元の環境だと同じくらいの速度しか出なかった*1んですが、面白いのでオッケー。例えば {1,2,3,4} から生成される順列(24通り)を何百万回か食わすような処理を書いてみると Celeron 2.4GHz & Linux だとアセンブラ版が10%遅く、Athron64 3200+ & Linux/x86_64, gcc -m32 だとアセンブラ版が10%速く、ARM9 200MHz & Linux だと同じ速度でした。


まずx86向けから。

// for GCC + Linux/x86
void sort4(unsigned int *d) {
  __asm__ (
     "mov     (%0), %%eax;"
     "mov    4(%0), %%ebx;"
     "mov    8(%0), %%ecx;"
     "mov   12(%0), %%edx;"

     "cmp   %%ebx,  %%eax;"
     "cmova %%ebx,  %%eax;"
     "cmova  (%0),  %%ebx;"

     "cmp   %%edx,  %%ecx;"
     "cmova %%edx,  %%ecx;"
     "cmova 8(%0),  %%edx;"

     "mov   %%eax,  %%esi;"
     "cmp   %%ecx,  %%eax;"
     "cmova %%ecx,  %%eax;"
     "cmova %%esi,  %%ecx;"

     "mov   %%ebx,  %%esi;"
     "cmp   %%edx,  %%ebx;"
     "cmova %%edx,  %%ebx;"
     "cmova %%esi,  %%edx;"

     "mov   %%ebx,  %%esi;"
     "cmp   %%ecx,  %%ebx;"
     "cmova %%ecx,  %%ebx;"
     "cmova %%esi,  %%ecx;"

     "mov   %%eax,   (%0);"
     "mov   %%ebx,  4(%0);"
     "mov   %%ecx,  8(%0);"
     "mov   %%edx, 12(%0);"
     :: "D"(d)
     : "%eax", "%ebx", "%ecx", "%edx", "%esi", "memory" );
}

せっかくコードが短く、オブジェクトサイズ比で sort4l() の1/5以下なので、マクロにしてコード中にインラインで埋め込んでしまうのも良いかもしれません。当然ですが関数呼び出しのオーバーヘッドが減る分、ずいぶん速くなります。Cで書かれた sort4l() を、無理矢理 __attribute__((__always_inline__)) 等でインライン化するより、(気のせいかもしれませんが)スマートかと。


こんな感じ。

// for GCC + Linux/x86
#define SORT4(d)                                \
  ({                                            \
    unsigned int *d_ = (d);                     \
    __asm__                                     \
      (                                         \
       "mov     (%0),  %%eax;"                  \
       "mov    4(%0),  %%ebx;"                  \
       "mov    8(%0),  %%ecx;"                  \
       "mov   12(%0),  %%edx;"                  \
       "cmp    %%ebx,  %%eax;"                  \
       "cmova  %%ebx,  %%eax;"                  \
       "cmova   (%0),  %%ebx;"                  \
       "cmp    %%edx,  %%ecx;"                  \
       "cmova  %%edx,  %%ecx;"                  \
       "cmova  8(%0),  %%edx;"                  \
       "mov    %%eax,  %%esi;"                  \
       "cmp    %%ecx,  %%eax;"                  \
       "cmova  %%ecx,  %%eax;"                  \
       "cmova  %%esi,  %%ecx;"                  \
       "mov    %%ebx,  %%esi;"                  \
       "cmp    %%edx,  %%ebx;"                  \
       "cmova  %%edx,  %%ebx;"                  \
       "cmova  %%esi,  %%edx;"                  \
       "mov    %%ebx,  %%esi;"                  \
       "cmp    %%ecx,  %%ebx;"                  \
       "cmova  %%ecx,  %%ebx;"                  \
       "cmova  %%esi,  %%ecx;"                  \
       "mov    %%eax,   (%0);"                  \
       "mov    %%ebx,  4(%0);"                  \
       "mov    %%ecx,  8(%0);"                  \
       "mov    %%edx, 12(%0);"                  \
       :: "D"(d_)                               \
       : "%eax", "%ebx", "%ecx", "%edx", "%esi", "memory" \
       );                                       \
  })

次はx86_64向け。

// for GCC + Linux/x86_64
void sort4(unsigned int* d) {
  __asm__
    (
     "mov     (%0), %%r8d;"
     "mov    4(%0), %%r9d;"
     "mov    8(%0), %%r10d;"
     "mov   12(%0), %%r11d;"
     "cmp   %%r9d,  %%r8d;"
     "cmova %%r9d,  %%r8d;"
     "cmova   (%0), %%r9d;"
     "cmp   %%r11d, %%r10d;"
     "cmova %%r11d, %%r10d;"
     "cmova  8(%0), %%r11d;"
     "mov   %%r8d,  %%eax;"
     "cmp   %%r10d, %%r8d;"
     "cmova %%r10d, %%r8d;"
     "cmova %%eax,  %%r10d;"
     "mov   %%r9d,  %%eax;"
     "cmp   %%r11d, %%r9d;"
     "cmova %%r11d, %%r9d;"
     "cmova %%eax,  %%r11d;"
     "mov   %%r9d,  %%eax;"
     "cmp   %%r10d, %%r9d;"
     "cmova %%r10d, %%r9d;"
     "cmova %%eax,  %%r10d;"
     "mov   %%r8d,    (%0);"
     "mov   %%r9d,   4(%0);"
     "mov   %%r10d,  8(%0);"
     "mov   %%r11d, 12(%0);"
     :: "D"(d)
     : "%r8", "%r9", "%r10", "%r11", "%eax", "memory"
     );
}

おまけでARM向け。ARMというCPUは、殆ど全ての命令をconditionalに実行できます(条件実行機能)。ですので、分岐しないソートを簡単に実装できます。Armadillo-9で動作確認しました。

// for GCC + Linux/arm
void sort4(unsigned int* d) {
  __asm__ (
     "LDMIA %0, {r1-r4};"

     "MOV   r5, r1;"
     "CMP   r2, r1;"
     "MOVCC r1, r2;"
     "MOVCC r2, r5;"

     "MOV   r5, r3;"
     "CMP   r4, r3;"
     "MOVCC r3, r4;"
     "MOVCC r4, r5;"

     "MOV   r5, r1;"
     "CMP   r3, r1;"
     "MOVCC r1, r3;"
     "MOVCC r3, r5;"

     "MOV   r5, r2;"
     "CMP   r4, r2;"
     "MOVCC r2, r4;"
     "MOVCC r4, r5;"

     "MOV   r5, r2;"
     "CMP   r3, r2;"
     "MOVCC r2, r3;"
     "MOVCC r3, r5;"

     "STMIA %0, {r1-r4};"
     :: "g"(d)
     : "r1", "r2", "r3", "r4", "r5", "memory" );
}

以上です。http://www.nist.gov/dads/HTML/bitonicSort.html も参考になります。同じアルゴリズムですね。

*1:わたしが何か間違えていなければ