FFmpegで学ぶアセンブリ言語の魅力
引用元:https://news.ycombinator.com/item?id=43140614
同じテーマの別のリソースがあるよ:
>”https://blogs.gnome.org/rbultje/2017/07/14/writing-x86-simd-…”
手書きのSIMDが重要なケースとそうでないケースがあるけど、FFmpegは特に使われるからわかりやすいね。dav1dのスピードの良さも手書きのSIMDのおかげ。トリリオン回実行されるコードがあって、性能差は手書きとコンパイラ生成で50%も変わることがあるから、その分野では手書きが重要だと思う.
dav1dはアセンブリで書かれてるから、独自の呼び出し規約が使えるんだ。それがメソッドごとに違うから、コンパイラが生成するコードに比べてスタックの保存や読み込みが少ないよ。
時間が重要なコードに関数呼び出しが多いのは何で?インライン化で済ませられそうじゃない?時間が大事でないなら、カスタム呼び出し規約にこだわる必要あるの?
バイナリサイズが気になるから、過剰にインライン化するのは避けた方がいいんだ。手書き最適化したバージョンには、手動最適化がないプラットフォーム用のCのフォールバックがあるから、あらゆる環境で使えるし、checkasmで確認するのに使われるよ。
そうだね、JPEG XLはSIMDコードが数百KBだったけど、やっぱり動画コーデックはもっと大きいだろうな。
関数呼び出しは非常に速いし、インライン化して使い回せるコードが多い方がキャッシュへの負担が減るから、インライン化は常に最良とは限らないよ。
ユースケースが異なるかもしれないけど、前面バウンドになることはあまりなかったから、icacheの心配はなかったかな。
コーデックには同じことをする冗長な方法がたくさんあって、それぞれのデータに対して最適な部分を選ぶから、事前にどれを使うかわからないからインライン化できないんだ。
キャッシュミスは痛いよね。
他のアーキテクチャへのポートを維持するのが難しくなるだけじゃないの?
アセンブリを書くとポータビリティは無理ってことで、Cから呼ぶ高水準なエントリポイントくらいが例外だろうね。複数のターゲットをサポートしたいなら、各アーキテクチャごとに別々のアセンブリモジュールが必要だし、x64の各SIMD世代ごとにさらに分岐させる必要があるかも。
そうだね、こういうプロジェクトでは保守性は性能やスループットに比べたら二の次になることが多い。
確かにamd64のアセンブリコードがWindowsビルドのUnix呼び出し規約を前提にしていたためにバグが出てデータ破損を引き起こしたことがあったから、注意が必要だね。
SIMD命令はすでにアーキテクチャ依存だからね。
私もミッションクリティカル派なんだけど、面白い反論もあるよ。小さな詳細にこだわると、アルゴリズムの最適化が見えにくくなることがあるし、大量のプラットフォームごとのコードを変更するフリクションがローカルミニマムから抜け出すのを妨げることもある。実際、私たちの新しいmatmulは、時々AMXを使ってる有名なライブラリよりも優れているんだ。なんでかっていうと、向こうにはスレッドのボトルネックがあるか、他の理由かもしれないけど、JITが関わってるから判断が難しい。もしプラットフォームごとのカーネルを書いてたらこんなことにはならなかったと思うよ。時間は限られてるからね。
>私は”幅広い探索がレジスタ割り当てや呼び出し規約の微調整に先行することを希望している”って言ったけど、実際は両方ともそれぞれの状況で独立して追求されるべきだと思う。どちらが先に来るべきか、どちらが重要かっていう考え方は単純に間違ってる。
どうしてチューニングがアルゴリズムを考えることと独立してるって言えるの?カーネルのバリアントを書いて最高にチューニングしてから、新しい異なる方法を見つけたら最初の実装を捨てるって、無駄な努力が多すぎるように思える。
Zigにおける組み込みのSIMDサポートって、単純な算術操作のオーバーロードを超えて何を提供してるの?SIMDの90%はその単純操作以外にあるから。Zigは好きだけど、大半の場合CPU固有のビルトインを使う必要があるってのが理解している。GCCやClangはvector_size属性やそれらの「ベクター化」された型に対するオーバーロード算術演算子をサポートしていて、もっと色々あるよ。
ZigはLLVMの内部の一般的なSIMDを出荷していて、これは新しいシステム言語ではかなり一般的だよ。動的シャッフルやmaddubs、aesencみたいなややエキゾチックなものを使いたいなら、特定の命令のLLVMインストリンシックかアセンブリを使う必要がある。
私も「ビルトイン」が何を意味するか気になる。多くの言語は標準ライブラリの一部としてSIMD、ベクター、マトリックス、クォータニオンなどを持っているけど、必ずしも独自のキーワードがあるわけじゃない。C#/.NETやJavaもこの基準で言えばSIMDを持ってる。
JavaのPanama Vectorsはまだ発展途上で、.NETのSIMD抽象化には全然かなわないよ。C/C++/RustからC#へのSIMDアルゴリズムの移植は簡単だけど、Javaだとほぼ不可能なんだ。Cのベテランは、プラットフォーム固有のSIMDコードがまどろっこしいことに気づいてないことが多いと思う。エキゾチックな命令が必要じゃない限り、プラットフォーム特有の道を持つ理由はほとんどないよ。 FFmpegでは必要な命令をすべて必要とし、手動でレジスタ割り当てをすることが多いよ。 その通りだね!FFmpegは極限の最適化を行う場面にあたるから合理的だけど、他のコードパスはそこまでじゃなくて、最近のコンパイラは命令選択がうまくなってきてるよ。たまにおかしな回帰があるのは確かだけど、LLVMやGCC、RyuJITでも問題に見られることがあるよ。 自分は主に一般的なコード(特にパーサーやフォーマッター)を書いてるから、異なるベクター拡張に対して同じアプローチを使うのは現実的じゃないんだ。 その場合でも、異なるアプローチが必要なことが多いよ。例えば8x8バイトブロックの違いでは、SSE2は水平加算(PSADBW)を好むけど、ARM64は垂直(UABAL)を好む。これを一般的なオブジェクトで抽象化すると最適じゃなくなるんだ。 その通りだよ。我々もエンコーダー側で手書きのSIMDをたくさん行っているよ。エンコーダー側ではループの早期排除やロードの排除を行うために、問題を「構造化」する必要があるから、コンパイラではこういった自動ベクタイズされたコードを生成できないことが多いんだ。 以前は重要な関数のSIMDバージョンをたくさん作ってたけど、今はめったにやってないよ。試してみる価値があるのは、コードを孤立させて最も優れたコンパイラエクスプローラーで実行することだね。生成されたコードをじっくり見ると、自動ベクタイズがうまくいってることが多いよ。コンパイラにヒントを与えることで、結構なことができるんだ。最悪の場合、コンパイラが賢くないなら、その生成されたアセンブリを適応する基盤ができるし、手間をかけずに書き始めることができる。 我々も少し似たことをやったよ。意味のある限られた箇所(例えば、GPUドライバーでの画像のアップロードや変換)をCで書いて、コンパイラの注釈を使ってアライメントやポインタのエイリアスを指定して生成したいコードを作ってたんだ。でも他のコンパイラやプラットフォームをサポートするために、実際には生成されたアセンブリをインポートしてビルドしているよ。 逆に、コンパイラがうまく自動ベクタイズできない単純なケースによく遭遇するよ。これはサポートされている単純なベクタープリミティブで、x86-64やARM64の指示で直接サポートされているのに、すべてのコンパイラがうまく処理できていない。これは基本的でシンプルなベクタープリミティブなんだけど、より複雑なもの(例えば丸められた縮小飽和右シフト)を使わせるのは困難なんだ。 やっぱり完璧じゃないよね。なんでサチュレイテッドマATHが標準的なオペレーターにならないのか分からないけど、いろいろやってみて、生成されたコードを手直しするのは俺もやったことあるよ。 問題は、コンパイラーの出力をちゃんと見る必要があるってことだね。細かく調整しないと、自分が書いたのと同じになるまで手間がかかるし、だいたい自分で書いた方が早いこと多いよ。 問題は、コンパイラーの出力が期待通りじゃないかよく確認しないといけないところだよね。新しいコンパイラーやバージョンを使うたびに、その度にやり直しになるし、オートベクタライズがうまくいかなくなることもある。 俺の経験では、オートベクタライズは脆弱な最適化で、いろんな条件でこっそり失敗するから、あんまり頼りたくないね。 生成されたバイナリやアセンブリを保存して、それに頼ることもできるよ。 ARM Macのユーザーなんだけど、全プラットフォームで最適化されたコードを動かすのはどれぐらい大変なの?テストとかフォールバックアルゴリズムが必要だよね?FFmpegが俺のMacで動くのは奇跡だね。手作業でポーティングしたの? FFmpegは手作業ではなく、再実装してるよ。詳しく説明すると、x86では手書きのAVX2実装が含まれて、Armでは手書きのNeon実装が入るって感じ。実行時にFFmpegはCPUがサポートする命令セットを確認して、ディバイスに応じて関数ポインタを変更するんだ。 指示は違うけど、全プラットフォームで基本的な操作(ロードやストア、ブロードキャストなど)の実装は何らかあるはず。それを使って加速したベースライン実装を書くことができるけど、そこから進むと特化したアルゴリズムが必要になってくる。 やあ、ありがとう!アセンブリを書いたりアイデアを分解するのが直感的にできないんだけど、どうやって学ぶかアドバイスある?アセンブリでパフォーマンスを上げるタイミングってどうわかる?関数をアセンブリでの実装にすべきか、コンパイラの出力を利用すべきか、ゼロから始めるべきか、どう思う? 最も頻繁に実行される最小のブロックを探すんだ。例えば、グラフィックスレンダラーでは、ピクセルを描く部分が最も呼ばれるから、命令の順序を調整してサイクルを減らす工夫をしてた。ループの条件チェックはパフォーマンスのボトルネックになったよ。 最近のCPUは条件チェックを予測して動作するよね?たいていの場合、同じ結果になるし。 CPUの最適化技術は大体、高コストだよ。起こりうる問題で、特にブランチ予測は脆弱な最適化だから、過信しないほうがいい。 ブランチ予測は予測可能な条件に対しては有効だけど、予測できない場合はコストがかかる。運が悪い時は、50%以上の誤予測が起きちゃうこともあるから注意が必要。 質問への最良の答えは「もっとアセンブリを書け」ってこと。プログラミング学びたいって人には、何本プログラム書いたか聞くけど、だいたいゼロだよ。1000本書けばまあまあ、10000本でかなり良く、100000本で本当に上手くなれるかも。あと、Turing Completeってゲームもオススメだよ! いい質問だね。アセンブリを学ぶためには、アセンブリで書き直すのが手っ取り早いと思う。どんなことでも、早道はないから、時間をかけるのが一番だね。 可変幅のSIMD命令セットについてどう思う?従来のSIMDと比べて開発のしやすさやパフォーマンスはどう?SIMDの種類が減る方向に向かってるのかな? 可変幅SIMDは同じように書けるけど、ベクターの配列やsizeofベクターには注意が必要。ベクター長依存のものは書きにくいかも。でも、ISAsは増えているけど、プラットフォーム毎に実装するのは避けたいところだね。 FFmpegはWindows上でのアセンブリ関数のためにSEHテーブルをどう生成してるの?x86asm.incが扱ってるの?それとも心配しないことにしてる? 1990年代にx86の最適化コードを書いてたけど、2025年にもまだ手動でやる必要あるのかな?テスト書いてLLMに1万通りのアルゴリズム試させて結果をプロファイルできるんじゃない?でもLLMが最適解見つけるのは難しいのかな?手作業でx86最適化するのは大変だから。レジスタや命令の組み合わせとかいろいろ考えなきゃいけなくて、タイミングや特殊なケースも把握するのは人間には難しいんだよね。 君の質問は「もっと良いコンパイラが作れない?」って言い換えられるね。答えはわからないけど、賢いコンパイラはたくさん努力して作られてるから、可能でも簡単ではないと思う。他にも手書きのアセンブリを超えるのは無理な場合もあると思う。Cプログラムではすべての情報を渡せないし、全体の動作を知ってる開発者が仮定を持てるから、コンパイラにはかなわないこともあるよ。 LLMを使うのが最適解ではないかもしれないけど、今の時代に自動化できるツールがあるのか知りたいね。コンパイラにホットループと一週間を与えて、どんな結果を出すか見てみたいって思うよ。ただ、現代のシステムには許可できない非局所的な相互作用が多くて、理論上最適でも実際には最適じゃないこともあるから注意が必要だね。 「ホットループを与えて一週間待つべきだ」って意見、面白いね。Optunaみたいな最適化ライブラリがあって、目的に対する最適なパラメータを見つけられるんだ。LLVMのすべての最適化ノブを早めに見せれば、特定のコードとテストペイロードに対して最適解が見つかるはずだよ。 君が探しているのはSuperoptimizationだね。 コラボターボはRISC-Vの複雑なHighwayオペレーションをスーパ最適化したことがあって、いい結果が得られたけど、大きめのタスクやアルゴリズムでは困難かもしれないね。 Grok3とClaudeを使ってみたんだけど、意外にアルゴリズムやデータパターンを理解してて驚いた。ただし、出してくる解はしばしばナンセンスなんだよね。 LLMが生成したパターンの出力を検証するのは非常に慎重に行う必要があるね。LLMの生成したものをSATソルバーに通すのは可能かもしれないけど、通常は短いコードシーケンスに限られることが多いよ。 実際にやった人に聞きたいな。アセンブリ言語を学んだり実装したりする楽しみってある?LISPやRISC-Vみたいに。特定のシステムで働くためにCOBOLを学ぶみたいに、他の何かをするために取り組むものなのか気になる。アセンブリに興味があるけど、日常の仕事で必要性がないから、やる価値があるのか考えてるところ。 このチュートリアルの最初の27章をやってみたけど、楽しかった!自分でCから呼び出せる配列ライブラリも作ったし、アセンブリ言語のコーディングが面白いと思った。全てが明確に動くのがいいところだし、リンクについても理解が深まった。x86のFFmpegチュートリアルも見てみたい! めっちゃ面白そうだね!Mario Kart Wiiのサイトにあるのは意外だけど、モッダーやハッカーはアセンブリを扱う必要がある人たちだから納得。 少なくとも一つのアセンブリ言語を学ぶのは良い経験だよ。アセンブリは難しくなく、ただの冗長な表記で、古い8ビットの機械でやるとドキュメントもそろってて環境が整いやすい。Rosetta Codeにはいい例もあるから、初心者にはおすすめ。 アセンブリの面白い点は、思ったより高レベルなとこだよね。例えば、プロセッサのブランチ予測とパイプラインがあるから、制御できるのはほんの一部。大学の授業でアセンブリのパフォーマンスを競ったのが懐かしい! アセンブリを学ぶことは自分にとって非常に大切だった。これまで30年プログラミングしてきて使ったことはないけど、トランジスタから論理ゲート、CPUアーキテクチャ、高級プログラミングまで、全てがどう繋がっているかを理解する瞬間は努力の価値がある。 アセンブリを学ぶのは有用だけど、高級言語でアセンブリ言語の概念を適用する時には注意が必要だよ。それぞれのポインタには意味の違いがあるからね。高級言語のポインタはアセンブリ言語のポインタに変換されるけど、効率的にレジスタを使うための制限があるし。 これで納得したよ。全体像を理解するほど、コンピュータがいかに素晴らしいかを実感できる。ForthやCを経てFPGAをVerilogでプログラミングするまで来たから、アセンブリを学ぶことが最後のギャップを埋めるかも! 約25年アセンブリに深く関わっていて、本当に楽しい。たまに役立つけど、全てのバイトを正確に配置したり、数十年見られていないバイナリを解析したり、不可能だったエミュレーターを作るのにやりがいを感じる。今でも初めて始めた時の魔法を感じる数少ない分野だよ。 アセンブリを学ぶことは本当に価値があるよ。実際にアセンブリを書かなくても、例えばCやC#から生成されたx64やARM64のアセンブリを見ることでパフォーマンスの特性を理解できるし、そこから知識をもとに最適化も可能。まあ、大体のアプリケーションはそこまで最適化は必要ないけど。 一度、SIMDを使ってsqrtの計算を4倍速くしたことがある。めっちゃ楽しかったし、自分で管理できる範囲だった。sqrtライブラリはエッジケースの処理をしていて、コンパイラが自動ベクタ化できない部分を扱ってくれるんだよね。 C++やCでのデバッグにはアセンブリの知識が役立つよね。アセンブリの基本パターンを理解しておくと、困惑せずに済むよ。コンパイラにはデバッグシンボルがあり、最適化レベルも調整できるから、obdumpしても問題なくなるよ。アセンブリの知識を活かして報酬を得る人もいるみたいだね。 プロセッサから最高のパフォーマンスを引き出すにはアセンブリの理解が重要だよ。手動で書く必要はなくなったけど、コンパイラの出力を読んで最適化がどうなっているかを理解することは大きなパフォーマンス向上になるね。 アセンブリを学ぶ価値は間違いなくあるよ。CPUアーキテクチャについての理解が深まるし、その知識はどのプラットフォームにも応用できるからね。 アセンブリの用途は限られてきたけど、特定の問題があるときには役立つこともあるよ。昔は楽しかったけど、今はオペレーティングシステムが進化し、便利になったからプログラミングの役割が変わったね。デバッグは今の方が楽になったよ。 大学で8086アセンブリを学んで、PCスピーカーで”Jingle Bells”を演奏するコンテストで優勝したんだ。それ以来、アセンブリをいじって楽しかったけど、x86の拡張については学んでいないな。マスターズでは8052 CPU用のゲームを作ったりしたよ。アセンブリの理解はある程度できてる。 僕は楽しみでアセンブリをやってるよ。NES/Sega/GBAのコーディングを学んで、いつかゲームを作れたらいいなと。コロナで時間ができたからUdemyのクラスを受けたんだ。アプリ開発をしてるけど、アセンブリは日常的には関係ないね。 アセンブリを学ぶ理由はたくさんあるよ。楽しみだけじゃなくて、デバッグやバイナリセキュリティ、コンパイラなどでも役立つし、特にSIMDアルゴリズムを書く時には必須だよ。 今はRISC-Vを60%ほど学んでて面白いよ。ESP32のコードにアセンブリを埋め込むためだから、一時期ARMアセンブリもやってたけど、ちょっと面倒に感じたね。x64は学ぶ気が起きないな。 ISAによるけど、ARM32はx86-64よりずっと楽しいね。TileGXやBlackfinみたいなVLIWアーキテクチャはパズルみたいで面白いし、ほとんどのISAでベクトル化された処理の厳密なループを実装するのも面白いよ。 アセンブリを使ってパズルを解くゲームがあるから、興味があればやる価値ありそうだよね。ZachtronicsのゲームとかTomorrow Corpのゲームが人気で、めっちゃ面白いよ! 大学でアセンブリの授業を受けたことあるけど、めちゃ楽しかった!今はPythonのマイクロサービスとかで必要とされる技術ではないけど、その授業のおかげで自信がついたよ。 アセンブリは楽しいし、自分の使ってるコンピュータの内部がどうなってるかを理解できるようになるよ。 コンピュータに興味があったり、パフォーマンスを追求したい人には理解する価値があると思う。ただ、最近は非技術職のプログラマーが多くて、アセンブリを学ぶ人は少数派かもね。 アセンブリを書くことにあまり価値は感じないけど、読むことはすごく役に立った。Compiler Explorer(https://godbolt.org/)を使って、最適化されたアセンブリを見て理解することが多いよ。もっとコメントを表示(1)
もっとコメントを表示(2)
もっとコメントを表示(3)