TASImage creation time depends on number of TASImages

Hello everone,
I am using TASImages to display a whole load of greyscale images, which is working quite well. However, I recently noticed, that the creation of my TASImages takes a lot longer than expected.
Maybe I am doing something wrong, or maybe this is a hardware related issue, but according to my data the time it takes to create a single TASImage depends on the number of TASImages I already have in memory.


This plot was generated by running the following code, which creates a lot of TASImages and measures the time used for each iteration:

[code]{
TBenchmark MyBenchmark;
TImagePalette *Colors = new TImagePalette(1, 0);

//Approximately 8GB of RAM, adjust Parameters if you run this on a machine with less RAM
UInt_t dimX = 512, dimY = 512;
UInt_t Iterations = 4000;

Double_t *PixelArray = new Double_t[dimX * dimY];
for (UInt_t idx = 0; idx < dimX*dimY; idx++)
	PixelArray[idx] = idx;

Double_t IterationTimes[Iterations], PlotXIndex[Iterations];
for (UInt_t idx = 0; idx < Iterations; idx++){
	PlotXIndex[idx] = idx;
}

for (UInt_t idx = 0; idx < Iterations; idx++){
	Double_t Zero = MyBenchmark.GetRealTime("Bench");

// Double_t Zero = MyBenchmark.GetCpuTime(“Bench”);
MyBenchmark.Start(“Bench”);
TASImage *img = new TASImage(“Test”, PixelArray, dimX, dimY, Colors);
MyBenchmark.Stop(“Bench”);
IterationTimes[idx] = MyBenchmark.GetRealTime(“Bench”) - Zero;
// delete img;
// IterationTimes[idx] = MyBenchmark.GetCpuTime(“Bench”) - Zero;
}

TCanvas *OutputCanvas = new TCanvas("Time per Iteration", "Time per Iteration", 600, 400);
TGraph *Graph = new TGraph(Iterations, PlotXIndex, IterationTimes);
Graph->SetTitle("Time to create an additional TASImage");
Graph->GetXaxis()->SetTitle("Number of images already created");
Graph->GetYaxis()->SetTitle("Time [s]");
Graph->GetXaxis()->CenterTitle();
Graph->GetYaxis()->CenterTitle();
Graph->Draw("APL");

}[/code]
I ran the example code via the root-interpreter, using root 5.34.02, but my original project was compiled with g++. The Computer used has 24GB RAM, so that shouldn’t be the issue.

root -l .x ~/Code/MinimalExample/MinimalExample.cxx++

While I could use a workaround, where I don’t have to use as many TASImages at the same time, I think this issue is interesting enough to warrant some investigation, because I expected the time to be constant. Are there any explanations or possible solutions to this problem?

Additional Information:
While measuring the CPU time is a more meaningful measurement, the plot is less readable.


Also, if I delete each TASImage before creating the next one, everything behaves as expected and i get constant time.

tl;dr: why does it take longer to create an additional TASImage, if i already have a lot of them in memory?
MinimalExample.cxx (1.32 KB)

Why do you expect the time to be constant? How can the fact, that you already have a lot of images in memory, speedup the creation of new images?

Does TAsImage allocate a memory? Do you know, how your (system) memory allocator works? Does it have any guarantees of a constant time/complexity?

Hi,

I ran the compiled version of your test code and already 500 iterations took about 1GB in RAM with your dimensions. Since you see a rise after around 1000 iterations are you sure you aren’t just benchmarking the performance of your memory (swapping or page faults come to mind)?

I do not get the same behaviour as you. I have 8 GB of memory but only 4 free when I ran the test (I ran a tool checking free memory on the fly). I ran your macro with only 2000 iterations. I see the free memory rapidly decreasing. I think this plot looks compatible with the following figures give the fact, even if not full, the memory can be fragmented:

 800 loops means 512*512*800  = 209 715 200 => 2.0 GB
1000 loops means 512*512*1000 = 262 144 000 => 2.6 GB
1500 loops means 512*512*1500 = 393 216 000 => 3.9 GB 
2000 loops means 512*512*2000 = 524 288 000 => 5.2 GB 


Thank you for your answers. This might very well be an issue of memory management, but it still seems strange to me:
When I use a loop to allocate 8 GB of memory in chunks of 512*512 doubles and perform some actions on that memory in each iteration, every iteration takes the same time.
When I use a loop to allocate 8 GB worth of TASImages, each iterations takes more time than the last one.

I was unconvinced that this is purely a memory issue, so I decided to do some further testing to try and track this down. I found that my problem seems to be connected to the function colorize_asimage_vector from the libAfterImage, which is called by create_asimage_vector which is called byTASImage::SetImage. This function takes longer to execute, the more ASImages I already have in memory.
I don’t know how to access the libAfterImage directly, so I include the source file “libAfterImage/afterimage.h” in my code. Unfortunately this makes my example unable to be run from the root interpreter. I used the following source code for my tests:

