[OpenCV] メモリアクセスの最適化

ランダムアクセスとシーケンシャルアクセス

シーケンシャルアクセス

void BM_ordered(benchmark::State &bm) {
    
    
	for(auto _: bm) {
    
    
#pragma omp parallel for
		for(size_t i = 0; i<n; i++) {
    
    
			benchmark::DoNotOptimize(a[a[i]]);
		}
		benchmark::DoNotOptimize(a[a[i]]);
	}
}

BENCHMARK(BM_ordered);

ランダムアクセス

void BM_random(benchmark::State &bm) {
    
    
	for(auto _ : bm) {
    
    
#pragma omp parallel for
		for( size_t i = 0; i < n; i++) {
    
    
			size_t r = randomize(i) % n;
			benchmark::DoNotOptimize(a[a[i]]);
		}
		benchmark::DoNotOptimize(a[a[i]]);
	}
}

結果:
ここに画像の説明を挿入します

  • ランダム アクセスはシーケンシャル アクセスよりも効率が大幅に低くなります
  • ランダム アクセスはフロートの 1 つにのみアクセスするため、近くの 64 バイトがキャッシュに読み込まれますが、使用されるのは 4 バイトのみで、残りの 60 バイトは使用されないため、無駄が生じます。
  • 連続的かつシーケンシャルなアクセスが理想的ですが、ハッシュ テーブルなどのデータ構造を使用する場合、ハッシュ関数を使用してランダムなアドレスを取得してアクセスする必要があり、Value 型が 64 バイト未満になる可能性があり、帯域幅が無駄になります。

最大ブロック(4096バイト)に従ってランダムアクセスを解決

  • 解決策は、ブロック サイズを 4KB など、より大きくなるように調整することです。つまり、1 つのキャッシュ ラインではなく 64 のキャッシュ ラインになります。
  • このようなランダム アクセスの後には 64 回のシーケンシャル アクセスが続き、これを CPU が検出してキャッシュ ラインのプリフェッチを開始し、データの到着を待つアイドリングの無駄を回避します。
void BM_random_64B(benchmark::State &bm) {
    
    
	for(auto _ : bm) {
    
    
#pragma omp parallel for
		for( size_t i = 0; i < n/16; i++) {
    
    
			size_t r = randomize(i) % (n/16);
			for (size_t j = 0; j < 16; j++) {
    
    
				benchmark::DoNotOptimize(a[a[i]]);
			}
		}
		benchmark::DoNotOptimize(a[a[i]]);
	}
}
void BM_random_4KB(benchmark::State &bm) {
    
    
	for(auto _ : bm) {
    
    
#pragma omp parallel for
		for( size_t i = 0; i < n/1024; i++) {
    
    
			size_t r = randomize(i) % (n/1024);
			for (size_t j = 0; j < 1024; j++) {
    
    
				xxx
			}
		}`
	}
}

ここに画像の説明を挿入します

ページ配置の重要性

  • なぜ 4KB なのか? オペレーティング システムはページングを使用してメモリを管理するため、プログラムのメモリはアドレス空間内でページごとにポストされます。一部の場所はアクセスできないか、割り当てられていない可能性があります。その後、このページを利用できない状態に設定すると、アクセスできます。エラーが発生しましたそしてカーネルモードに入りました。
  • したがって、ハードウェアは安全側にあり、プリフェッチはページ境界を越えることはできません。そうしないと、不要なページ フォールトが開始される可能性があります。したがって、ページ サイズを選択します。ページをまたいで連続してプリフェッチすることはできないため、ページを切り取っても問題ありません。
  • mm_allocを使用して、開始アドレスがページ境界に位置合わせされたメモリのセクションを適用することができるため、各ブロック内でページをまたぐ現象が発生しません。

書き込みが読み取りよりも遅いのはなぜですか?

ここに画像の説明を挿入します

  • 書き込みには読み取りの 2 倍の時間がかかるようです
  • 書くことと読むことを同時に行うと、一人で書くのと同じ時間がかかります。
  • 配列への書き込みは同時に配列の読み取りも行うため、帯域幅が 2 倍になるようです。

書き込みの粒度が小さすぎるため、不必要な読み取りが発生します

  • キャッシュとメモリ通信の最小単位はキャッシュ ライン、つまり 64 バイトです。
  • CPU が 4 バイトを書き込もうとすると、残りの 60 バイトは変更されていないため、キャッシュは CPU がその 60 バイトを次に使用するかどうかを知りません。そのため、メモリから 64 バイト全体を読み取って変更する必要があります。4 バイトは次のとおりです。 CPU によって与えられたデータに基づいて、機会が関連付けのために選択されます。
  • その結果、データは読み取られませんが、実際にはキャッシュがメモリから読み取られるため、帯域幅が 2 倍無駄になります。

おすすめ

転載: blog.csdn.net/qq_30340349/article/details/131316542