SIMDプログラミングコードベースのメンテナンスコスト


14

質問:

ソフトウェア業界のコンセンサスは、クリーンでシンプルなコードが、コードベースとそれを所有する組織の長期的な実行可能性の基本であるということです。これらのプロパティにより、メンテナンスコストが削減され、コードベースが継続される可能性が高まります。

ただし、SIMDコードは一般的なアプリケーションコードとは異なります。SIMDコードに特に適用されるクリーンでシンプルなコードに関して、同様のコンセンサスがあるかどうかを知りたいと思います。


私の質問の背景。

さまざまな画像処理および分析タスクのために、たくさんのSIMD(単一命令、複数データ)コードを作成します。最近、これらの関数のいくつかを、あるアーキテクチャ(SSE2)から別のアーキテクチャ(ARM NEON)に移植しなければなりませんでした。

このコードはシュリンクラップされたソフトウェア用に記述されているため、MATLABなどの無制限の再配布権がなければ、独自の言語に依存することはできません。

典型的なコード構造の例:

  • 使用のOpenCVのマトリックスタイプMatすべてのメモリのため、緩衝液および寿命管理。
  • 入力引数のサイズ(次元)を確認した後、ピクセルの各行の開始アドレスへのポインターが取得されます。
  • ピクセルカウント、および各入力マトリックスからのピクセルの各行の開始アドレスは、いくつかの低レベルC ++関数に渡されます。
  • これらの低レベルC ++関数は、SIMD組み込み関数(Intel ArchitectureおよびARM NEON用)を使用して、生のポインターアドレスからの読み込みと保存を行います。
  • これらの低レベルC ++関数の特徴:
    • 排他的に1次元(メモリ内で連続)
    • メモリ割り当てを処理しません。
      (一時を含むすべての割り当ては、OpenCV機能を使用する外部コードによって処理されます。)
    • シンボルの名前の長さの範囲(組み込み関数、変数名など)は約10〜20文字で、これは非常に過剰です。
      (テクノバブルのように読みます。)
    • コンパイラは「単一割り当て」コーディングスタイルで記述されていないコードを正しく解析するのに非常にバグがあるため、SIMD変数の再利用は推奨されません
      (私はいくつかのコンパイラのバグレポートを提出しました。)

SIMDプログラミングのどの側面が議論を一般的な場合と異なるものにしますか?または、SIMDが異なるのはなぜですか?

初期開発コストの観点から

  • 優れたパフォーマンスを備えたC ++ SIMDコードの初期開発コストは、カジュアルに記述された C ++コードと比較して、約10倍から100倍(マージンは大きい)であることはよく知られています。
  • パフォーマンスと読み取り可能/クリーナーコードの選択の回答で述べたように、ほとんどのコード(カジュアルに記述されたコードとSIMDコードを含む)は、最初はクリーンでも高速でもありません
  • (スカラーコードとSIMDコードの両方での)コードパフォーマンスの進化的な改善は推奨されません(ソフトウェアの一種と見なされるため)。コストと利点は追跡されません。

