2017-01-29 14 views
3

gcc(Intel & T構文)で次のコードを実行します。gccを使用したYMM命令を使用した配列の追加

; float a[128], b[128], c[128]; 
; for (int i = 0; i < 128; i++) a[i] = b[i] + c[i]; 
; Assume that a, b and c are aligned by 32 
     xor ecx, ecx    ; Loop counter i = 0 
L: vmovaps ymm0, [b+rcx]  ; Load 8 elements from b 
     vaddps ymm0,ymm0,[c+rcx] ; Add 8 elements from c  
     vmovaps [a+rcx], ymm0  ; Store result in a 
     add ecx,32    ; 8 elements * 4 bytes = 32 
     cmp ecx, 512    ; 128 elements * 4 bytes = 512 
     jb L      ;Loop 

コードはOptimizing subroutines in assembly languageです。

私がこれまでに書いたコードは次のとおりです。私はこのような何かを取得したい(GDBのを逆アセンブルから除く)

0x0000000000000b78 <+19>: vmovaps -0x10(%rbp),%ymm0 
0x0000000000000b7d <+24>: vaddps -0x18(%rbp),%ymm0,%ymm0 

static inline void addArray(float *a, float *b, float *c) { 
__asm__ __volatile__ (
    "nop       \n" 
    "xor  %%ecx, %%ecx  \n" //;Loop counter set to 0 
    "loop: \n\t" 
    "vmovaps %1, %%ymm0   \n" //;Load 8 elements from b <== WRONG 
    "vaddps  %2, %%ymm0, %%ymm0 \n" //;Add 8 elements from c <==WRONG 
    "vmovaps %%ymm0, %0   \n" //;Store result in a 
    "add  0x20, %%ecx   \n" //;8 elemtns * 4 bytes = 32 (0x20) 
    "cmp  0x200,%%ecx   \n" //;128 elements * 4 bytes = 512 (0x200) 
    "jb   loop    \n" //;Loop" 
    "nop       \n" 
     : "=m"(a)      //Outputs  
     : "m"(b), "m"(c)    //Inputs 
     : "%ecx","%ymm0"    //Modifies ECX and YMM0 
    ); 
} 

行が生成する「間違った」としてマーク(私が推測する):

vmovaps -0x10(%rbp,%ecx,%0x8),%ymm0 

しかし、%ecxをインデックスレジスタとして指定する方法はわかりません。

お願いします。

__asm__ __volatile__ (
    "nop       \n" 
    "xor  %%ecx, %%ecx  \n" //;Loop counter set to 0 
    "loop: \n\t" 
    "vmovaps (%1, %%rcx), %%ymm0   \n" //;Load 8 elements from b <== MODIFIED HERE 
    "vaddps  %2, %%ymm0, %%ymm0 \n" //;Add 8 elements from c 
    "vmovaps %%ymm0, %0   \n" //;Store result in a 
    "add  0x20, %%ecx   \n" //;8 elemtns * 4 bytes = 32 (0x20) 
    "cmp  0x200,%%ecx   \n" //;128 elements * 4 bytes = 512 (0x200) 
    "jb   loop    \n" //;Loop" 
    "nop       \n" 
     : "=m"(a)      //Outputs  
     : "m"(b), "m"(c)    //Inputs 
     : "%ecx","%ymm0"    //Modifies ECX and YMM0 
    ); 

そして、私が得た:

inline1.cpp: Assembler messages: 
inline1.cpp:90: Error: found '(', expected: ')' 
inline1.cpp:90: Error: junk `(%rbp),%rcx)' after expression 
+1

これにアセンブリを使用する必要はありません.gccには組み込み関数が組み込まれており、組み込み関数もサポートしています。 – Jester

+0

はい、わかっています。 ASMの使い方を学ぶだけです。 – Chocksmith

+0

'r'制約を使います。また、 'メモリ' clobberを追加することを忘れないでください。最良の効果を得るには、 'ecx'と' ymm0'をバインドしないでください。また、コンパイラにゼロを残しておいてください。ループターゲットを整列させることは言うまでもありません。もちろん、すでに言ったように、このためにアセンブリを使用してはいけません。 – Jester

答えて

4

私はそれが可能だとは思わない私は(%1、%% ECX)を試してみた

EDIT

これを文字どおりGASインラインアセンブリに変換します。 AT & T構文では、構文は次のとおりです。

に似て何かを生み出す
displacement(base register, offset register, scalar multiplier) 

:あなたが使用する場合の問題は、ある

vmovaps -16(%rsp, %ecx, 0), %ymm0 

movl -4(%ebp, %ecx, 4), %eax 

またはあなたのケースではメモリ制約(m)の場合、インラインアセンブラでは、次のように書くことになります。%n(ここで、nはth電子入出力):

-16(%rsp) 

実際に必要なフォームに上記を操作する方法はありません。あなたは書くことができます:

(%1, %%rcx) 

が、これは生成されます:

(-16(%rsp),%rcx) 

明らかに間違っています。 %nが全体として-16(%rsp)をチャンクとして放出しているので、オフセットレジスタを、それらのカッコ内の内に取得する方法はありません。

の速度を取得するためにインラインアセンブリを作成するので、もちろんこれは問題ではありません。メモリからの読み込みについては何もスピードがありません。レジスタに入力がある必要があります。また、入出力(r)にレジスタ制約を使用すると、問題はありません。これはあなたのインラインアセンブリと間違ってわずか

他のものには、あなたのコードを変更する必要がありますことに注意してください:

  1. 数値リテラルを$で始まります。
  2. 命令には、32ビットの場合はl、64ビットの場合はqのようなサイズの接尾辞が必要です。
  3. aで書き込むときにメモリが壊れているので、memoryのクローバーが必要です。
  4. 最初と最後のnopの説明は完全に無意味です。彼らは分岐ターゲットを整列させていません。
  5. 新しい行(\n)に加えて、すべての行は実際にはタブ文字(\t)で終了する必要があります。そのため、逆アセンブリを検査するときに適切な位置合わせが得られます。ここで

コードの私のバージョンです:

void addArray(float *a, float *b, float *c) { 
__asm__ __volatile__ (
    "xorl  %%ecx, %%ecx    \n\t" // Loop counter set to 0 
    "loop:         \n\t" 
    "vmovaps (%1,%%rcx), %%ymm0   \n\t" // Load 8 elements from b 
    "vaddps  (%2,%%rcx), %%ymm0, %%ymm0 \n\t" // Add 8 elements from c 
    "vmovaps %%ymm0, (%0,%%rcx)   \n\t" // Store result in a 
    "addl  $0x20, %%ecx    \n\t" // 8 elemtns * 4 bytes = 32 (0x20) 
    "cmpl  $0x200, %%ecx    \n\t" // 128 elements * 4 bytes = 512 (0x200) 
    "jb   loop"        // Loop" 
     :           // Outputs  
     : "r" (a), "r" (b), "r" (c)    // Inputs 
     : "%ecx", "%ymm0", "memory"    // Modifies ECX, YMM0, and memory 
    ); 
} 

これは、コンパイラは、次のことを発光させる:

addArray(float*, float*, float*): 
     xorl  %ecx, %ecx     
    loop:         
     vmovaps (%rsi,%rcx), %ymm0   # b 
     vaddps  (%rdx,%rcx), %ymm0, %ymm0 # c 
     vmovaps %ymm0, (%rdi,%rcx)   # a 
     addl  $0x20, %ecx    
     cmpl  $0x200, %ecx    
     jb   loop 
     vzeroupper 
     retq 

あるいは、より身近インテル構文で:

addArray(float*, float*, float*): 
     xor  ecx, ecx 
    loop: 
     vmovaps ymm0, YMMWORD PTR [rsi + rcx] 
     vaddps ymm0, ymm0, YMMWORD PTR [rdx + rcx] 
     vmovaps YMMWORD PTR [rdi + rcx], ymm0 
     add  ecx, 32 
     cmp  ecx, 512 
     jb  loop 
     vzeroupper 
     ret 

System V 64ビット呼び出し規約では、最初の3つのパラメータrdi,rsi、およびrdxレジスタに渡されるため、コードはパラメータをレジスタに移動する必要はありません。これらのパラメータはすでに存在しています。

しかし、あなたは最大限の入出力制約を使用していません。あなたはをカウンターとして使用する必要はありません。rcxスクラッチ・レジスタとしてymm0を使用する必要もありません。コンパイラにどの空きレジスタを使用させるかを選択させると、コードがより効率的になります。あなたはまた、明示的なクロバーリストを提供する必要がありません。もちろん

#include <stdint.h> 
#include <x86intrin.h> 

void addArray(float *a, float *b, float *c) { 
    uint64_t temp = 0; 
    __m256 ymm; 
    __asm__ __volatile__(
     "loop:      \n\t" 
     "vmovaps (%3,%0), %1  \n\t" // Load 8 elements from b 
     "vaddps  (%4,%0), %1, %1 \n\t" // Add 8 elements from c 
     "vmovaps %1, (%2,%0)  \n\t" // Store result in a 
     "addl  $0x20, %0  \n\t" // 8 elemtns * 4 bytes = 32 (0x20) 
     "cmpl  $0x200, %0  \n\t" // 128 elements * 4 bytes = 512 (0x200) 
     "jb   loop"     // Loop 
    : "+r" (temp), "=x" (ymm) 
    : "r" (a), "r" (b), "r" (c) 
    : "memory" 
    ); 
} 

、コメントで述べたように、この全体の運動は時間の無駄です。 GASスタイルのインラインアセンブリーは、強力ですが、正確に書くのが難しいです(私のコードは正解ではありません!)ので、絶対にしないインラインアセンブリーを使って何も書いてはいけません。はかなりです。する必要がある。 -O2-mavx2

void addArray(float *a, float *b, float *c) { 
    for (int i = 0; i < 128; i++) a[i] = b[i] + c[i]; 
} 

を、GCCは、以下にこれをコンパイルします:

addArray(float*, float*, float*): 
     xor  eax, eax 
    .L2: 
     vmovss xmm0, DWORD PTR [rsi+rax] 
     vaddss xmm0, xmm0, DWORD PTR [rdx+rax] 
     vmovss DWORD PTR [rdi+rax], xmm0 
     add  rax, 4 
     cmp  rax, 512 
     jne  .L2 
     rep ret 

まあ、これは確かに、コンパイラあなたが持っていない場合は、自動的に追加ループを最適化しますですそれはひどくおなじみですね。公正であるためには、あなたのコードのようにベクトル化されていません。 -O3または-ftree-vectorizeを使用して取得することもできますが、a lot more code generatedも得られるので、実際にはコードサイズのほうが速く、爆発的に価値があると私に納得させるためにベンチマークが必要です。しかし、これのほとんどは、入力が整列していないケース、つまりif you indicate that it is aligned and that the pointer is restricted, that solves these problems and improves the code generation substantiallyを処理することです。ループをアンロールし、加算をベクトル化するのは完全にであることに注意してください。

+1

あなたは、 'a'が指しているところから、あなたがメモリに書き込んでいる' a'に書きません。したがって、出力として 'a'は必要ありませんが、' memory' clobberが必要です。また、文法的に正しい '-16(%rsp、%ecx、0)'を発行することができたとしても、それは間接的なレベルがないので動作しません。最後に、わかりやすく示されたCコードは残念ながらベクトル化されていませんが、スカラーを使用しています。 – Jester

+0

おっと、そうです、@jester。ありがとう、固定。 '-O3'や' -ftree-vectorize'を使ってベクター化することもできますが、*たくさんのコードが追加されます。私は実際にそれが速かったことを検証するためにベンチマークする必要があります。おそらく私はそれが整列していたことを示していなかったからだろう。 –

+0

"この全練習は時間の無駄です" - そうではありません。私はこの練習でたくさんのことを学んだ。皆さんありがとう!あなたは最高です。 – Chocksmith

関連する問題