回答:
それはかなり直感的です:
CPUが高速である場合、プログラムが高速になると、プログラムはCPUにバインドされます。πの新しい数字を計算するプログラムは、通常、CPUに依存し、数値を処理するだけです。
I / Oサブシステムが高速である場合、プログラムは高速になるとI / Oバウンドになります。正確なI / Oシステムの意味はさまざまです。私は通常それをディスクに関連付けますが、もちろんネットワークや通信一般も一般的です。ボトルネックとなるのはディスクからのデータの読み取りであるため、巨大なファイルを調べて一部のデータを探すプログラムは、I / Oバウンドになる可能性があります(実際、この例は、最近数百MB /秒の昔ながらの方法のようです) SSDから入ってくる)。
CPUバウンドは、プロセスの進行速度がCPUの速度によって制限されることを意味します。小さな行列の乗算など、小さな数のセットに対して計算を実行するタスクは、CPUにバインドされる可能性があります。
I / Oバウンドとは、プロセスの進行速度がI / Oサブシステムの速度によって制限されることを意味します。たとえば、ファイルの行数をカウントするなど、ディスクからのデータを処理するタスクは、I / Oバウンドである可能性があります。
メモリバウンドとは、プロセスの進行速度が、使用可能なメモリの量とそのメモリアクセスの速度によって制限されることを意味します。大量の行列を乗算するなど、大量のメモリ内データを処理するタスクは、メモリバインドになる可能性があります。
キャッシュバウンドとは、プロセスの進行速度が、使用可能なキャッシュの量と速度によって制限されることを意味します。キャッシュに収まるよりも多くのデータを単純に処理するタスクは、キャッシュバインドされます。
I / Oバウンドは、メモリバウンドがキャッシュバウンドよりも遅く、CPUバウンドよりも遅くなります。
I / Oバウンドになるための解決策は、必ずしもより多くのメモリを取得することではありません。状況によっては、アクセスアルゴリズムは、I / O、メモリ、またはキャッシュの制限に基づいて設計できます。Cache Oblivious Algorithmsを参照してください。
マルチスレッド
この回答では、CPUとIOの制限付き作業を区別する1つの重要なユースケース、つまりマルチスレッドコードを記述する場合について調べます。
RAM I / Oバウンドの例:ベクトル合計
単一のベクトルのすべての値を合計するプログラムを考えます。
#define SIZE 1000000000
unsigned int is[SIZE];
unsigned int sum = 0;
size_t i = 0;
for (i = 0; i < SIZE; i++)
/* Each one of those requires a RAM access! */
sum += is[i]
コアごとにアレイを均等に分割して並列化することは、一般的な最近のデスクトップでは制限された有用性があります。
たとえば、Ubuntu 19.04では、CPUを搭載したLenovo ThinkPad P51ラップトップ:Intel Core i7-7820HQ CPU(4コア/ 8スレッド)、RAM:2x Samsung M471A2K43BB1-CRC(2x 16GiB)次のような結果が得られます。
ただし、実行の間には多くの差異があることに注意してください。しかし、私はすでに8GiBにいるため、配列サイズをこれ以上増やすことはできません。また、今日の複数の実行にわたる統計の気分ではありません。しかし、これは多くの手動実行を行った後の典型的な実行のように見えました。
ベンチマークコード:
曲線の形を完全に説明するのに十分なコンピューターアーキテクチャがわかりませんが、1つだけ明確です。8つのスレッドをすべて使用しているため、計算が単純に期待される8倍速くなりません。何らかの理由で、2スレッドと3スレッドが最適でしたが、スレッドを追加すると処理が非常に遅くなります。
これを実際に8倍速くなるCPUバウンド作業と比較してください。time(1)の出力で「real」、「user」、「sys」は何を意味しますか?
これは、すべてのプロセッサがRAMにリンクする単一のメモリバスを共有している理由です。
CPU 1 --\ Bus +-----+
CPU 2 ---\__________| RAM |
... ---/ +-----+
CPU N --/
そのため、CPUではなくメモリバスがすぐにボトルネックになります。
これは、2016年のハードウェアでは2つの数値を加算すると1つのCPUサイクルがかかり、メモリの読み取りには約100 CPUサイクルかかるためです。
したがって、入力データのバイトごとに実行されるCPU作業は小さすぎるため、これをIOバウンドプロセスと呼びます。
その計算をさらに高速化する唯一の方法は、マルチチャネルメモリなどの新しいメモリハードウェアで個々のメモリアクセスを高速化することです。
たとえば、より高速なCPUクロックにアップグレードしても、あまり役に立ちません。
その他の例
行列の乗算は、RAMとGPUのCPUに依存します。入力には以下が含まれます。
2 * N**2
数字ですが:
N ** 3
乗算が行われます。これは、並列化が実用的な大きなNの価値があるには十分です
これが、次のような並列CPU行列乗算ライブラリが存在する理由です。
キャッシュの使用は、実装の速度に大きな違いをもたらします。たとえば、この教訓的なGPU比較の例を参照してください。
以下も参照してください。
ネットワーキングは、典型的なIOバインドの例です。
1バイトのデータを送信する場合でも、送信先に到達するまでに長い時間がかかります。
HTTPリクエストのような小さなネットワークリクエストを並列化すると、パフォーマンスが大幅に向上します。
ネットワークがすでにフルキャパシティにある場合(例:トレントのダウンロード)、並列化によりレイテンシが改善されます(例:「同時に」Webページをロードできる)。
1つの数値を受け取り、それを大量に処理するダミーのC ++ CPUバインド操作:
次の実験に基づくと、ソートはCPUのようです。C++ 17並列アルゴリズムはすでに実装されていますか?並列ソートのパフォーマンスが4倍向上しましたが、より理論的な確認も必要です
CPUバインドかIOバインドかを確認する方法
非RAM IOは、ディスク、ネットワークのようにバインド:ps aux
、その後、theck場合CPU% / 100 < n threads
。はいの場合、IOバウンドです。たとえば、ブロッキングread
sはデータを待っているだけで、スケジューラはそのプロセスをスキップしています。次に、などのツールを使用してsudo iotop
、どのIOが問題であるかを正確に判断します。
実行は迅速で、あなたがスレッドの数をパラメータ場合は、あなたがから簡単にそれを見ることができtime
、パフォーマンスCPUバウンドの作業のためのスレッド数の増加に応じて向上:「本当の」、「ユーザー」と「SYS」平均で何time(1)の出力?
RAM-IOバウンド:RAM待機時間はCPU%
測定に含まれるため、わかりにくいです。以下も参照してください。
いくつかのオプション:
GPU
通常のCPU読み取り可能RAMからGPUに入力データを最初に転送するとき、GPUにはIOボトルネックがあります。
したがって、GPUはCPUにバインドされたアプリケーションのCPUよりも優れているだけです。
ただし、データがGPUに転送されると、GPUは次の理由により、CPUよりも高速にこれらのバイトを操作できます。
ほとんどのCPUシステムよりもデータのローカリゼーションが多いため、一部のコアでは他のコアよりも高速にデータにアクセスできます
すぐに操作する準備ができていないデータをスキップするだけで、データの並列処理を利用し、レイテンシを犠牲にします。
GPUは大規模な並列入力データを操作する必要があるため、現在のデータが利用可能になるのを待つのではなく、CPUが主に行うような他のすべての操作をブロックするのではなく、利用可能な次のデータにスキップすることをお勧めします
したがって、アプリケーションが次の場合、GPUはCPUよりも高速になります。
これらのデザインの選択は、もともと3Dレンダリングのアプリケーションを対象としていました。その主な手順は、「OpenGLのシェーダーとは何ですか?
したがって、これらのアプリケーションはCPUに依存していると結論付けます。
プログラム可能なGPGPUの出現により、CPUバウンド操作の例として機能するいくつかのGPGPUアプリケーションを観察できます。
ブラーフィルターなどのローカル画像処理操作は、本質的に高度に並列化されています。
1秒あたり60回のポイントデータからヒートマップを作成することは可能ですか?
プロットされた関数が十分に複雑である場合のヒートマップグラフのプロット。
https://www.youtube.com/watch?v=fE0P6H8eK4I Fluid Dynamics:CPU vs GPU」byJesúsMartínBerlanga
流体力学のナビエ・ストークス方程式などの偏微分方程式を解く:
以下も参照してください。
CPythonグローバルインタープリターロック(GIL)
簡単なケーススタディとして、Pythonグローバルインタープリターロック(GIL)を指摘したいと思います。CPythonのグローバルインタープリターロック(GIL)とは何ですか?
このCPython実装の詳細により、複数のPythonスレッドがCPUにバインドされた作業を効率的に使用できなくなります。CPythonのドキュメント言います:
CPython実装の詳細:CPythonでは、グローバルインタープリターロックにより、一度に1つのスレッドのみがPythonコードを実行できます(特定のパフォーマンス指向のライブラリーがこの制限を克服する場合があります)。アプリケーションでマルチコアマシンの計算リソースをより有効に利用したい場合は、
multiprocessing
またはを使用することをお勧めしますconcurrent.futures.ProcessPoolExecutor
。ただし、複数のI / Oバウンドタスクを同時に実行する場合は、スレッド化が適切なモデルです。
したがって、ここでは、CPUバウンドコンテンツが適切でなく、I / Oバウンドが適している例を示します。
プログラムがI / O(ディスクの読み取り/書き込みまたはネットワークの読み取り/書き込みなど)を待機している場合、プログラムが停止していても、CPUは他のタスクを自由に実行できます。プログラムの速度は、主にそのIOが発生する速度に依存します。高速化したい場合は、I / Oを高速化する必要があります。
プログラムが多くのプログラム命令を実行していて、I / Oを待機していない場合、CPUバウンドと呼ばれます。CPUを高速化すると、プログラムの実行が速くなります。
どちらの場合も、プログラムを高速化する鍵はハードウェアを高速化することではなく、プログラムを最適化して必要なIOまたはCPUの量を減らすか、CPUを集中的に使用しながらI / Oを実行させることです。もの。
I / Oバウンドとは、計算の完了にかかる時間が主に入出力操作が完了するのを待つために費やされた期間によって決定される状態を指します。
これは、CPUバウンドであるタスクの反対です。この状況は、データが要求される速度がデータが消費される速度よりも遅い場合、つまり、データを処理するよりも多くの時間がデータの要求に費やされる場合に発生します。
非同期プログラミングの中核は、非同期操作をモデル化するTaskオブジェクトとTaskオブジェクトです。これらは、asyncおよびawaitキーワードでサポートされています。ほとんどの場合、モデルはかなり単純です。
I / Oバウンドコードの場合、非同期メソッド内でタスクまたはタスクを返す操作を待ちます。
CPUバインドコードの場合、Task.Runメソッドを使用してバックグラウンドスレッドで開始される操作を待ちます。
awaitキーワードは、魔法が発生する場所です。待機を実行したメソッドの呼び出し元に制御を渡し、最終的にUIの応答性やサービスの弾力性を可能にします。
I / Oバインドの例:Webサービスからのデータのダウンロード
private readonly HttpClient _httpClient = new HttpClient();
downloadButton.Clicked += async (o, e) =>
{
// This line will yield control to the UI as the request
// from the web service is happening.
//
// The UI thread is now free to perform other work.
var stringData = await _httpClient.GetStringAsync(URL);
DoSomethingWithData(stringData);
};
private DamageResult CalculateDamageDone()
{
// Code omitted:
//
// Does an expensive calculation and returns
// the result of that calculation.
}
calculateButton.Clicked += async (o, e) =>
{
// This line will yield control to the UI while CalculateDamageDone()
// performs its work. The UI thread is free to perform other work.
var damageResult = await Task.Run(() => CalculateDamageDone());
DisplayDamage(damageResult);
};
上記の例では、非同期を使用して、I / OバウンドおよびCPUバウンドの作業を待機する方法を示しました。コードのパフォーマンスに大きな影響を与え、特定の構成要素の誤用につながる可能性があるため、実行する必要があるジョブがI / OバウンドまたはCPUバウンドである場合を識別することができるのは重要です。
コードを記述する前に、次の2つの質問をする必要があります。
コードは、データベースからのデータなどの何かを「待機」しますか?
- 答えが「はい」の場合、作業はI / Oバウンドです。
あなたのコードは非常に高価な計算を実行しますか?
- 「はい」と答えた場合、あなたの仕事はCPUに依存しています。
現在の作業がI / Oバウンドである場合は、非同期を使用し、Task.Run なしで 待機します。Task Parallel Libraryは使用しないでください。この理由は、Async in Depthの記事で説明されています。
使用している作業がCPUバウンドであり、応答性を重視する場合は、asyncを使用して待機しますが、Task.Runを使用して別のスレッドで作業を開始します。作業が並行性と並列処理に適している場合は、Task Parallel Libraryの使用も検討する必要があります。
実行中の算術/論理/浮動小数点(A / L / FP)のパフォーマンスがプロセッサの理論上のピークパフォーマンス(製造元が提供し、プロセッサ:コアの数、周波数、レジスタ、ALU、FPUなど)。
不可能とは言わないまでも、実際のアプリケーションでピークパフォーマンスを達成することは非常に困難です。ほとんどのアプリケーションは実行のさまざまな部分でメモリにアクセスし、プロセッサは数サイクルの間A / L / FP操作を実行していません。これはフォンノイマン制限と呼ばれています、メモリとプロセッサの間に存在する距離のため、ます。
CPUのピークパフォーマンスに近づけたい場合は、メインメモリからのデータを必要としないようにするために、キャッシュメモリ内のほとんどのデータを再利用する方法が考えられます。この機能を利用するアルゴリズムは、行列と行列の乗算です(両方の行列をキャッシュメモリに格納できる場合)。これは、行列がサイズの場合、FP数のデータのみを使用n x n
して2 n^3
演算を実行する必要があるために発生し2 n^2
ます。一方、たとえば行列の加算n^2
は、同じデータを持つFLOP のみを必要とするため、行列の乗算よりもCPUに依存しない、またはメモリに依存するアプリケーションです。
次の図では、Intel i5-9300Hでの行列の加算と行列の乗算の単純なアルゴリズムで取得したFLOPを示しています。
予想通り、行列の乗算のパフォーマンスは行列の加算よりも大きいことに注意してください。これらの結果は、実行することで再現できるtest/gemm
とtest/matadd
、この中で利用できるリポジトリ。
この効果についてJ. Dongarraが提供したビデオもご覧になることをお勧めします。
I / Oバウンドプロセス:-プロセスの存続期間の大部分がI / O状態で費やされている場合、そのプロセスはAI / Oバウンドプロセスです。例:-計算機、インターネットエクスプローラー
CPUバウンドプロセス:-プロセスライフの大部分がCPUで費やされている場合、CPUバウンドプロセスです。