2009-07-16 9 views
10

私はインラインアセンブリを使用して私のコンパイラを打つのに苦労しています。インラインアセンブリでより高速に実装される単純なC関数の例は何ですか?

コンパイラが実際に、本当に速く簡単にするのに苦労している機能の、良い、非人為的な例は何ですか?しかし、これはインラインアセンブリで行うのが比較的簡単です。

+7

あなたを選ぶことはできませんが、非常に多くの人が最適化とスピードの質問をしています。彼らは要件を満たしていないため、必要と言っている人はほとんどいません。どうやら私たちは "時期尚早な最適化は、すべての悪の根源だ"と打ち勝っていませんでした。 –

+0

私の質問には、私がiPhoneのインラインアセンブリを使っていたことがありました。 。 しかし、私の人生は私のコンパイラを上回ることができませんでした。だから私は、コンパイラが非効率的なコードを生成するエッジケースが知られているかどうか不思議に思っています。 –

+1

ARMアセンブリは、「クリーンな」命令セットの1つです。 RISCプロセッサの哲学の一部は、コンパイラが容易に使用できない命令を追加しないことです。特定のARMバリアントの命令セットを見て、明確なC変換を持たないオペコードを見つけなければなりません。 – NoMoreZealots

答えて

7

それはiPhoneとアセンブリコードに関連しているので、私はiPhoneの世界で(そしていくつかのsseまたはx86 asmではなく)関連する例を挙げます。 誰かが実際のアプリケーション用のアセンブリコードを書くことを決めたら、これはデジタル信号処理や画像操作のようなものになるでしょう。例:RGBピクセルの色空間を変換したり、画像をJPEG/PNG形式にエンコードしたり、サウンドをmp3、amr、またはg729にエンコードしてvoipアプリケーションに使用します。 サウンドエンコーディングの場合、コンパイラによって効率的なasmコードに変換できないルーチンが多数あります。これらは単にCで同等のものはありません。サウンド処理でよく使われる例:飽和演算、積和ルーチン、行列乗算。

飽和加算の例:32ビット符号付き整数の範囲は、0x8000 0000 < = int32 < = 0x7fff ffffです。 2つのintを加算すると結果がオーバーフローする可能性がありますが、デジタル信号処理ではこれが受け入れられない場合があります。基本的に、結果がオーバーフローまたはアンダーフローした場合、addは0x8000 0000または0x7fff ffffを返します。それはそれをチェックする完全な関数になります。飽和アドオンの 最適化されたバージョンは次のようになります/他の場合にも、オーバーフロー用またはx86をチェックするために、複数行うことができます

 
int saturated_add(int a, int b) 
{ 
    int result = a + b; 

    if (((a^b) & 0x80000000) == 0) 
    { 
     if ((result^a) & 0x80000000) 
     { 
      result = (a < 0) ? 0x80000000 : 0x7fffffff; 
     } 
    } 
    return result; 
} 

あなたは(もASMを使用する必要がある)、オーバーフローフラグをチェックすることができます。 iPhoneはdsp asmを持つarmv6またはv7のCPUを使用します。したがって、複数のbrunches(if/elseステートメント)と2つの32ビット定数を持つsaturated_add関数は、1つのCPUサイクルを使用する単純なasm命令になります。 したがって、単に飽和命令をasm命令を使用するようにすると、アルゴリズム全体を2〜3倍高速に(サイズを小さくして)作ることができます。ここでQADDマニュアルです:多くの場合、長いループで実行されるコードの QADD

他の例である

 
res1 = a + b1*c1; 
res2 = a + b2*c2; 
res3 = a + b3*c3; 

は何もここで最適化することができないように思えるが、ARM CPU上で、あなたは、特定のDSP命令を使用することができます簡単な乗算を行うよりもサイクルを減らしてください!そうです、具体的な指示のある+ b * cは、単純なa * bより速く実行できます。この種のケースでは、コンパイラは単にコードのロジックを理解することができず、これらのdsp命令を直接使用することはできないため、コードを最適化するために手動でasmを書く必要があります。しかし、最適化された。あなたが手作業で単純なループを書くことを開始すれば、ほぼ確実にコンパイラを打ち破ることができます! インラインアセンブリー用のWeb上に、全文フィルタ、AMRエンコード/デコードなどのコードを記述した複数の良い記事があります。

0

私はコンパイラを使って単純なmemcpyルーチンを使っていましたが...基本的なセットアップをたくさんスキップしました(例えば、スタックフレームはあまり必要ありませんでした。 )、いくつかのかなり毛深いものをしました。