傾向の観点から
(例えば、パレート原理、別名80-20ルール

  • 画像処理がソフトウェアシステムの20%(コードサイズと機能の両方)のみで構成されている場合でも、画像処理は(CPU時間の割合として見た場合)比較的遅く、80%以上の時間がかかります。
    • これは、データサイズの影響によるものです。典型的な画像サイズはメガバイト単位で測定されますが、非画像データの典型的なサイズはキロバイト単位で測定されます。
  • 画像処理コード内で、SIMDプログラマーは、C ++コード内のループ構造を識別することにより、ホットスポットを含む20%コードを自動的に認識するように訓練されます。したがって、SIMDプログラマーの観点からは、「重要なコード」の100%がパフォーマンスのボトルネックです。
  • 多くの場合、画像処理システムには複数のホットスポットが存在し、同等の割合の時間を消費します。たとえば、5つのホットスポットがそれぞれ合計時間(20%、18%、16%、14%、12%)を占める場合があります。高いパフォーマンスを実現するには、すべてのホットスポットをSIMDで書き換える必要があります。
    • これは、バルーンをポップするルールとして要約されています。バルーンを2回ポップすることはできません。
    • バルーンがいくつかあると仮定します。たとえば、そのうち5つです。それらを間引く唯一の方法は、それらを1つずつポップすることです。
    • 最初のバルーンがポップされると、残りの4つのバルーンの合計実行時間の割合が高くなります。
    • さらに利益を上げるには、別のバルーンをポップする必要があります。
      (これは最適化の80-20ルールに反します:ぶら下がりが最も少ない果物の20%が選ばれた後、良好な経済的結果を達成できます。)

読みやすさとメンテナンスの面で

  • SIMDコードは、明らかに読みにくいです。

    • これは、すべてのソフトウェアエンジニアリングのベストプラクティス(ネーミング、カプセル化、const-correctness(および副作用の明確化)、関数の分解など)に従っても当てはまります。
    • これは、経験のあるSIMDプログラマーにも当てはまります。
  • 最適なSIMDコードは、同等のC ++プロトタイプコードと比較して、非常にゆがんでいます注意を参照)

    • SIMDコードをゆがめる方法は数多くありますが、10回の試行のうち1回だけで許容可能な高速の結果が得られます。
    • (つまり、高い開発コストを正当化するために、4倍から10倍のパフォーマンスゲインを調整します。実際には、さらに高いゲインが観察されています。)

(注釈)
これは MIT Halideプロジェクトの主要な論文です-論文のタイトルを逐語的に引用します:

「アルゴリズムをスケジュールから分離して、画像処理パイプラインを簡単に最適化する」

順応性の観点から

  • SIMDコードは、単一のアーキテクチャに厳密に結び付けられています。新しいアーキテクチャ(またはSIMDレジスタの拡張)ごとに書き換えが必要です。
  • ソフトウェア開発の大部分とは異なり、SIMDコードの各部分は通常、変更されない単一の目的のために作成されます。
    (他のアーキテクチャへの移植を除きます。)
  • 一部のアーキテクチャは、完全な下位互換性を維持しています(Intel)。些細な量(ARM AArch64、置き換えることにより、いくつかの秋の短いvtblvtblq)が、十分であり、いくつかのコードがコンパイルに失敗する原因になります。

スキルとトレーニングの観点から

  • 新しいプログラマを適切にトレーニングしてSIMDコードを記述および保守するために必要な知識の前提条件は明らかではありません。
  • 学校でSIMDプログラミングを学んだ大卒者は、それを非現実的なキャリアトラックとして軽deし、却下しているようです。
  • 分解読み取りと低レベルのパフォーマンスプロファイリングは、高性能のSIMDコードを記述するための2つの基本的なスキルとして引用されています。しかし、プログラマーにこれら2つのスキルを体系的にトレーニングする方法は不明です。
  • 最新のCPUアーキテクチャ(教科書で教えられているものとは大きく異なる)は、トレーニングをさらに困難にします。

正確性および欠陥関連コストの観点から

  • 単一のSIMD処理関数は実際には十分に凝集性があるため、次の方法で正確性を確立できます。
    • 正式な方法の適用(ペンと紙を使用)、および
    • 出力プロトタイプ範囲の検証(プロトタイプコードを使用し、ランタイム外で実行)
  • ただし、検証プロセスは非常にコストがかかり(コードレビューに100%時間、プロトタイプモデルのチェックに100%時間を費やします)、SIMDコードの既に高価な開発コストを3倍にします。
  • バグが何らかの形でこの検証プロセスをすり抜けた場合、疑わしい欠陥機能を置き換える(書き換える)ことを除いて、「修復」(修正)することはほぼ不可能です。
  • SIMDコードは、C ++コンパイラー(コードジェネレーターの最適化)の欠陥の鈍さに苦しんでいます。
    • C ++ 式テンプレートを使用して生成されたSIMDコードも、コンパイラの欠陥に大きく悩まされます。

