数日前、私はキャッシュフレンドリーなコードに取り組み、スタックやヒープに変数を配置するとパフォーマンスがどのように変化するかを決定するためにいくつかの異なる構造をロールバックし、反復と検索のように。スタック対キャッシュフレンドリーなアロケータ
私は割当時間を処理していません。処理のパフォーマンスだけです。
テストは正確ではありませんが、少なくともパフォーマンスがどのように異なるかを示す関連数字が記載されています。
まず、std :: arrayのパフォーマンスとベクターのパフォーマンスを比較しました。
アレイのテストコード:
int main()
{
std::array<mango::int16, 5000000> v;
mango::delta_timer timer; //simple timer class
for (int i = 0; 5000000 > i; ++i)
{
v[i] = i; //I know that i will overflow but that's no problem in this case
}
timer.start();
mango::for_each(v.begin(), v.end(), [](mango::int16& i)->void {++i; });
timer.stop();
std::cout << (double)timer.totalTime();
mango::mgetch(); /*crossplatform wrapper for _getch() --> supposed to
give me a point where I can exit the program without printing the results*/
mango::for_each(v.begin(), v.end(), print); /*print the entire
vector and hope that this will prevent the compiler from optimizing the array away*/
return 0;
}
通常のベクトルのためのコード:
int main()
{
std::vector<mango::int16> v;
v.reserve(5000000);
mango::delta_timer timer;
for (int i = 0; 5000000 > i; ++i)
{
v.push_back(i);
}
timer.start();
mango::for_each(v.begin(), v.end(), [](mango::int16& i)->void {++i; });
timer.stop();
std::cout << (double)timer.totalTime();
mango::mgetch();
mango::for_each(v.begin(), v.end(), print);
return 0;
}
for_eachアレイ上には、ベクターに0.003〜0.004秒とfor_eachの間に何かを取りました0.005秒と0.007秒との間であった。
最初のテストの後、私はスタックメモリで同様のパフォーマンスを得ることができるかどうかを試すために、非常にスリムで最小限のアロケータを使いました。
アロケータは、次のようになります。
class block_allocator
{
public:
block_allocator(mango::int32 n, mango::int32 bsize, mango::int32 id)
: m_Memory(new mango::byte[n * bsize]), m_Capacity(n), m_BlockSize(bsize), m_ID(id), m_Blocks(n)
{
for (mango::byte* iterator = (mango::byte*)m_Memory; ((mango::byte*)m_Memory + n * bsize) > iterator; iterator += bsize)
{
m_Blocks.push_back(iterator);
}
}
~block_allocator()
{
delete[](mango::byte*)m_Memory;
m_Memory = nullptr;
}
void* allocate(mango::uint32 n)
{
if (m_Blocks.empty())
{
throw mango::exception::out_of_range(mango::to_string(m_ID) + std::string(" allocator went out of range"), "out_of_range");
}
void* block = m_Blocks.back();
m_Blocks.pop_back();
return block;
}
void deallocate(void* target)
{
if (m_Blocks.size() == m_Capacity)
{
delete[](mango::byte*)target;
}
m_Blocks.push_back(target);
}
private:
void* m_Memory;
mango::int32 m_Capacity;
mango::int32 m_BlockSize;
mango::int32 m_ID;
std::vector<void*> m_Blocks;
};
それはテストのためだけに非常に最小限のサンプルだとそれが生産的使用には適していません!
これはアロケータと私のテストサンプルである:
int main()
{
std::array<mango::int16*, 5000000> v;
mango::delta_timer timer;
for (int i = 0; 5000000 > i; ++i)
{
v[i] = allocate_int(i); //allocates an int with the allocator
}
timer.start();
mango::for_each(v.begin(), v.end(), [](mango::int16* i)->void {++(*i); });
timer.stop();
std::cout << (double)timer.totalTime();
mango::mgetch();
mango::for_each(v.begin(), v.end(), print);
return 0;
}
この例ではfor_eachの性能はちょうど最初の配列例のように0.003と0.004の間に落下しました。
この例ではクリーンアップはありません。
これは質問です。このサンプルを実行するにはビジュアルスタジオ2015のスタックサイズを増やす必要がありました(それ以外の場合はスタックオーバーフローが発生します)。また、サイズが大きくなるにつれてスタックが遅くなるという単純な事実キャッシュフレンドリーなコードのための好ましい方法でしょうか?
ヒープ上でオブジェクトを密接に保つキャッシュフレンドリーなアロケータを使用すると、スタックを使用する場合と同等のパフォーマンスになります(これは異なる例では異なるかもしれませんが、ほとんどのプログラムではスタックのパフォーマンスには十分です)。
適切なアロケータを構築し、大きなものをヒープに格納し、スタックを過度に使用するのではなく、「本当の」割り当ての数を低く保つことは、より効果的ではないでしょうか?インターネット上で頻繁にできるだけ頻繁にスタックを使用することを読んでいるので、このアプローチは多くの人々が考えるように単純ではないことを心配しています。
ありがとうございます。
スタックを使用すると、できるだけベクトルの代わりに配列を使用することが推奨されます。プログラマがヒープを要求したときにスタックにデータを強制することはお勧めできません。プログラマがベクトルを選択した場合、その理由があると仮定します。あなたが観察したタイミングのわずかな違いは、おそらくベクトル内の追加の逆参照から来ている可能性があります。 5mのランでのテストでは、スタックもヒープもキャッシュの親和性が向上しません。 – dasblinkenlight
ポインタの参照解除は、配列whitoutポインタと似たパフォーマンスを持つ最後の例でも同じですが、ポインタが問題ではないと思います。そして、うまくいけば、大規模な配列では、CPUのプリフェッチャは実際には多くを行うことができます。 – Mango
スタックメモリには、プリフェッチャをヒープメモリよりもスタックの方が高速にする「マジック」プロパティはありません。アロケータが適切にアライメントされたアドレスを返すように注意する限り、プリフェッチャに優しいレイアウトになります。コンパイラはそれを処理するので、スタックメモリのアライメントを心配する必要はありません。 – dasblinkenlight