簡単な例によるElbrusプラットフォームのコード最適化

「通常、ハッカーは営利目的でプログラムを作成しません

しかし、私自身の喜びのために。 そのようなプログラム

役に立つかもしれませんが、残っているかもしれません

単なる知性のゲームです。」

ヘンリー・S・ウォーレン。 プログラマー向けのアルゴリズムのトリック[1]







今日は、エルブラスに関するメモを続けます。 パスポート認識システムの起動と最適化に関する最初の記事は、 ここにあります







画像







かつて、同僚と私は、最も簡単な最適化手法がElbrusでどのように機能するかに興味を持ちました。







ElbrusプロセッサにはVLIW(Very Long Instruction Word)アーキテクチャがあります。つまり、「広範な」コマンドワードで動作します。 これは、lccコンパイラがコンパイル中にプログラムのソースコードを分析し、コマンド間の依存関係を判断し、広範なコマンドワードを生成することを意味します。 そのような言葉では、同時に実行される最大23のアクションに適合できます。 SIMD(単一命令複数データ)を使用する場合、この数は33以上の操作に増加する可能性があります。 広義のコマンドは並行して実行され、各プロセッサに6つの算術論理デバイスすべてを確実にロードします。 すべての計算機の並列化と読み込みは、最適化コンパイラの肩に完全に依存します。これにより、コマンドの分析と実行のための機器が大幅に簡素化され、Elbrus-4Cの消費電力が最大45 W削減されます[2、3]。







スマートエンジンでは、スマートコンパイラを備えたこのような珍しいプラットフォームでのループの展開など、通常の最適化がどのように機能するのか疑問に思いました。







C ++の簡単な例を調べ、Elbrus-4CとIntel Core i7-6700Kでの作業結果を比較しました。 Elbrusでは、Core i7-Microsoft Visual Studio Community 2015にlccコンパイラバージョン1.20.17を使用しました。lccには、-O3および-ffast-mathコンパイルフラグを使用しました。







はじめに、Elbrus-4CおよびIntel Core i7-6700Kの特徴を以下に示します。







エルブルス-4C Intel Core i7-6700K
建築 エルブルス スカイレイク
周波数、GHz 0.8 4
コアの数 4 4(8 cハイパースレッディング)
技術プロセス 65 nm 14 nm
キャッシュL1サイズ、データ 64 Kb 32 Kb
キャッシュL1サイズ、命令 128 Kb 32 Kb
キャッシュl2サイズ 8 Mb 1 Mb
キャッシュl3サイズ - 8 Mb
RAMタイプ DDR3-1600 DDR4-2133
消費電力 45 91


これらのプロセッサのクロック周波数は大きく異なります。Elbrus-4Cの場合は800 MHz、Intel Core i7の場合は4 GHzです。 また、Elbrusのキャッシュ構造は異なります。L3キャッシュはありませんが、L2キャッシュのサイズは8 Mb(コアあたり2 Mb)であり、レビュー済みのCore i7には1 Mb(コアあたり0.25 Mb)があります。 ElbrusのL1キャッシュ、特に命令キャッシュも大きくなります(128 Kb対32 Kb)。







例1.サイクル展開







この最適化は、サイクルの本体を増やすことで、サイクルの反復回数を減らすことを目的としています(したがって、サイクルを終了するための条件のチェック回数を減らします)。 このような最適化は、ほとんどすべてのプログラムで発生する単純なループに適しています。







次に例を考えます。







#define N 64000 unsigned int x = 0; unsigned int a[N]; for (int i = 0; i < N; ++i) a[i] = i; //  for (int k = 0; k < N;) { x += a[k]; a[k++] = k; }
      
      





最後のサイクルを展開しようとしました。 時間測定の結果(サイクルの10,000回の繰り返し)を表1に示します。Elbrus(コンパイラフラグはmptr32)で32ビットアドレッシングモードが使用されたことに注意してください。 また、測定時間にGHz単位のプロセッサクロック周波数を掛けて、1 GHzでの動作時間を計算しました。 この方法で得られた無次元の値により、クロック周波数の違いを考慮して、ElbrusとCore i7のパフォーマンスを比較できます。







表1. Nに依存する動作時間-展開された反復の数。







エルブルス-4C エルブルス-4C Intel Core i7 Intel Core i7
N 時間、ミリ秒 1 GHz単位の時間 時間、ミリ秒 1 GHz単位の時間
1 401 320 255 1020
2 400 320 275 1100
4 401 320 261 1044
8 401 320 247 988
16 401 320 361 1444
32 452 362 262 1048
64 451 362 262 1048


この例のサイクルの展開は、最新のCore i7とElbrus-4Cの両方で動作時間の増加をもたらさないことがわかります。 検討した非常に単純なサイクルの場合、Elbrus-4Cは、周波数比を考慮して、Core i7よりも効率的に動作します。







例2.さまざまな長さのデータを処理する







この例では、1、4、または8バイトで配列を処理します。 元の配列