これは、約6年前、未知の品質の独自のコンパイラを使用していました。私が持っていたコードを掘り下げて、GCCに対して今試してみる必要があります。私はそれがもっと速くなることは知りませんが、私はそれを排除しません。

私のmemcpyは私たちのCライブラリのものよりも平均で約15倍速いものの、私はそれが必要な場合に備えてバックポケットに入れました。 PPCアセンブリで遊ぶのはおもちゃだったし、私たちのアプリケーションではスピードブーストは必要なかった。

2

SIMD操作のような操作をしたい場合は、コンパイラを破ることができます。これはアーキテクチャと命令セットの良い知識を必要とするでしょう。

+0

アセンブリを扱う際には、アーキテクチャと命令セットを理解することの重要性を過小評価することはできません。私は典型的にはasmを避けますが、アーキテクチャの機能を学ぶためにはまだポイントを作っていますので、理論的なパフォーマンスを知ることができます。 – NoMoreZealots

8

あなたが浮気SIMD演算を考慮しない場合(それも自動ベクトルを持っている場合!)、あなたは通常、あなたのコンパイラの自動ベクトル能力よりもはるかに良好に機能するSIMDアセンブリを書くことができます

Here's非常に基本的なSSE(のx86のの一つSIMD命令セット)チュートリアル。 Visual C++のインラインアセンブリ用です。

編集:自分で試してみたい場合は、小さなペアの機能があります。それはn長のドット積の計算です。 1つはSSE 2命令インライン(GCCインライン構文)を使用していますが、もう1つは非常に基本的です。

良いコンパイラが単純なCループをベクトル化できない場合は非常に簡単ですSSE2で速度が上がるはずです。より多くのレジスタを使用すると、SSE 2バージョンはおそらくもっと速くなる可能性がありますが、私は非常に弱いSSEスキルを伸ばしたくありません:)。

float dot_asm(float *a, float*b, int n) 
{ 
    float ans = 0; 
    int i; 
    // I'm not doing checking for size % 8 != 0 arrays. 
    while(n > 0) { 
    float tmp[4] __attribute__ ((aligned(16))); 

    __asm__ __volatile__(
      "xorps  %%xmm0, %%xmm0\n\t" 
      "movups  (%0), %%xmm1\n\t" 
      "movups  16(%0), %%xmm2\n\t" 
      "movups  (%1), %%xmm3\n\t" 
      "movups  16(%1), %%xmm4\n\t" 
      "add  $32,%0\n\t" 
      "add  $32,%1\n\t" 
      "mulps  %%xmm3, %%xmm1\n\t" 
      "mulps  %%xmm4, %%xmm2\n\t" 
      "addps  %%xmm2, %%xmm1\n\t" 
      "addps  %%xmm1, %%xmm0" 
      :"+r" (a), "+r" (b) 
      : 
      :"xmm0", "xmm1", "xmm2", "xmm3", "xmm4"); 

    __asm__ __volatile__(
     "movaps  %%xmm0, %0" 
     : "=m" (tmp) 
     : 
     :"xmm0", "memory");    

    for(i = 0; i < 4; i++) { 
     ans += tmp[i]; 
    } 
    n -= 8; 
    } 
    return ans; 
} 

float dot_c(float *a, float *b, int n) { 

    float ans = 0; 
    int i; 
    for(i = 0;i < n; i++) { 
    ans += a[i]*b[i]; 
    } 
    return ans; 
} 
+1

SIMDは間違いなく不正行為ではありません。コンパイラがハードウェアに追いついていない場所を明確に示しています。 Cは命令レベル並列化をうまく処理しません。たぶん、ここではループを巻き戻すことができますが、より進歩的なルーチンには深刻な調整が必要です。 – NoMoreZealots

+0

SIMD命令を出力するコンパイラはたくさんあります。 – jrockway

+0

これは限られた場合に限られます。基本的には、コードが共通のテクニックやアルゴリズムで書かれている限りです。命令セットが大きくなりすぎると、コンパイラやオプティマイザを単純に複雑にすると、多くの命令を最適に使用することができなくなります。これは「RISC」プロセッサーコンセプトの基盤の大部分でした。最適化はチェスと同じように、コンピュータは大半の人を打ち負かすことができますが、壮大なマスターに勝つためにはデスクトップ以上のものが必要です。 – NoMoreZealots

6

あなたは、コンパイラを破ってのassembly guruオッズない限り非常に低いです。

例えば上記リンク、

から断片、ビット指向の「XOR %EAX、EAX%」命令は、初期世代でゼロ にレジスタを設定する 最速の方法でしたほとんどのコードは コンパイラとコンパイラによって生成されることはめったにありません XOR命令を生成しました。だから、IA 設計者は、アップ文字通り「MOVL $ 0%EAX」 命令を作る組み合わせデコードロジック の前 に 頻繁に発生するコンパイラ 生成された命令を移動することを決めたが XOR命令よりも高速に実行します。

