web-dev-qa-db-ja.com

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

質問:

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

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


質問の背景。

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

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

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

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

SIMDプログラミングのどの側面が、一般的なケースとは異なる議論を引き起こしますか?または、なぜSIMDが異なるのですか?

初期開発コストに関して

  • 優れたパフォーマンスを持つC++ SIMDコードの初期開発コストは、casually-writtenC++コードと比較して約10倍から100倍(マージンが広い)であることはよく知られています。
  • の回答で述べたように、パフォーマンスと読み取り可能/よりクリーンなコードのどちらを選択しますか? 、ほとんどのコード(不用意に書かれたコードとSIMDコードを含む)は、最初はクリーンでも高速でもないです。
  • (スカラーコードとSIMDコードの両方での)コードパフォーマンスの進化的改善は推奨されません(これは一種のsoftware reworkと見なされるため)。コストと利益は追跡されません。

傾向に関して
(例: パレート原理、別名80-20ルール

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

読みやすさとメンテナンスの観点から

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

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

    • SIMDコードを歪める方法はたくさんありますが、そのような試みを10回のうち1回だけ行うと、許容できるほど高速な結果が得られます。
    • (つまり、開発コストが高いことを正当化するために4x-10xのパフォーマンスが向上します。実際にはさらに高いゲインが見られます。)

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

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

前方適用可能性の観点から

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

スキルとトレーニングに関して

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

正確性および欠陥に関連するコストに関して

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

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

  • アカデミアから多くのソリューションが提案されていますが、商業的に広く使用されているソリューションはほとんどありません。

    • MITハライド
    • スタンフォードダークルーム
    • NT2(Numerical Template Toolbox)および関連するBoost.SIMD
  • 商用利用が広まっているライブラリは、SIMDにあまり対応していないようです。

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

このプログラマーの質問: 低レイテンシコードは時々「醜い」でなければならないのですか? は関連していて、私は以前、その質問に対する回答を書いて、数年前の私の見解を説明しました。

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

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

しかし、このような見方は、この ACM記事 で論争されています。


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

14
rwong

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

  • 「高レベルのコード」よりもコードの開発に10倍から100倍かかります

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

  • コードが「クリーン」でもリファクタリングも簡単ではない

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

  • デバッグと保守は難しく、進化は本当に難しい

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

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

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

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

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

あなたの質問ですでに説明したように、現在の最先端のライブラリには品質の問題もあります。だから私たちが期待できる最高のことは、今後数年でコンパイラとライブラリの品質が向上し、SIMDハードウェアがより「コンパイラフレンドリー」になるために変更しなければならないこと、多分簡単なベクトル化をサポートする専用のプログラミング言語(Halideなど)あなたは2度言及しました)より人気があります(それはすでにFortranの強みではありませんでしたか?)。 Wikipedia によると、SIMDは約15〜20年前に "大量生産品"になりました(ドキュメントを正しく解釈すると、Halideは3年未満です)。これを、成熟するために必要な「古典的な」アセンブリ言語のコンパイラと比較してください。 このWikipediaの記事 によると、コンパイラーが(非並列マシンコードの生成において)人間の専門家のパフォーマンスを超えるまで、約30年(〜1970年から1990年代の終わりまで)かかりました。したがって、SIMD対応のコンパイラに同じことが起こるまで、さらに10〜15年待つ必要があるかもしれません。

6
Doc Brown

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

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

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

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

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

4
user1118321

より高水準の言語を使用することを検討しても、メンテナンスのオーバーヘッドがあまり増えないようです。

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

0
Den