説明
openmp の parallel for コンストラクトを使用して 4 つ以上のスレッドでランダムなサイズのメモリ チャンクを割り当ておよび割り当て解除すると、プログラムはテスト プログラムの実行時間の後半にかなりの量のメモリ リークを開始するようです。したがって、余分なメモリを実際に使用することなく、消費メモリを 1050 MB から 1500 MB 以上に増やします。
valgrind には問題が見られないため、メモリ リークのように見えるものは、実際にはメモリ フラグメンテーションの強調された影響であると想定する必要があります。
興味深いことに、2 つのスレッドがそれぞれ 10000 の割り当てを行う場合、効果はまだ示されませんが、4 つのスレッドがそれぞれ 5000 の割り当てを行うと、効果が強く示されます。また、割り当てられるチャンクの最大サイズが (1 MB から) 256 KB に減ると、効果は弱くなります。
大量の同時実行によって断片化がそれほど強調される可能性はありますか? それとも、これはヒープのバグである可能性が高いですか?
テストプログラムの説明
デモ プログラムは、合計 256 MB のランダムなサイズのメモリ チャンクをヒープから取得し、5000 回の割り当てを行うようにビルドされています。メモリ制限に達すると、メモリ消費量が制限を下回るまで、最初に割り当てられたチャンクの割り当てが解除されます。5000 回の割り当てが実行されると、すべてのメモリが解放され、ループが終了します。このすべての作業は、openmp によって生成された各スレッドに対して行われます。
このメモリ割り当てスキームにより、スレッドあたり約 260 MB のメモリ消費が予想されます (一部の簿記データを含む)。
デモプログラム
これは実際にテストしたいものなので、ドロップボックスから簡単なメイクファイルを含むサンプル プログラムをダウンロードできます。
プログラムをそのまま実行する場合は、少なくとも 1400 MB の RAM を使用できる必要があります。必要に応じて、コード内の定数を自由に調整してください。
完全を期すために、実際のコードは次のとおりです。
#include <stdlib.h>
#include <stdio.h>
#include <iostream>
#include <vector>
#include <deque>
#include <omp.h>
#include <math.h>
typedef unsigned long long uint64_t;
void runParallelAllocTest()
{
// constants
const int NUM_ALLOCATIONS = 5000; // alloc's per thread
const int NUM_THREADS = 4; // how many threads?
const int NUM_ITERS = NUM_THREADS;// how many overall repetions
const bool USE_NEW = true; // use new or malloc? , seems to make no difference (as it should)
const bool DEBUG_ALLOCS = false; // debug output
// pre store allocation sizes
const int NUM_PRE_ALLOCS = 20000;
const uint64_t MEM_LIMIT = (1024 * 1024) * 256; // x MB per process
const size_t MAX_CHUNK_SIZE = 1024 * 1024 * 1;
srand(1);
std::vector<size_t> allocations;
allocations.resize(NUM_PRE_ALLOCS);
for (int i = 0; i < NUM_PRE_ALLOCS; i++) {
allocations[i] = rand() % MAX_CHUNK_SIZE; // use up to x MB chunks
}
#pragma omp parallel num_threads(NUM_THREADS)
#pragma omp for
for (int i = 0; i < NUM_ITERS; ++i) {
uint64_t long totalAllocBytes = 0;
uint64_t currAllocBytes = 0;
std::deque< std::pair<char*, uint64_t> > pointers;
const int myId = omp_get_thread_num();
for (int j = 0; j < NUM_ALLOCATIONS; ++j) {
// new allocation
const size_t allocSize = allocations[(myId * 100 + j) % NUM_PRE_ALLOCS ];
char* pnt = NULL;
if (USE_NEW) {
pnt = new char[allocSize];
} else {
pnt = (char*) malloc(allocSize);
}
pointers.push_back(std::make_pair(pnt, allocSize));
totalAllocBytes += allocSize;
currAllocBytes += allocSize;
// fill with values to add "delay"
for (int fill = 0; fill < (int) allocSize; ++fill) {
pnt[fill] = (char)(j % 255);
}
if (DEBUG_ALLOCS) {
std::cout << "Id " << myId << " New alloc " << pointers.size() << ", bytes:" << allocSize << " at " << (uint64_t) pnt << "\n";
}
// free all or just a bit
if (((j % 5) == 0) || (j == (NUM_ALLOCATIONS - 1))) {
int frees = 0;
// keep this much allocated
// last check, free all
uint64_t memLimit = MEM_LIMIT;
if (j == NUM_ALLOCATIONS - 1) {
std::cout << "Id " << myId << " about to release all memory: " << (currAllocBytes / (double)(1024 * 1024)) << " MB" << std::endl;
memLimit = 0;
}
//MEM_LIMIT = 0; // DEBUG
while (pointers.size() > 0 && (currAllocBytes > memLimit)) {
// free one of the first entries to allow previously obtained resources to 'live' longer
currAllocBytes -= pointers.front().second;
char* pnt = pointers.front().first;
// free memory
if (USE_NEW) {
delete[] pnt;
} else {
free(pnt);
}
// update array
pointers.pop_front();
if (DEBUG_ALLOCS) {
std::cout << "Id " << myId << " Free'd " << pointers.size() << " at " << (uint64_t) pnt << "\n";
}
frees++;
}
if (DEBUG_ALLOCS) {
std::cout << "Frees " << frees << ", " << currAllocBytes << "/" << MEM_LIMIT << ", " << totalAllocBytes << "\n";
}
}
} // for each allocation
if (currAllocBytes != 0) {
std::cerr << "Not all free'd!\n";
}
std::cout << "Id " << myId << " done, total alloc'ed " << ((double) totalAllocBytes / (double)(1024 * 1024)) << "MB \n";
} // for each iteration
exit(1);
}
int main(int argc, char** argv)
{
runParallelAllocTest();
return 0;
}
テストシステム
これまでのところ、ハードウェアは非常に重要です。より高速なマシンで実行する場合、テストを調整する必要がある場合があります。
Intel(R) Core(TM)2 Duo CPU T7300 @ 2.00GHz
Ubuntu 10.04 LTS 64 bit
gcc 4.3, 4.4, 4.6
3988.62 Bogomips
テスト
makefile を実行すると、 という名前のファイルが得られるはずですompmemtest
。経時的なメモリ使用量を照会するために、次のコマンドを使用しました。
./ompmemtest &
top -b | grep ompmemtest
これにより、非常に印象的なフラグメンテーションまたはリーク動作が発生します。4 つのスレッドで予想されるメモリ消費量は1090 MB で、時間の経過とともに1500 MB になりました。
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
11626 byron 20 0 204m 99m 1000 R 27 2.5 0:00.81 ompmemtest
11626 byron 20 0 992m 832m 1004 R 195 21.0 0:06.69 ompmemtest
11626 byron 20 0 1118m 1.0g 1004 R 189 26.1 0:12.40 ompmemtest
11626 byron 20 0 1218m 1.0g 1004 R 190 27.1 0:18.13 ompmemtest
11626 byron 20 0 1282m 1.1g 1004 R 195 29.6 0:24.06 ompmemtest
11626 byron 20 0 1471m 1.3g 1004 R 195 33.5 0:29.96 ompmemtest
11626 byron 20 0 1469m 1.3g 1004 R 194 33.5 0:35.85 ompmemtest
11626 byron 20 0 1469m 1.3g 1004 R 195 33.6 0:41.75 ompmemtest
11626 byron 20 0 1636m 1.5g 1004 R 194 37.8 0:47.62 ompmemtest
11626 byron 20 0 1660m 1.5g 1004 R 195 38.0 0:53.54 ompmemtest
11626 byron 20 0 1669m 1.5g 1004 R 195 38.2 0:59.45 ompmemtest
11626 byron 20 0 1664m 1.5g 1004 R 194 38.1 1:05.32 ompmemtest
11626 byron 20 0 1724m 1.5g 1004 R 195 40.0 1:11.21 ompmemtest
11626 byron 20 0 1724m 1.6g 1140 S 193 40.1 1:17.07 ompmemtest
注意: gcc 4.3、4.4、および 4.6(trunk) でコンパイルすると、この問題を再現できます。