web-dev-qa-db-ja.com

C / C ++でのアセンブリ言語の使用

コードの特定のセクションを本当に最適化して高速化するために、プログラマーがアセンブリー言語でそのセクションを書いている場所を読んだことを覚えています。私の質問は-

  1. この練習はまだ行われていますか?そして、これをどのように行うのですか?
  2. アセンブリ言語で書くことは少し面倒で古風ではありませんか?
  3. Cコードをコンパイルするとき(-O3フラグの有無にかかわらず)、コンパイラーはコードの最適化を行い、すべてのライブラリーをリンクして、コードをバイナリー・オブジェクト・ファイルに変換します。したがって、プログラムを実行すると、プログラムはすでに最も基本的な形式、つまりバイナリになっています。では、「Assembly Language」の導入はどのように役立ちますか?

私はこの概念を理解しようとしています&ヘルプやリンクは大歓迎です。

PDATE: dbemerlinからの要求に応じてポイント3を言い換えると、コンパイラが生成するよりも効果的なアセンブリコードを記述できる可能性がありますが、アセンブラの専門家でない限り、コンパイラが最適化されることが多いため、コードの実行速度が遅くなる可能性があります。ほとんどの人間ができるよりも良いコード。

45

アセンブリ言語に戻すのが役立つのは、

  • cPU命令には、C++に同等の機能はありません(例:単一命令、複数データ命令、BCD、または10進算術演算)

    [〜#〜]または[〜#〜]

  • 不可解な理由-オプティマイザが最適なCPU命令を使用できない

...かつ...

  • これらのCPU命令を使用すると、ボトルネックコードのパフォーマンスが大幅に向上し、便利になります。

インラインアセンブリを使用して、C++で簡単に表現できる操作(2つの値の追加や文字列の検索など)を実行するだけでは、次の理由により、逆効果になります。

  • コンパイラはこれを同じようにうまく行う方法を知っています
    • これを確認するには、アセンブリの出力(例:gcc -S)を確認するか、マシンコードを逆アセンブルします
  • レジスタ割り当て、CPU命令などに関する選択を人為的に制限しているため、ハードコードされた命令を実行するために必要な値でCPUレジスタを準備するのに時間がかかり、その後の命令に最適な割り当てに戻るのに時間がかかる場合があります。 ____。]
    • コンパイラオプティマイザは、異なるレジスタを指定する同等のパフォーマンスの命令を選択して、それらの間のコピーを最小限に抑え、1つのコアが1サイクルで複数の命令を処理できるようにレジスタを選択し、特定のレジスタにすべてを強制するとシリアル化されます。 。]
      • 公平に言うと、GCCは、CPUを正確なレジスターに制約せずに特定のタイプのレジスターのニーズを表現する方法を備えており、そのような最適化を可能にしますが、これを解決するインラインアセンブリはこれだけです。
  • 同じCPUのモデルが来年、同じ論理演算で1000%高速な別の命令を発表する場合、コンパイラベンダーはその命令を使用するようにコンパイラを更新する可能性が高いため、プログラムを再コンパイルすると、プログラムよりもメリットが大きくなります。 (またはソフトウェアを保守している人は誰でも)
  • コンパイラは、ターゲットアーキテクチャに最適なアプローチを選択します。1つのソリューションをハードコードする場合、プラットフォームの最小公分母または#ifdef- edにする必要があります。
  • アセンブリ言語は、CPU全体とコンパイラ全体の両方でC++ほど移植性が高くありません。命令を移植しているように見えても、クローバーや引数渡しの規則などに対して安全なレジスタを誤って登録する可能性があります。
  • 他のプログラマーはアセンブリを知らないか、使い慣れていない可能性があります

