I have come across some unexplained memory behavior on Linux in my imaging application. Specifically, it appears that the application holds onto a certain amount of resident memory and will never release it. In our application, it appears that when allocating larger chunks, such as four sessions that each use 3GBs, once all resources are released the baseline may jump 1GB and then stay constant for a while. At some later point, it might jump again. The exact same sequence of operations on Windows demonstrates the expected memory behavior where the baseline remains fixed i.e. memory usage comes back to what it was when the application started.
We have profiled the code extensively to confirm the same number of allocations / deletions, verified in valgrind and that has shown no leaks.
When the image buffer is a simple array of char, allocation and deallocation behave as expected. If the image buffer is a class which contains the array of char, that too works as expected. If I have a container class (DicomImageData) that hold the image buffer class, the problem occurs. It has been isolated and shown below.
First run with an input parameter (WORK_AROUND_LEAK in C++ code below) set to 0. The first run will allocate 2.9GBs of memory and then release it, as viewed by top. The second and all subsequent runs, it will allocate 2.9GBs of memory, but it will not be released.
Work around
I discovered a “work around” by pure luck, but it appears that if I create an intermediate variable to hold the vector of DicomImageData objects, this issue no longer occurs. To verify, run it again with the input parameter set to 1 and do the same sequence. Memory will continually grow to 2.3GB, and then be released. I have tested on various Ubuntu and RHEL implementations and all behave in a consistent manner.
For reference, the dev system is Ubuntu 18.04.4 LTS with 72 cores and 32GB of memory, however running with 8-cores and 16GB exhibits identical behavior, although the 16GB system will thrash much sooner. I’m using gcc 7.5.0.
Any help would be appreciated.
My cpp code and Makefile are as below C++ source code
#define WORK_AROUND_LEAK 0
#include <stdio.h>
#include <iostream>
#include <vector>
#include <string.h>
int totalImages = 2299;
// Class to hold image data
class PixelData {
public:
char * buffer;
size_t size;
PixelData()
: buffer(NULL)
, size(0) {
}
~PixelData() {
if (buffer) {
delete[] buffer;
buffer = NULL;
}
size = 0;
}
};
// Class to hold above defined Pixel data and some other meta-data relevant to the image
class DicomImageData {
public:
PixelData * pixelData;
DicomImageData() {
pixelData = NULL;
}
~DicomImageData() {
if (pixelData) {
delete pixelData;
pixelData = NULL;
}
}
};
using namespace std;
int main() {
#if WORK_AROUND_LEAK
std::vector<DicomImageData *> imageDataArray[1];
#endif
char c = 'c';
int executionCount(0);
size_t pixelDataSize(2 * 1024 * 520);
do {
int numIterations = 1;
executionCount++;
cout << "\nStarting execution test1 " << executionCount << endl;
int iter = 0;
cout << "Starting execution test iterations " << iter << endl;
std::vector<DicomImageData*> imageData;
for (size_t i = 0; i < totalImages; i++) {
char * readPixelData = new char [pixelDataSize + 1 + (i*3)];
memset(readPixelData, '@', pixelDataSize + 1);
DicomImageData * dicomImageData = new DicomImageData();
if (dicomImageData) {
// Create new pixel data struct and set
PixelData * pixelData = new PixelData();
pixelData->buffer = readPixelData;
pixelData->size = pixelDataSize;
dicomImageData->pixelData = pixelData;
imageData.push_back(dicomImageData);
}
#if WORK_AROUND_LEAK
imageDataArray[0] = imageData;
#endif
}
printf("\nPress ENTER to release memory \n");
scanf("%c", &c);
iter = 0;
cout << "Starting release iterations " << iter << endl;
#if WORK_AROUND_LEAK
imageData = imageDataArray[0];
#endif
for (size_t i = 0; i < totalImages; i++) {
PixelData *pixelData = imageData[i]->pixelData;
delete[] pixelData->buffer;
pixelData->buffer = NULL;
delete imageData[i]->pixelData;
imageData[i]->pixelData = NULL;
delete imageData[i];
imageData[i] = NULL;
}
imageData.clear();
printf("Press ENTER to run another test or press X to exit \n");
scanf("%c", &c);
} while (c != 'x' && c != 'X');
}
Makefile if needed
all: memory_leak
memory_leak:
g++ -std=c++11 -O2 -D NDEBUG memory_leak.cpp -o memory_leak
clean:
rm memory_leak