Benchmark MyBenchmark;
TImagePalette *Colors = new TImagePalette(1, 0);
UInt_t dimX = 512, dimY = 512;
UInt_t Iterations = 1000;
Double_t *PixelArray = new Double_t[dimX * dimY];
for (UInt_t idx = 0; idx < dimX*dimY; idx++)
	PixelArray[idx] = idx;
Double_t PlotXIndex[Iterations], AllocationTimes[Iterations];
for (UInt_t idx = 0; idx < Iterations; idx++){
	PlotXIndex[idx] = idx;
}

//asPalette creation is the same as in TASImage::SetImage
const TImagePalette &pal = *Colors;
ASVectorPalette asPalette;
asPalette.npoints = pal.fNumPoints;
Int_t col;
for (col = 0; col < 4; col++)
	asPalette.channels[col] = new UShort_t[asPalette.npoints];
memcpy(asPalette.channels[0], pal.fColorBlue,  pal.fNumPoints * sizeof(UShort_t));
memcpy(asPalette.channels[1], pal.fColorGreen, pal.fNumPoints * sizeof(UShort_t));
memcpy(asPalette.channels[2], pal.fColorRed,   pal.fNumPoints * sizeof(UShort_t));
memcpy(asPalette.channels[3], pal.fColorAlpha, pal.fNumPoints * sizeof(UShort_t));
asPalette.points = new Double_t[asPalette.npoints];
for (Int_t point = 0; point < Int_t(asPalette.npoints); point++)
	asPalette.points[point] = 0 + (Iterations - 0) * pal.fPoints[point];

//If I fill my memory with random stuff, the execution time doesn't change:
//Double_t *Memoryblocker = new Double_t[1024*1000000];

ASImage *Images[Iterations];

for (UInt_t idx = 0; idx < Iterations; idx++){
	Double_t Zero = MyBenchmark.GetRealTime("Allocation");
	MyBenchmark.Start("Allocation");
	//Creating Images with a palette executes colorize_asimage_vector:
	Images[idx] = create_asimage_from_vector(0, PixelArray, 512, 512, &asPalette, ASA_ASImage, 0, 0);
	//Creating Images without a palette does not execute colorize_asimage_vector;
	//Images[idx] = create_asimage_from_vector(0, PixelArray, 512, 512, 0, ASA_ASImage, 0, 0);
	MyBenchmark.Stop("Allocation");
	AllocationTimes[idx] = MyBenchmark.GetRealTime("Allocation") - Zero;
}
TCanvas *OutputCanvas = new TCanvas("Time per Iteration", "Time per Iteration", 600, 400);
TGraph *Graph = new TGraph(Iterations, PlotXIndex, AllocationTimes);
Graph->SetTitle("Time to create an additional ASImage with a palette");
Graph->GetXaxis()->SetTitle("Number of images already created");
Graph->GetYaxis()->SetTitle("Time [s]");
Graph->GetXaxis()->CenterTitle();
Graph->GetYaxis()->CenterTitle();
Graph->Draw("APL");
OutputCanvas->Update();

This results in the following execution times:


Up until this point the results are consistent with the ones above, however, if I prevent the function colorize_asimage_vector from executing by passing 0 as my palette (see comments in code), i get constant time for each execution:


The memory usage with or without palette is nearly the same. If I fill up my memory with random junk, the execution times do not change. Therefore I don’t think this is a memory problem.
So i case anyone else thinks the described behavior is strange, it looks like the libAfterImage is the place to search. I would be glad for any explanation you can offer, but I just don’t get why only the function colorize_asimage_vector would execute more slowly when I have a lot of ASImages in memory.

For reference, this is the code of create_asimage_from_vector:

ASImage *
create_asimage_from_vector( ASVisual *asv, double *vector,
							int width, int height,
							ASVectorPalette *palette,
							ASAltImFormats out_format,
							unsigned int compression, int quality )
{
	ASImage *im = NULL;

	if( asv == NULL ) 	asv = &__transform_fake_asv ;

	if( vector != NULL )
	{
		im = create_destination_image( width, height, out_format, compression, ARGB32_DEFAULT_BACK_COLOR);

		if( im != NULL )
		{
			if( set_asimage_vector( im, vector ) )
				if( palette )
					colorize_asimage_vector( asv, im, palette, out_format, quality );
		}
	}
	return im ;
}

This is really interesting and exciting, that you have a constant time working allocator, I’d say, you are lucky.

At my machine (Mac OSX, 12 GB RAM) the time required for memory allocation is not constant and the less memory you have, the more time is spent by allocator.

But anyway, if you can find a bug in asimage, it’s nice.

LibAfterImage is public domain software afterstep.org/afterimage/index.php.
We have it in ROOT.
I tried to get a more recent version from the site. It is the same behaviour.