私が心に留めておく価値があると思う1つの視点は、Cが導入されたとき、生成されたマシンコードに悩まされた多くの筋金入りのアセンブリ言語プログラマーに勝たなければならなかったということです。マシンのCPUパワーは低下し、RAM当時、人々は最も小さなことで大騒ぎしていました。オプティマイザは非常に洗練され、改良が続けられていますが、x86のようなプロセッサのアセンブリ言語はパフォーマンスに関係する実行パイプライン、キャッシュ、その他の要素があるように、ますます複雑になります。命令あたりのサイクル数のテーブルから値を追加することはできません。コンパイラの作成者は、これらすべての微妙な要素(特に、 CPUメーカーにとっては、他のコンパイラへのプレッシャーも高まります。アセンブリプログラマにとって、重要なアプリケーション全体に対して、優れた最適化コンパイラによって生成されるコードよりもはるかに優れたコードの効率を平均化することは非現実的であり、圧倒的です。したがって、Assemblyの使用は、測定と有用な違いが実際に生じる時間に制限する必要があり、カップリングとメンテナンスのコストに見合う価値があります。

30
Tony Delroy

まず、プログラムのプロファイルを作成する必要があります。次に、CまたはC++コードで最もよく使用されるパスを最適化します。 利点が明らかでない限り、アセンブラで書き換えないでください 。アセンブラーを使用すると、コードの保守が困難になり、移植性が大幅に低下します。非常にまれな状況を除いて、価値はありません。

15
sharptooth

(1)はい、これを試す最も簡単な方法はインラインアセンブリを使用することです。これはコンパイラに依存しますが、通常は次のようになります。

__asm
{
    mov eax, ebx
}

(2)これは非常に主観的です

(3)コンパイラが生成するよりも効果的なアセンブリコードを記述できる可能性があるため。

10
Andreas Brinck

あなたは古典的な本を読むべきですZen of Code OptimizationとフォローアップZen of Graphics Programming by Michael Abrash

彼は最初の本で要約して、制限に追いやられたアセンブリプログラミングの使い方を説明しました。その後のフォローアップで、プログラマーはCのような高レベルの言語を使用し、必要に応じて、アセンブリを使用して特定のスポットのみを最適化するようにすべきであると説明しました。

この心変わりの1つの動機は、ある世代のプロセッサ向けに高度に最適化されたプログラムが、同じレベルの次世代プロセッサでは、高級言語からコンパイルされたコード(たぶん、新しい命令を使用するコンパイラ)と比較して(ある程度)遅くなる可能性があることでした。たとえば、既存のプロセッサのパフォーマンスと動作がプロセッサの世代から別の世代に変化した場合など)。

もう1つの理由は、コンパイラーが非常に優れており、現在は積極的に最適化しているためです。通常、Cコードをアセンブリーに変換するアルゴリズムの作業には、より多くのパフォーマンスがあります。 GPU(グラフィックカードプロセッサ)プログラミングでも、cudaまたはOpenCLを使用してCでプログラミングできます。

通常はハードウェアを非常に細かく制御するために、アセンブリを使用する必要がある(まれな)場合がまだいくつかあります。しかし、OSカーネルコードでも、通常は非常に小さなパーツであり、それほど多くのコードではありません。

6
kriss

SSEや古いMMXのような低レベルの構成要素でさえ、最近アセンブリ言語を使用する理由はほとんどありません(gccとMSVCの両方に組み込みの組み込み関数があります)それ)。