破壊的イノベーションの観点から

  • 学界から多くの解決策が提案されていますが、商業的に広く使用されているものはほとんどありません。

    • MITハライド
    • スタンフォードダークルーム
    • NT2(数値テンプレートツールボックス)および関連するBoost.SIMD
  • 商用利用が普及しているライブラリは、SIMDにあまり対応していないようです。

    • オープンソースのライブラリは、SIMDには温かくないようです。
      • 最近、バージョン2.4.9の時点で、多数のOpenCV API関数をプロファイリングした後、これを直接観察しました。
      • 私がプロファイリングした他の多くの画像処理ライブラリも、SIMDを多用しないか、真のホットスポットを見逃しています。
    • 商用ライブラリは、SIMDを完全に回避しているようです。
      • いくつかのケースでは、画像処理ライブラリが以前のバージョンのSIMD最適化コードを後のバージョンの非SI​​MDコードに戻し、深刻なパフォーマンスの低下を引き起こすことさえ見ました。
        (ベンダーの応答は、コンパイラのバグを回避する必要があったということです。)

このプログラマーの質問: 低レイテンシーのコードは時々「ugい」必要がありますか? に関連しており、数年前に私の視点を説明するために以前にその質問への回答を書きました。

ただし、その答えは、「時期尚早な最適化」の観点、つまり次の観点に対する「緩和」です。

  • すべての最適化は定義により時期尚早です(または、性質により短期的です)。
  • 長期的なメリットがある唯一の最適化は、単純化に向けたものです。

しかし、このような視点はこのACM記事で争われています



SIMDコードは一般的なアプリケーションコードとは異なります。SIMDコードのクリーンでシンプルなコードの価値に関して、同様の業界コンセンサスがあるかどうかを知りたいと思います。


2
パフォーマンス要件はありますか?SIMDを使用せずにパフォーマンス要件を満たすことはできますか?そうでない場合、質問は議論の余地がありません。
チャールズE.グラント14

4
これは、質問には長すぎます。おそらく、そのかなりの部分が事実上質問に回答しようとするためであり、回答にも長いためです(部分的には、最も合理的な回答よりもはるかに多くの側面に触れるため)。

3
最適化された代替物に加えて、クリーン/シンプル/スローコード(初期の概念実証と後のドキュメント化目的)の両方が必要です。これにより、理解しやすく(クリーン/シンプル/スローコードを読むことができるため)、検証が容易になります(最適化バージョンとクリーン/シンプル/スローバージョンを手動およびユニットテストで比較することにより)
Brendan

2
@Brendan私は同様のプロジェクトに参加しており、シンプル/遅いコードでテスト手法を使用しました。これは検討に値するオプションですが、制限もあります。まず、パフォーマンスの違いが法外なものになる可能性があります。最適化されていないコードを使用したテストは、数時間から数日間実行できます。第二に、画像処理では、最適化されたコードがわずかに異なる結果を生成する場合、ビットごとの比較が単に機能しないことが判明する可能性があります。そのため、ef root mean square diff
gnat

2
ヘルプセンターで説明されているような概念的なプログラミングの問題ではないため、この質問をトピック外として終了することを投票しています
durron597

回答:


6

私は自分用のSIMDコードをあまり書きませんでしたが、数十年前に多くのアセンブラコードを書きました。SIMD組み込み関数を使用したAFAIKは基本的にアセンブラープログラミングであり、「SIMD」を「アセンブリ」という単語に置き換えるだけで、質問全体を言い換えることができます。例えば、あなたがすでに言及したポイント、例えば

  • コードの開発には、「ハイレベルコード」よりも10倍から100倍かかります。

  • 特定のアーキテクチャに関連付けられています

  • コードは決して「クリーン」でもリファクタリングも簡単ではありません

  • あなたはそれを書いて維持するための専門家が必要

  • デバッグとメンテナンスは難しく、進化は非常に難しい

SIMDに「特別」ではありません-これらの点は、あらゆる種類のアセンブリ言語に当てはまり、それらはすべて「業界のコンセンサス」です。また、ソフトウェア業界の結論も、アセンブラーの場合とほぼ同じです。

  • 必要がない場合は書かないでください-可能な限り高水準言語を使用し、コンパイラーにハードワークを任せてください

  • コンパイラが十分でない場合は、少なくとも一部のライブラリに「低レベル」部分をカプセル化しますが、プログラム全体にコードを広めることは避けてください

  • 「自己文書化」アセンブラーまたはSIMDコードを記述することはほとんど不可能であるため、これを多くの文書でバランスをとるようにしてください。

