7

SSE を利用するために、(Visual Studio 2012 C++ コードで) インライン アセンブラーを実装しようとしています。1e9回の7つの数字を追加したいので、RAMからCPUのxmm0からxmm6レジスタに配置しました。次のコードを使用して、Visual Studio 2012 でインライン アセンブリを使用すると、次のようになります。

C++ コード:

for(int i=0;i<count;i++)
        resVal+=val1+val2+val3+val4+val5+val6+val7;

私のASMコード:

int count=1000000000;

    double resVal=0.0;
       //placing values to register
    __asm{  
        movsd xmm0,val1;placing var1 in xmm0 register  
        movsd xmm1,val2  
        movsd xmm2,val3  
        movsd xmm3,val4  
        movsd xmm4,val5  
        movsd xmm5,val6  
        movsd xmm6,val7  
        pxor xmm7,xmm7;//turns xmm7 to zero
         }

    for(int i=0;i<count;i++)
    {
        __asm
        {
            addsd xmm7,xmm0;//+=var1
            addsd xmm7,xmm1;//+=var2
            addsd xmm7,xmm2;
            addsd xmm7,xmm3;
            addsd xmm7,xmm4;
            addsd xmm7,xmm5;
            addsd xmm7,xmm6;//+=var7
        }

    }

    __asm
        {
            movsd resVal,xmm7;//placing xmm7 into resVal
        }

これは、コード 'resVal+=val1+val2+val3+val4+val5+val6+val7' の C++ コンパイラから逆アセンブルされたコードです。

movsd       xmm0,mmword ptr [val1]  
addsd       xmm0,mmword ptr [val2]  
addsd       xmm0,mmword ptr [val3]  
addsd       xmm0,mmword ptr [val4]  
addsd       xmm0,mmword ptr [val5]  
addsd       xmm0,mmword ptr [val6]  
addsd       xmm0,mmword ptr [val7]  
addsd       xmm0,mmword ptr [resVal]  
movsd       mmword ptr [resVal],xmm0  

ご覧のとおり、コンパイラは xmm0 レジスタを 1 つだけ使用し、それ以外の場合は RAM から値をフェッチしています。

両方のコード (私の ASM コードと C++ コード) の答えは同じですが、C++ コードの実行時間は私の asm コードの約半分です!

CPUレジスタについて読んだところ、それらを使用する方がメモリよりもはるかに高速です。この比率が正しいとは思えません。asm バージョンの C++ コードのパフォーマンスが低いのはなぜですか?

4

2 に答える 2

11
  • データがキャッシュに格納されると (最初のループの後で、まだキャッシュに格納されていない場合)、メモリまたはレジスタを使用してもほとんど違いはありません。
  • 浮動小数点加算は、最初は 1 サイクルよりも少し時間がかかります。
  • resValレジスタを自由に「名前変更」できるように、xmm0 レジスタを「結合解除」する最後のストアでは、より多くのループを並行して実行できます。

これは、「絶対に確信が持てない限り、コードの作成はコンパイラに任せる」という典型的なケースです。

上記の最後の箇条書きは、ループのすべてのステップが以前に計算された結果に依存するコードよりも高速である理由を説明しています。

コンパイラで生成されたコードでは、ループは次と同等の処理を実行できます。

movsd       xmm0,mmword ptr [val1]  
addsd       xmm0,mmword ptr [val2]  
addsd       xmm0,mmword ptr [val3]  
addsd       xmm0,mmword ptr [val4]  
addsd       xmm0,mmword ptr [val5]  
addsd       xmm0,mmword ptr [val6]  
addsd       xmm0,mmword ptr [val7]  
addsd       xmm0,mmword ptr [resVal]  
movsd       mmword ptr [resVal],xmm0  

movsd       xmm1,mmword ptr [val1]  
addsd       xmm1,mmword ptr [val2]  
addsd       xmm1,mmword ptr [val3]  
addsd       xmm1,mmword ptr [val4]  
addsd       xmm1,mmword ptr [val5]  
addsd       xmm1,mmword ptr [val6]  
addsd       xmm1,mmword ptr [val7]  
addsd       xmm1,mmword ptr [resVal]  
movsd       mmword ptr [resVal],xmm1

ご覧のとおり、これら 2 つの「スレッド」を「混ぜ合わせる」ことができます。

movsd       xmm0,mmword ptr [val1]  
movsd       xmm1,mmword ptr [val1]  
addsd       xmm0,mmword ptr [val2]  
addsd       xmm1,mmword ptr [val2]  
addsd       xmm0,mmword ptr [val3]  
addsd       xmm1,mmword ptr [val3]  
addsd       xmm0,mmword ptr [val4]  
addsd       xmm1,mmword ptr [val4]  
addsd       xmm0,mmword ptr [val5]  
addsd       xmm1,mmword ptr [val5]  
addsd       xmm0,mmword ptr [val6]  
addsd       xmm1,mmword ptr [val6]  
addsd       xmm0,mmword ptr [val7]  
addsd       xmm1,mmword ptr [val7]  
addsd       xmm0,mmword ptr [resVal]  
movsd       mmword ptr [resVal],xmm0  
// Here we have to wait for resval to be uppdated!
addsd       xmm1,mmword ptr [resVal]  
movsd       mmword ptr [resVal],xmm1

順不同で実行することを示唆しているわけではありませんが、ループがループよりも速く実行される方法は確かにわかります。スペアレジスタがあれば、おそらくアセンブラコードで同じことを達成できます[x86_64ではインラインアセンブラを使用できませんが、x86_64ではさらに8つのレジスタがあります...]

(レジスタの名前変更は、2 つの異なるレジスタを使用する「スレッド化された」ループとは異なることに注意してください。ただし、効果はほぼ同じです。ループは、「resVal」更新にヒットした後、結果が返されるのを待たずに続行できます。更新しました)

于 2013-03-11T21:51:57.820 に答える