正直なところ、最近のオプティマイザーはめちゃくちゃ積極的で、ほとんどの人は、アセンブリでコードを作成するパフォーマンスの半分にさえも一致できませんでした。メモリ内でのデータの順序付け方法(局所性のため)を変更したり、コンパイラにコードの詳細を(#pragmaを使用して)通知したりできますが、実際にアセンブリコードを記述します。

@VJo、高レベルCコードで組み込み関数を使用すると、単一のAssembly命令を使用せずに同じ最適化を実行できることに注意してください。

そして、その価値については、次のMicrosoft C++コンパイラー、およびインラインアセンブリーをそこから削除する方法についての議論がありました。それはそれの必要性について多くを語っています。

4
Blindy

プロセッサを指定したとは思わない。プロセッサーと環境によって答えは異なります。一般的な答えははい、それはまだ行われています、それは確かに古風ではありません。一般的な理由はコンパイラーです。一般に最適化には優れているものの、特定のターゲットには適さない場合があります。あるターゲットでは本当に上手く、他のターゲットではそれほど上手ではない人もいます。ほとんどの場合それで十分ですが、ほとんどの場合、移植不可能なアセンブラーではなく、移植可能なCコードが必要です。しかし、Cライブラリがmemcpyや他のルーチンを手動で最適化しても、コンパイラーがそれを実装する非常に高速な方法があることを簡単に理解できないことに気づくでしょう。そのコーナーケースはコンパイラーの最適化に時間を費やす価値がないため、アセンブラーでそれを解決するだけで、ビルドシステムには、このターゲットがCを使用する場合、そのターゲットがCを使用する場合、そのターゲットがasmを使用する場合、ターゲットはasmを使用します。したがって、それはまだ発生しており、一部の領域では永久に継続する必要があると私は主張します。

X86は多くの歴史を持つ独自の獣であり、常により速いアセンブラーの1つのBLOBを実際に作成することが実際には不可能であり、特定のマシンの特定のプロセッサーのルーチンを確実に最適化することができます日、そしてコンパイラを実行します。いくつかの特定の場合を除いて、それは一般的に無駄です。教育的ですが、全体的に時間の価値はありません。また、プロセッサはもはやボトルネックではないので、ずさんな一般的なCコンパイラで十分です。他の場所でパフォーマンスを見つけてください。

組み込み、arm、mips、avr、msp430、picなどを意味することが多い他のプラットフォーム。オペレーティングシステムを実行している場合と実行していない場合があり、キャッシュやデスクトップにあるその他の機能を実行している場合とそうでない場合があります。したがって、コンパイラの弱点が明らかになります。また、プログラミング言語は、プロセッサではなくプロセッサから進化し続けていることにも注意してください。おそらく低水準言語と見なされているCの場合でも、命令セットと一致しません。コンパイラよりも優れたアセンブラのセグメントを作成できる場合は常にあります。必ずしもボトルネックとなっているセグメントとは限りませんが、プログラム全体で、あちこちで改善を加えることができます。あなたはまだそれを行うことの価値をチェックする必要があります。組み込み環境では、製品の成功と失敗を区別できます。製品のユニットあたり25ドルがより多くの電力を必要とする、ボードの不動産、より高速なプロセッサに投資されているため、アセンブラを使用する必要はないが、競合他社はユニットあたり10ドル以下しか消費せず、AとCを混ぜて小さなメモリを使用する場合、より少ない電力、より安い部品などを使用します。NREが回復する限り、長期的にはasmソリューションと混合されます。

True Embeddedは、専門のエンジニアがいる専門の市場です。別の組み込み市場、組み込みのLinux roku、tivoなど。組み込みの電話などは、サードパーティの開発者が必要であるため、存続するにはポータブルオペレーティングシステムが必要です。そのため、プラットフォームは組み込みシステムよりもデスクトップのようにする必要があります。前述のようにCライブラリまたはオペレーティングシステムに埋め込まれているため、アセンブラの最適化が行われる場合がありますが、デスクトップと同様に、ハードウェアを増やして、ソフトウェアを手動で最適化する代わりにポータブルにしたいとします。また、サードパーティの成功にアセンブラが必要な場合、製品ラインまたは組み込みオペレーティングシステムは失敗します。

私の最大の懸念は、この知識が驚くべき速度で失われていることです。アセンブラを検査したり、アセンブラで書き込んだりする人がいないためです。生成されるコードに関してコンパイラが改善されていないことに誰も気付いていません。多くの場合、開発者は、コンパイラーやプログラムの方法を理解することで、同じコンパイラーで(場合によっては同じソースコードで)パフォーマンスを5〜数百パーセント向上できることに気付く代わりに、ハードウェアを購入する必要があると考えています。通常、同じソースコードとコンパイラで5〜10%。 gcc 4は常にgcc 3よりも優れたコードを生成するわけではありません。時々gcc3の方が優れているため、両方を保持します。特定のコンパイラをターゲットにすると、(常にそうとは限らないが)gccを中心に円を描くことができます。同じソースコードの異なるコンパイラでも、数百パーセントの改善が見られることがあります。これはどこから来たのですか?まだ見たり、アセンブラを使用したりしている人々。それらの人々の何人かはコンパイラのバックエンドで働いています。フロントエンドとミドルは確かに楽しく教育的ですが、バックエンドは、結果として生じるプログラムの品質とパフォーマンスを作成または破壊する場所です。アセンブラーを作成せず、コンパイラーからの出力のみを時々見る場合(gcc -O2 -s myprog.c)でも、より優れた高水準のプログラマーになり、この知識の一部を保持します。誰もがアセンブラを知り、作成する気がない場合、定義により、高水準言語およびソフトウェア一般のコンパイラの作成および保守をあきらめたことはなくなります。

たとえば、gccを使用すると、コンパイラの出力はAssemblyに渡され、オブジェクトコードに変換するAssemblyに渡されます。 Cコンパイラは通常、バイナリを生成しません。オブジェクトは、最終的なバイナリに結合されると、リンカーによって実行されます。さらに、コンパイラーによって呼び出され、コンパイラーの一部ではない別のプログラムです。コンパイラーはC、C++、ADAなどをアセンブラーに変換し、アセンブラーおよびリンカーツールが残りの処理を行います。たとえばtccのような動的リコンパイラーは、なんとかしてその場でバイナリを生成できなければなりませんが、例外として、ルールではないと思います。 LLVMには独自のランタイムソリューションがあり、クロスコンパイラとして使用すると、内部コードからバイナリコードへのターゲットコードへの高レベルのコードが目に見える形で表示されます。

したがって、要点に戻ると、そうです、あなたが思っているよりも頻繁に行われます。ほとんどの場合、命令セットと直接比較しない言語に関係しており、コンパイラは常に十分な速度でコードを生成するわけではありません。 mallocやmemcpyのような頻繁に使用される関数を数十倍に改善できるとしたら。または、ハードウェアサポートなしで携帯電話にHDビデオプレーヤーを持ちたい場合は、アセンブラーの長所と短所のバランスを取ります。真に埋め込まれた市場では、依然としてアセンブラがかなり使用されています。場合によってはすべてCですが、ソフトウェアが完全にアセンブラでコーディングされていることもあります。デスクトップx86の場合、プロセッサはボトルネックではありません。プロセッサーはマイクロコード化されています。表面上で見た目が美しいアセンブラを作成したとしても、すべてのファミリのx86プロセッサで実際に高速に実行されるわけではありません。

Arm、thumb/thumb2、mips、msp430、avrなどのx86以外のISAのアセンブラを学習することを強くお勧めします。コンパイラーを持つターゲット、特にgccまたはllvmコンパイラー・サポートを持つターゲット。アセンブラーを学び、Cコンパイラーの出力を理解することを学び、実際にその出力を変更してテストすることで、より良い結果が得られることを証明します。この知識は、アセンブラーなしで、より高速で信頼性の高いデスクトップの高レベルのコードを作成するのに役立ちます。

4
old_timer

場合によります。これは(まだ)いくつかの状況で行われていますが、ほとんどの場合、それだけの価値はありません。最近のCPUはめちゃくちゃ複雑で、効率的なアセンブリコードを記述するのも同様に複雑です。そのため、ほとんどの場合、手動で作成したアセンブリは、コンパイラが生成できるものよりも遅くなります。

最近数年以内にリリースされた適切なコンパイラーを想定すると、通常はC/C++コードを微調整して、Assemblyを使用する場合と同じパフォーマンス上の利点を得ることができます。

ここのコメントと回答の多くの人々は、アセンブリで何かを書き直した「N倍のスピードアップ」について話していますが、それだけではあまり意味がありません。流体力学の方程式を評価するC関数を書き直すことで、13倍のスピードアップが得られましたC、ハードウェアを知っていることで、アセンブリで記述する場合と同じ最適化の多くを適用し、プロファイリング。最後に、それはCPUの理論的なピークパフォーマンスに十分近づき、アセンブリでの書き換えでポイントなしが発生しました。通常、それは制限要因である言語ではなく、あなたが書いた実際のコードです。コンパイラーが困難な「特別な」命令を使用していない限り、よく書かれたC++コードを打ち負かすことは困難です。

組み立ては魔法の速さではありません。コンパイラがループから抜け出すだけです。 reallyが何をしているかを知らない限り、コンパイラは多くの最適化を実行しますが、手動で行うのは本当に面倒です。ただし、まれに、コンパイラがコードを理解できず、効率的なアセンブリを生成できない場合がありますthen。アセンブリを自分で作成すると便利な場合があります。ドライバーの開発など(ハードウェアを直接操作する必要がある場合)以外で、効率的なSSE組み込み関数(MSVCなど)からのコード。そこでも、C++の組み込み関数の使用を開始し、それをプロファイリングして可能な限り微調整しようとしますが、コンパイラーはこれがあまり得意ではないため、最終的にはそのコードを書き直す価値がありますアセンブリで。

3
jalf
  1. 「この練習はまだ行われていますか?」 ->画像処理、信号処理、AI(効率的な行列乗算など)、その他で行われます。 Macbookのトラックパッドでのスクロールジェスチャーの処理も、即時であるため、部分的にはアセンブリコードであると思います。 -> C#アプリケーションでも実行されます( https://blogs.msdn.Microsoft.com/winsdk/2015/02/09/c-and-fastcall-how-to-make-them-を参照)。 work-together-without-ccli-shellcode /

  2. 「アセンブリ言語で書くのは少し面倒で古臭いのではないですか?」 ->ハンマーやドライバーなどの工具で、時計職人のドライバーが必要な作業もあります。

    1. 「(-O3フラグの有無にかかわらず)Cコードをコンパイルすると、コンパイラーはコードの最適化を行います...では、「アセンブリ言語」の導入はどのように役立ちますか?」 -> @jalfの発言が気に入っています。Cコードをアセンブリを作成する方法で作成すると、すでに効率的なコードが作成されます。ただし、これを行うには、アセンブリ言語でコードを記述する方法を考える必要があります。データがコピーされるすべての場所を理解する(そして、それが不必要になるたびに痛みを感じる)。アセンブリ言語を使用すると、どの命令が生成されるかを確認できます。 Cコードが効率的であっても、結果のアセンブリがすべてのコンパイラで効率的である保証はありません。 ( https://lucasmeijer.com/posts/cpp_unity/ を参照)->アセンブリ言語を使用すると、バイナリを配布するときに、cpuをテストし、cpuの機能に応じて異なるブランチを作成できますAVXまたはSSE向けに最適化されていますが、配布する必要があるのは1つのバイナリのみです。組み込み関数を使用すると、C++または.NET Core 3でもこれが可能になります( https://devblogs.Microsoft.com/dotnet/using-net-hardware-intrinsics-api-to-accelerate-machine-learning-を参照)。シナリオ/
2
David

ご覧ください こちら 。ここで、男はアセンブリコードを使用してパフォーマンスを6倍改善しました。したがって、答えは次のとおりです。それはまだ行われていますが、コンパイラーはかなり良い仕事をしています。

2
BЈовић

私が行ったアセンブリの最適化の例がありますが、やはり埋め込みターゲットです。 PC用のアセンブリプログラミングのいくつかの例も見ることができ、それは本当に小さくて高速なプログラムを作成しますが、通常は努力する価値はありません(「Windows用のアセンブリ」を探してください。非常に小さくてきれいなプログラムが見つかります)。

私の例は、プリンターコントローラーを作成しているときに、50マイクロ秒ごとに呼び出されるはずの関数がありました。それは多かれ少なかれビットの入れ替えをしなければなりません。 Cを使用すると、約35マイクロ秒でそれを行うことができ、アセンブリを使用すると、約8マイクロ秒でそれを実行できました。これは非常に具体的な手順ですが、それでも現実的で必要なものです。

1
SurDin

一部の組み込みデバイス(電話およびPDA)では、コンパイラーがそれほど成熟しておらず、非常に遅く、正しくないコードを生成する可能性があるため、これは便利です。私は個人的に、ARMベースの組み込みプラットフォーム用のいくつかの異なるコンパイラーのバグのある出力を回避するか、修正するアセンブリコードを記述する必要がありました。

1
Graham Borland

私の作業では、組み込みのターゲット(マイクロコントローラー)のアセンブリを使用して低レベルのアクセスを行いました。

しかし、PCソフトウェアの場合、それはあまり役に立たないと思います。

1
Benoît
  1. はい。インラインアセンブリを使用するか、アセンブリオブジェクトモジュールをリンクします。どちらの方法を使用するかは、記述する必要のあるアセンブリコードの量によって異なります。通常、数行にインラインアセンブリを使用し、複数の関数の場合は個別のオブジェクトモジュールに一度切り替えても問題ありません。
  2. 確かに、しかし時にはそれは必要です。ここでの顕著な例は、オペレーティングシステムのプログラミングです。
  3. 今日のほとんどのコンパイラーは、高水準言語で記述したコードを、誰もがアセンブリコードを記述できるよりもはるかに最適化しています。ほとんどの場合、Cなどの高級言語では記述できないコードを記述するために使用します。他の目的で使用する場合は、最新のコンパイラよりも最適化が優れているか(疑わしい)、または単純に愚かです。 、例えば彼は、使用するコンパイラフラグまたは関数属性を知りません。
0
flacs