は8バイトで整列されました。







 #define MASK1 0xF1 #define MASK4 0xF1F2F3F4 #define MASK8 0xF1F2F3F4F5F6F7F8 for (k = 0; k < n; ++k) { [k] &= MASK1; }
      
      





時間測定の結果を表2に示します。







表2. Nに依存する動作時間-処理されたバイト数。







エルブルス-4C エルブルス-4C Intel Core i7 Intel Core i7
N 時間、ミリ秒 1 GHz単位の時間 時間、ミリ秒 1 GHz単位の時間
1 2400 1920 811 3244
4 600 480 201 804
8 300 240 102 408


最新のCore i7とElbrus-4Cの両方で4バイトと8バイトの処理が高速になり、処理されるバイト数の倍数だけ時間が短縮されることがわかります。 さらに、周波数比を考慮すると、ElbrusはCore i7よりも効率的です。







例3. SIMDの使用







この例では、組み込み関数をテストすることにし、 n = 12800



float



型の数のスカラー積の計算を調べました。

最適化されていないループ:







 float result = 0.0; for (int i = 0; i < n; ++i) { result += x[i] * y[i]; }
      
      





SSEの使用:







 float *pX = x; float *pY = y; __m128 Sum = _mm_setzero_ps(); int num = n / 4; for (int i = 0; i < num; ++i, pX += 4, pY += 4) { Sum = _mm_add_ps(Sum, _mm_mul_ps(_mm_load_ps(pX), _mm_load_ps(pY))); } float result = _mm_extract_ps(Sum, 0) + _mm_extract_ps(Sum, 1) + _mm_extract_ps(Sum, 2) + _mm_extract_ps(Sum, 3);
      
      





EMLの使用[4](Elbrus用に最適化されたライブラリ):







 double result; eml_Vector_DotProd_32F(x, y, n, &result);
      
      





時間測定の結果を表3に示します。Elbrus-4CのSIMDレジスタのサイズは64ビット(命令セットバージョン3)です。これは一般に、最適化なしのバージョンとSIMD付きのバージョン間で観測された2倍の加速に対応します。 Core i7では、すべてがもっともらしいです。128ビットのレジスタで動作し、4つのタイプのfloatに適合しました。 さらに、組み込み関数のないElbrusは、周波数を考慮してCore i7よりも効率的に動作しますが、組み込み関数の場合、動作時間はほぼ同じです。







表3.スカラー積を計算するための作業時間。







エルブルス-4C エルブルス-4C Intel Core i7 Intel Core i7
時間、ミリ秒 1 GHz単位の時間 時間、ミリ秒 1 GHz単位の時間
最適化なし 263 210 99 396
SIMDを使用 110 88 24 96


例4. 2つの配列間のハミング距離のカウント







ここでは、2つの配列のバイナリ表現間のハミング距離を計算しました。 配列内の対応する数値のバイナリ表現間のハミング距離を取り、それらの合計を見つけました。 8ビットデータの配列の場合、ビット単位の排他的ORと計算された距離テーブルを使用しました。







 int result = 0; for (int i = 0; i < n; ++i) { result += popcnt_table[x[i] ^ y[i]]; }
      
      





32ビットおよび64ビットデータの場合、ビット単位の論理排他的ORと、Intelの組み込み関数_mm_popcnt_u32, _mm_popcnt_u64



、およびElbrusの組み込み関数__builtin_e2k_popcnts, __builtin_e2k_popcntd



_mm_popcnt_u32, _mm_popcnt_u64



__builtin_e2k_popcnts, __builtin_e2k_popcntd



ました。 配列xおよびyのバイト単位の全長は変化せず、n = 512に等しくなりました。時間測定の結果を表4に示します。







表4.処理されたバイト数Nに応じたハミング距離の計算時間







エルブルス-4C エルブルス-4C Intel Core i7 Intel Core i7
N 時間、ミリ秒 1 GHz単位の時間 時間、ミリ秒 1 GHz単位の時間
1 630 504 155 620
4 110 88 47 188
8 76 61 15 60


この例では、ElbrusとCore i7の両方で64ビットおよび32ビットレジスタのユニット数をカウントするための組み込み関数が、事前に計算されたテーブルを使用したバージョンに比べて大幅に高速化されることがわかります。 さらに、周波数比を考慮して、Elbrusの32ビットpopcntコマンドはCore i7より高速です。 しかし、64ビットの場合、Core i7とElbrusの作業時間は同じです。







例5.データ依存関係を排除する







この例は、Chris Kasperskyによる「プログラムを最適化するための手法」という本から引用しています。 メモリの効率的な使用」[5]。 データの依存関係の解決が生産性の向上にどのように役立つかを示しています。 配列a