もちろん、「古典的な」アセンブリまたはマシンコードの状況には実際に違いがあります。今日、現代のコンパイラは通常、高レベル言語から高品質のマシンコードを生成します。現在人気のあるSIMDアーキテクチャの場合、利用可能なコンパイラの品質はそれよりはるかに低いです。そして、自動ベクトル化は依然として科学研究のトピックであるため、おそらく到達しません。たとえば、この記事を参照してくださいコンパイラと人間の最適化の違いを説明するを。優れたSIMDコンパイラを作成するのは非常に難しいかもしれないという考えを与えます。

すでに質問で説明したように、最新のライブラリには品質上の問題もあります。したがって、IMHOが望むことができるのは、今後数年でコンパイラとライブラリの品質が向上し、SIMDハードウェアがより「コンパイラフレンドリー」になるか、ベクトル化を容易にする特殊なプログラミング言語(ハライドなど)あなたは2回言及しました)はより一般的になります(それはすでにFortranの強みではなかったのですか?)それによると、コンパイラが人間の専門家のパフォーマンス(非並列マシンコードの生成)を超えるまで、ほぼ30年(1970年から1990年代の終わりまで)かかりました。そのため、SIMD対応のコンパイラーで同じことが起こるまで、10〜15年以上待つ必要があります。ウィキペディアに、SIMDは約15〜20年前に「大量生産品」になりました(ドキュメントを正しく解釈すると、ハライドは3歳未満です)。これを、成熟するために必要な「古典的な」アセンブリ言語のコンパイラと比較してください。このウィキペディアの記事によると


Wikipediaの記事の私の読書ごとに、一般的であるように思わ業界のコンセンサス低レベルで最適化されたコードは、「原因を忘れてはならない多くの技術的な詳細に、使用することは困難と考えられ」ていること
ブヨ

@gnat:はい、もちろんですが、これを答えに加えれば、OPが長すぎる質問で他の言葉で述べた他の多くのことをすべきだと思います。
Doc Brown 14

同意、あるとしてあなたの答えでの解析は、基準がそれを「オーバーロードする」のリスクを運ぶだろうと付け加え、良い十分に見える
ブヨ

4

私の組織はこの正確な問題に対処しています。私たちの製品はビデオの分野にありますが、私たちが書いたコードの多くは静止画像でも動作する画像処理です。

独自のコンパイラを作成することで、問題を「解決」(または「解決」)しました。これは、最初に聞こえるほどクレイジーではありません。入力の制限されたセットを持っています。すべてのコードが画像、ほとんどがRGBA画像で動作していることを知っています。入力バッファーと出力バッファーが重複しないように、ポインターのエイリアシングがないように、いくつかの制約を設定します。そういうもの。

次に、OpenGL Shading Language(glsl)でコードを記述します。スカラーコード、SSE、SSE2、SSE3、AVX、ネオン、そしてもちろん実際のglslにコンパイルされます。新しいプラットフォームをサポートする必要がある場合、コンパイラを更新してそのプラットフォームのコードを出力します。

キャッシュの一貫性を改善するために画像のタイリングも行います。しかし、画像処理を小さなカーネルに維持し、glsl(ポインターもサポートしない)を使用することで、コードのコンパイルの複雑さを大幅に軽減します。

このアプローチは万人向けではなく、独自の問題があります(たとえば、コンパイラの正確性を確認する必要があります)。しかし、私たちにとってはかなりうまくいきました。


これは🔥🔥ですね!この製品を販売していますか、それともスタンドアロンで提供していますか?(また、 'AVC' = AVXですか?)
アーメドファシィ

申し訳ありませんが、はい、AVXを意味しました(修正します)。現在、コンパイラをスタンドアロン製品として販売していませんが、将来的には発生する可能性があります。
user1118321

