問題タブ [llvm-codegen]
For questions regarding programming in ECMAScript (JavaScript/JS) and its various dialects/implementations (excluding ActionScript). Note JavaScript is NOT the same as Java! Please include all relevant tags on your question; e.g., [node.js], [jquery], [json], [reactjs], [angular], [ember.js], [vue.js], [typescript], [svelte], etc.
types - LLVM の整数型
LLVM 言語は整数型を iN として指定します。N は整数のビット幅で、範囲は 1 から 2^23-1 です ( http://llvm.org/docs/LangRef.html#integer-タイプ)
2 つの質問があります。
C プログラムを LLVM IR レベルまでコンパイルする場合、どの型を i1、i2、i3 などに下げることができますか? 型 i8、i16、i32、i64 で十分のように思われるので、他の約 800 万の整数型は何のためにあるのだろうと思っていました。
符号付き整数型と符号なし整数型の両方が i32 に下げられるというのは本当ですか? その理由は何ですか? また、32 ビット浮動小数点 (LLVM では f32 として表される) のようなものに適用されないのはなぜですか?
c - C LLVM を使用した ABI
私は LLVM で書かれたコンパイラを持っており、ABI 準拠を目指しています。たとえば、Windows x86 または Linux での C ABI の仕様ドキュメントを実際に見つけるのは難しいと感じました。そして、私が見つけたものは、私が使用できる IR 用語ではなく、RAX/EAX/etc の観点から説明しています。
これまでのところ、LLVM は集合体を目に見えないように扱っている、つまり、集合体のメンバーをそれぞれ別個のパラメーターと見なしていると考えてきたと思います。たとえば、Windows x64 では、ドキュメントにあるように集計を処理したい場合、8、16、32、または 64 ビットの場合、そのサイズの単一の整数に強制する必要があります。それ以外の場合は、ポインターで渡します。
Windows x86 の場合、すべてのパラメーターがスタックに渡されるため、__cdecl と __stdcall は私からのアクションを必要としないようです。__fastcall によると、最初の 2 つの 32 ビット以下の引数はレジスターで渡されるため、そのサイズ以下の集約を強制する必要があります。__thiscall はこれをレジスタに渡し、残りはスタックに渡すので、ここで調整を行う必要はないようです。
__vectorcall の場合、整数型強制により sizeof(void*) 以下の集合体を渡します。その他の集計については、それらが HVA の場合は値で渡します。それ以外の場合は、x86 では値渡し、x64 ではポインター渡しです。
これは (まあ、比較的) 単純に思えますが、LLVM のドキュメントにはsext
明確に記載されています。呼び出し元 (パラメーターの場合) または呼び出し先 (戻り値の場合) によって。". x86 呼び出し規則に関する Microsoft のページでは、任意の幅に何かを拡張することについては何も言及されていません。
byval
また、Windows で属性を生成する Clang によって生成された LLVM IR を観察しました。上記から得た理解では、byval
の使用は必要ありません。
さまざまなプラットフォーム C ABI を LLVM IR に下げるにはどうすればよいですか?
llvm-clang - キャリーフラグとゼロフラグの LLVM の使用
LLVM のドキュメントと IR のドキュメントを読み始めています。
一般的なアーキテクチャでは、asmcmp
命令の「結果」の値は、少なくとも 3 ビットの長さです。たとえば、最初のビットが SIGN フラグ、2 番目のビットが CARRY フラグ、3 番目のビットが ZERO フラグであるとします。
質問1)
IR icmp 命令の結果の値が i1 しかないのはなぜですか? (フラグは 1 つだけ選択できます)
IR が定義しないのはなぜicmp2
ですか? SIGN、CARRY、および ZERO フラグを持つ i3 を返す命令と呼びましょう。
この i3 値は、次のような switch 命令または特定の br2 命令で処理できます。
質問2)
これは理にかなっていますか?このbr2
指示は、新しい最適化の作成に役立ちますか? つまり、すべての jmp を削除しますか? それは必要ですか、それともパフォーマンスの向上は無視できますか?
私がこれを尋ねている理由は、LLVM の専門家ではないことに加えて、最初のテストで、比較を 2 回行うことを避け、asm を使用してすべての分岐を回避するために、LLVM によって何らかの最適化が行われることを期待していたからです。条件付き移動命令。
私のテスト:
私はこれをclang-LLVMでコンパイルしました:
出力 asm は: ...
私は期待していました(内側のループですべてのjmpが削除されました):
パフォーマンスの差 (1 秒) は無視できるようです (VirtualBox の VM 上):
- LLVM 生成 asm: 12.53 秒
- ハンコーディングされた asm: 11.53 秒
- 差分: 1 秒、5 億回の反復で
質問 3)
パフォーマンス測定値は正しいですか? makefile と完全な hancoded.compare.s は次のとおりです。
メイクファイル:
ハンドコーディング (固定) asm:
rust - Rust inline asm でデフォルト以外の丸めモードを設定すると、LLVM オプティマイザーによって尊重されませんか?
丸めモード (+inf、-inf、最も近い、または切り捨て) を変更する Rust クレートに取り組んでいます。
丸めモードを変更する関数は、インライン アセンブリを使用して記述されます。
デバッグ モードでコードをコンパイルすると、意図したとおりに動作します。正の無限大に向かって丸めると、3 分の 1 で 0.3333333333337 が得られますが、リリース モードでコンパイルすると、設定した丸めモードに関係なく同じ結果が得られます。この動作は、LLVM バックエンドが行う最適化によるものだと思います。
この最適化の原因となっている LLVM パスがわかっている場合は、現時点で他の回避策が見当たらないため、それらを無効にすることができます。
performance - 境界チェックを削除すると、コードの実行速度が遅くなるのはなぜですか?
Rust で線形代数ライブラリを作成しています。
特定の行と列で行列セルへの参照を取得する関数があります。この関数は、行と列が境界内にあるという 1 組のアサーションで始まります。
タイトなループでは、境界チェックをスキップする方が速いかもしれないと考えたので、get_unchecked
方法を提供します。
奇妙なことに、これらのメソッドを使用して (行と列の反復子を介して) 行列の乗算を実装すると、境界をチェックすると、実際には約 33% 高速になることがベンチマークで示されます。なぜこうなった?
Linux と OSX を実行している 2 つの異なるコンピューターでこれを試しましたが、どちらも効果を示しています。
完全なコードはgithub にあります。関連ファイルはlib.rsです。対象となる機能は次のとおりです。
get
68行目get_unchecked
81行目next
551行目mul
796行目matrix_mul
(ベンチマーク)1038行目
型レベルの数値を使用して行列をパラメーター化していることに注意してください (ダミーのタグ付き型を介した動的サイズのオプションもあります)。そのため、ベンチマークは 2 つの 100x100 行列を乗算しています。
アップデート:
ベンチマークで直接使用されていないものを削除し、一般的なパラメーターを削除して、コードを大幅に簡素化しました。イテレータを使用しない乗算の実装も作成しましたが、そのバージョンは同じ効果を引き起こしません。このバージョンのコードについては、こちらを参照してください。minimal-performance
ブランチを複製して実行cargo bench
すると、乗算の 2 つの異なる実装のベンチマークが実行されます (そのブランチで開始するためにアサーションがコメント アウトされていることに注意してください)。
get*
また、参照の代わりにデータのコピーを返すように関数を変更すると ( のf64
代わりに)、効果がなくなることにも注意してください&f64
(ただし、コードは全体的にわずかに遅くなります)。
rust - Rustでインラインを使用する必要があるのはいつですか?
Rust には、これら 3 つのフレーバーのいずれかで使用できる「インライン」属性があります。
#[inline]
#[inline(always)]
#[inline(never)]
いつ使用する必要がありますか?
Rust リファレンスでは、インライン属性セクションに次のように書かれています。
コンパイラは、内部ヒューリスティックに基づいて関数を自動的にインライン化します。関数を不適切にインライン化すると、実際にはプログラムが遅くなる可能性があるため、注意して使用する必要があります。
Rust internals フォーラムでは、huon はinline の指定についても保守的でした。
しかし、標準ライブラリを含む Rust ソースでかなりの使用が見られます。多くのインライン属性が 1 行の関数に追加されます。これは、コンパイラーがリファレンスに従ってヒューリスティックを介して簡単に見つけて最適化できるはずです。それらは実際には必要ありませんか?
rust - Rust はいつかオブジェクトの移動中にビットごとのコピーを最適化できますか?
スニペットを検討してください
プログラムの典型的な結果は
住所が違うところ。
どうやら、dummy
コンパイラの move 実装で予想されるように、大きな配列がコピーされています。dummy
残念ながら、非常に大きな配列と同様に、これはパフォーマンスに重大な影響を与える可能性があります。この影響により、関数が実際に引数を概念的に「消費」している場合でも、人々は代わりに参照による引数の受け渡しを選択することを余儀なくされる可能性があります。
Foo
は を導出しないため、Copy
オブジェクトo
が移動されます。Rust は移動されたオブジェクトへのアクセスを禁止しているためbar
、元のオブジェクトの「再利用」を妨げているものは何o
ですか? 根本的な問題はありますか、それともコンパイラがこのビット単位のコピーを最適化する日が来るのでしょうか?
rust - Rustでよりパフォーマンスの高い代替メソッドを持つ整数演算はどれですか?
Rust で何百万回も実行される (ピクセル処理を考えてください) 整数関数を作成する場合、C/C++ と同様に、最高のパフォーマンスで操作を使用すると便利です。
リファレンス マニュアルでは動作の変更について説明していますが、どのメソッドが標準(注 1 を参照)の整数算術演算よりもパフォーマンスが高いかは必ずしも明確ではありません。wrapping_add
Cの追加と同等のものにコンパイルされると思います。
標準演算 (加算 / 減算 / 乗算 / モジュロ / 除算 / シフト / ビット操作など) のうち、デフォルトでは使用されない、より高性能な代替手段を持つ演算はどれですか?
ノート:
- 標準では、 シンボルを使用した整数演算、または...などを意味します。数式を記述するときに使用するもの-オーバーフローをラップまたは返すメソッドのいずれかを使用する特別な必要がない限り。
a + b
i / k
c % e
- この質問に答えるには、調査が必要かもしれません。したがって、結果のアセンブリを見て、どの操作がチェックされていない/プリミティブ操作を使用しているかを確認することで、いくつかのチェックを行うことができてうれしいです。
- チェックされた操作とチェックされていない操作の速度の違いは重要ではない可能性があります。その場合、関数の「高速」バージョンを作成して、「安全な」バージョンと比較できるようにしたいと考えています。与えられた機能にとってそれが合理的な選択であるかどうかについての私自身の結論に。
- ピクセル処理について言及しましたが、SIMD が可能な解決策として登場しました。これは良い提案ですが。SIMD を使用して最適化できないケースがまだ残っているため、高速な整数演算の一般的なケースはまだ考慮すべき事項です。