+4

私はアセンブリの教祖ではなく、私はコンパイラを打ち倒しました。私はまれに組立に頼ることはほとんどありません。私がしなければならなかったのは最後の手段だった。これはちょうどいいえのように思える。それは彼の質問を無視する。彼はそれが簡単ではないことを認めている。 – NoMoreZealots

+1

私はそれが不可能だとは言わなかった。命令セットを壊した場合は、より高速なコードを書くか、ルーチンをより少ない命令に絞ることができます。あまり洗練されていないコンパイラがある場合、またはコンパイラがsse、3dnowのセットを処理しない場合は、アセンブリの作成はいくつかのルーチンを実装する*適切な方法かもしれません。 –

+1

コンパイラを打ち破ることを望むなら、命令セットを理解することは絶対必要です。しかし、良いコンパイラを使っていても、現代のアーキテクチャー上でうまく対応するC構造を持たない命令を見つけることができます。マルチコア・パラダイムが標準になるにつれ、より大きなものになる抽象概念にはまだ「ギャップ」があります。また、今日の電力消費意識とモバイル駆動市場では、アプリケーションのCPUコア速度が高速になるとは想定していません。 1999年にCPUが1GHzを突破し、新しいアプリが「ホットな」ハードで稼動している今日は400MHzでクロッキングしています。 – NoMoreZealots

5

一般的な「ストレートC」実装を使用して単純な相互相関を実装しました。そして、私が利用可能なタイムスライスよりも時間がかかったとき、私はアルゴリズムの明示的な並列化と、プロセッサ固有の命令を使用して計算に使用されるようにしました。この特定のケースでは、計算時間は30ms以上から4ms以上に短縮されました。次のデータ収集が行われる前に、処理を完了するために15msのウィンドウがありました。

これは、VLWIプロセッサでのSIMDタイプの最適化です。これは、基本的にアセンブラ言語命令であり、ソースコード内での関数呼び出しの外観を与えるプロセッサイントリンシクスを4つ程度しか必要としません。インラインアセンブリーでも同じことができますが、シンタックスとレジスタの管理はプロセッサ組み込み関数で少し良くなります。

サイズ以外に問題がある場合は、アセンブラはキングです。私は512バイト未満でフルスクリーンテキストエディタを書いた男と一緒に学校に行きました。

+0

これはアセンブラが分かりやすい典型的なケースです。コードはCで書かれています。働いたが、十分に速くはなかった。アセンブラでの再コーディングは、それが十分速く動作するようにしました - それがアセンブラに落とす良い理由でした。 –

+0

私はひどいCバージョンから抜け出したパフォーマンスには不満を抱いていました。チップベンダーの宣伝は、そのCコンパイラがいかに良いか自慢していました。そして、彼らは最新のツールチェーンでも、それを最適化するより良い仕事はしていません。残念ながら、VLWIを持つDSPはオプティマイザを書くのが容易ではありません。 – NoMoreZealots

5

私はチェックサムアルゴリズムを使用しています。このアルゴリズムでは、ワードをあるビット数だけ回転させる必要があります。

//rotate word n right by b bits 
#define ROR16(n,b) (((n)>>(b))|(((n)<<(16-(b)))&0xFFFF)) 

//... and inside the inner loop: 
sum ^= ROR16(val, pos); 

VisualStudioをリリースビルドは、このように展開:それを実装するために、私はこのマクロを持っている(valsumはBXであり、posはDXであり、斧である)

mov   ecx,10h 
sub   ecx,edx 
mov   ebp,eax 
shl   ebp,cl 
mov   cx,dx 
sar   ax,cl 
add   esi,2 
or   bp,ax 
xor   bx,bp 

より効率的な同等の手で生成されたアセンブリは、次のようになります。

mov  cl,dx 
ror  ax,cl 
xor  bx,ax 

私は純粋な「C」からror命令を発する方法を考え出したていませんコード。しかし...
これを書いているうちに、コンパイラ組み込み関数を思い出しました。だから私の答えは

sum ^= _rotr16(val,pos); 

:あなたは純粋なCコンパイラを打つことができると思う場合でも、アセンブリのインライン化を頼る前に、組み込み関数をチェック私は命令の第2のセットを生成することができます。

+0

良い具体例です。 – NoMoreZealots

+0

私はgcc(4.0.1)で-O4を試しました。 32ビットローテートのROR命令を出力しますが、16ビットは出力しません。 – finnw

関連する問題