冗談はありません、これは本当にすてきに聞こえます。私がこのように見た最も近いものは、CUDAコンパイラーがデバッグ用にCPU上で実行される「シリアル」プログラムを作成する方法でした。これがマルチスレッドおよびSIMD CPUコードを記述する方法に一般化されることを望みましたが、悲しいかな。次に考えることができるのはOpenCLです。OpenCLを評価して、GLSLからallへのコンパイラよりも劣っていますか?
アーメドファシ

1
まあ、OpenCLは私たちが始めたときには存在していなかったと思います。(または、もしそうなら、それはかなり新しいものでした。)それで、それは本当に方程式に入らなかった。
user1118321

0

より高レベルの言語を使用することを検討する場合、メンテナンスのオーバーヘッドをあまり追加しないようです:

Vector<float> values = GetValues();
Vector<float> increment = GetIncrement();

// Perform addition as a vector operation:
List<float> result = (values + increment).ToList();

List<float> values = GetValues();
List<float> increment = GetIncrement();

// Perform addition as a monadic sequence operation:
List<float> result = values.Zip(increment, (v, i) => v + i).ToList();

もちろん、ライブラリの制限に直面する必要がありますが、自分で管理することはありません。メンテナンスコストとパフォーマンスの向上のバランスが取れている場合があります。

http://blogs.msdn.com/b/dotnet/archive/2014/04/07/the-jit-finally-proposed-jit-and-simd-are-getting-married.aspx

http://blogs.msdn.com/b/dotnet/archive/2014/05/13/update-to-simd-support.aspx


私の読書によると、外部ライブラリを使用するオプションはすでに調査され、askerによって対処されています。「広範な商用利用のライブラリは、SIMDに大きく対応していないようです...」
gnat 14

@gnatトップレベルの箇条書きだけでなく、段落全体を実際に読みました。また、ポスターには汎用SIMDライブラリ、コンピュータビジョン、画像処理ライブラリについては言及していません。質問のタイトルにC ++タグやC ++の特異性が反映されていないにもかかわらず、高レベル言語アプリケーションの分析が完全に欠落していることは言うまでもありません。これは、私の質問が主要なものとはみなされないが、価値を追加し、人々に他の選択肢を認識させる可能性が高いと信じるようになります。
デン14

1
私の理解では、OPは商業的に広く使用されているソリューションがあるかどうかを尋ねています。私はあなたのヒントに感謝しますが(ここでプロジェクトにライブラリを使用できるかもしれません)、RyuJITは「広く受け入れられている業界標準」とは言えません。
Doc Brown 14

@DocBrownかもしれませんが、彼の実際の質問はより一般的に定式化されています:「... SIMDコードのクリーンでシンプルなコードの価値に関する業界のコンセンサス...」。(公式の)コンセンサスがまったくないことを疑いますが、C ++がアセンブリを忘れさせて、メンテナンスコストを削減するように、高レベルの言語は「通常の」コードとSIMDコードの違いを減らすことができると主張します。
デン14

-1

過去にアセンブリプログラミングを行ったことがありますが、最近はSIMDプログラミングを行っていません。

IntelのようなSIMD対応コンパイラの使用を検討しましたか?あるインテル(R)C ++コンパイラーでベクトル化へのAガイド興味深いですか?

「バルーンポッピング」などのコメントの中には、コンパイラを使用することを提案するものがあります(ホットスポットが1つもない場合に全体的に利益を得るため)。


私の読書ごとに、このアプローチは、アスカーで試みた、参照はコンパイラのバグの言及/問題の欠陥
ブヨ

OPは彼らがIntelコンパイラを試しかどうかは言わなかったが、これもこのProgrammers.SEトピックの主題です。ほとんどの人は試していません。それは万人向けではありません。ただし、OPのビジネス/質問(コーディング/設計/メンテナンスコストを削減するためのパフォーマンスの向上)に適している場合があります。
ChrisW 14

よく私はそのアスカーは、Intelや他のアーキテクチャ用のコンパイラについての意識であることを示唆している問題で読む:「いくつかのアーキテクチャは、完全な下位互換性を保つ(インテル)を維持、いくつかの秋の短い...」
ブヨ

その文の「Intel」は、Intel-the-compiler-writerではなく、Intel-the-chip-designerを意味します。
ChrisW 14
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.