ゼロで埋められます( n = 2097152





データ依存関係の例:







 int x = 0; for (size_t a = 0; a < BLOCK_SIZE; a += 8 * sizeof(int)) { x = *(int *)((char *)p1 + a + 0 * sizeof(int)); a += x; x = *(int *)((char *)p1 + a + 1 * sizeof(int)); a += x; x = *(int *)((char *)p1 + a + 2 * sizeof(int)); a += x; x = *(int *)((char *)p1 + a + 3 * sizeof(int)); a += x; x = *(int *)((char *)p1 + a + 4 * sizeof(int)); a += x; x = *(int *)((char *)p1 + a + 5 * sizeof(int)); a += x; x = *(int *)((char *)p1 + a + 6 * sizeof(int)); a += x; x = *(int *)((char *)p1 + a + 7 * sizeof(int)); a += x; }
      
      





後続の各要素インデックスは、前のコマンドで計算された値に依存するため、メモリからの要素の読み込みは、前の命令の完了後に順次行われます。

これで、コードには依存関係がなくなりました。







 int x = 0; for (size_t a = 0; a < BLOCK_SIZE; a += 8 * sizeof(int)) { x += *(int *)((char *)p2 + a + 0 * sizeof(int)); x += *(int *)((char *)p2 + a + 1 * sizeof(int)); x += *(int *)((char *)p2 + a + 2 * sizeof(int)); x += *(int *)((char *)p2 + a + 3 * sizeof(int)); x += *(int *)((char *)p2 + a + 4 * sizeof(int)); x += *(int *)((char *)p2 + a + 5 * sizeof(int)); x += *(int *)((char *)p2 + a + 6 * sizeof(int)); x += *(int *)((char *)p2 + a + 7 * sizeof(int)); }
      
      





時間測定の結果を表5に示します。データ依存関係の除去は、ElbrusとCore i7の両方で機能し、Core i7の動作時間は約11倍、Elbrusでは約20倍異なります。 データ依存性のあるコードは、1 GHzの観点からCore i7よりもElbrusで動作が遅くなりましたが、中毒がないと、ElbrusはCore i7よりも4倍遅いだけです(周波数差は5倍)。 Elbrusでのこのような結果は、スワップされた要素が直列に配置されている場合に効率的に機能する非同期スワッピング配列(配列プリフェッチバッファー)メカニズムの存在によって説明できます。







表5.依存データおよび独立データの読み取り時間。







エルブルス-4C エルブルス-4C Intel Core i7 Intel Core i7
時間、ミリ秒 1 GHz単位の時間 時間、ミリ秒 1 GHz単位の時間
依存データ 605 484 87 348
独立データ 32 26 8 32


例6.マルチスレッドコンピューティング







もちろん、並列化などの最適化方法を検討せざるを得ませんでした。 実験を純粋にするために、完全に独立したタスク(2つのdouble配列のスカラー積の計算)を行いました。 表6は、N個のタスクのシーケンス時間(T last )、N個のスレッド内のN個のタスクの時間(T ペア )、および加速度Eを示しています。







表6.タスクおよびフローの数Nに応じたスカラー積の逐次および並列計算の時間







エルブルス-4C エルブルス-4C エルブルス-4C Intel Core i7 Intel Core i7 Intel Core i7
N T last 、ms T ペア 、ms E = T last / T ペア T last 、ms T ペア 、ms E = T last / T ペア
2 2628 1316 2.00 1033 500 2.07
4 5259 1318 3.99 1994 500 3.99
8 10513 2634 3.99 3987 503 7.93
16 21045 5268 4.00 7980 1009 7.91
20 26321 6583 4.00 9967 1263 7.89
32 42053 10535 3.99 15948 2014 7.92
40 52566 13170 3.99 19936 2528 7.89


Core i7では、8スレッドで加速がほぼ8倍に達し、さらにわずかに異なることがわかります。 Elbrusでは、4つのストリームで4倍の加速が達成され、ストリームの数が増えても変化しません。 ElbrusとCore i7の速度比は約2.6でしたが、周波数比は5です。







結論







計算を高速化する通常の方法は、Elbrusで非常にうまく機能します。この点で、そのためのプログラミングは特定の知識とスキルを必要としません。 コードの最適化のために検討された基本的な例では、Elbrusは、クロック周波数が800 MHzであり、Core i7と比較して消費電力が半分であることを考えると、非常に優れていることがわかりました。







PSそして、私たちはSPARCでパスポート認識システムも立ち上げました! これは、さらに別のコンピューティングアーキテクチャで認識記事を作成できるようになることを意味します。







更新する 例1の結果にエラーが入り込みました。VLevのコメントのおかげで発見し、修正しました。 結果が更新されました。







使用したソース



[1]ヘンリー・C・ウォーレン・ジュニア プログラマー向けのアルゴリズムトリック。 M .:ウィリアムズ出版社、2014年。ISBN978-5-8459-1838-3-512 C.

[2] http://www.elbrus.ru/arhitektura_elbrus

[3] http://www.mcst.ru/mikroprocessor-elbrus4s

[4] http://www.mcst.ru/vysokoproizvoditelnye_biblioteki

[5]クリス・カスペルスキー。 「テクノロジー最適化プログラム。 メモリの効率的な使用。」 サンクトペテルブルク:BHV-Petersburg、2003。ISBN5-94157-232-8-464S。








All Articles