Skip to main content
gamedev.net
🔒 Locked

Optimisation advice

Started by LucasMeijer 6 replies 1.6k views
Original Post
LucasMeijer
LucasMeijer
Hi. I've written the code below that takes as input a RGBA image, multiplied on a black background, and the same RGBA image, multiplied on a white background. From these two images, I recreate the original RGBA image. It works like a charm, but it is slow. I'm hoping to get some optimisation tips for the code below:

unsigned char * image1Ptr = NULL, * image2Ptr = NULL, * image3Ptr = NULL;

// The calls below are into a specific API I'm working with. Nothing magic.
// They just fill the imageXPtr's with pointers to 720x576x4bytesperpixel images
// in memory.

// image1 = RGBA on white
// image2 = RGBA on black
// image3 = output image where the result should be written in.

ThrowErr(pObj->pMmImage->LockPixels(&pCall->pArgs[1],(PMoaVoid*) &image1Ptr));
ThrowErr(pObj->pMmImage->LockPixels(&pCall->pArgs[2],(PMoaVoid*) &image2Ptr));
ThrowErr(pObj->pMmImage->LockPixels(&pCall->pArgs[3],(PMoaVoid*) &image3Ptr));
	
for (int y=0;y<myImageInfo1.iHeight;y++)
{	
  for (int x=0;x<myImageInfo1.iWidth;x++)
    {
      unsigned char& tRedOnBlack  = image2Ptr[2];
      unsigned char& tRedOnWhite  = image1Ptr[2];
      unsigned char tBlend = 255 - (tRedOnWhite - tRedOnBlack);
      
      if (tBlend != 0)
      {
	const unsigned char& tBlueOnBlack  = image2Ptr[0];
	const unsigned char& tGreenOnBlack = image2Ptr[1];
	const unsigned int& tInverseBlend = pObj->m_lut[tBlend];
				
	image3Ptr[0] = (tBlueOnBlack * tInverseBlend) >> 8;
	image3Ptr[1] = (tGreenOnBlack * tInverseBlend) >> 8;
	image3Ptr[2] = (tRedOnBlack * tInverseBlend) >> 8;
      }
					
      image3Ptr[3] = tBlend;
      image1Ptr+=4;
      image2Ptr+=4;
      image3Ptr+=4;
}
Where, pObj->m_lut is a pregenerated lookuptable, made like this:

unigned int lut[512]
for (int i=1;i<512;i++)	m_lut = 65535.f / i;
Running this code on 720x576 images takes between 15 and 22 milliseconds in releasemode, on my p4 2.8ghz. I'd love to hear suggestion from folks that think it could be written in a faster way. Bye, Lucas
doynax
doynax
On the CPU this seems like the job for the vector unit. Eighter through hand written assembly code, intrinsics or an advanced compiler. You'll probably have to eliminate that blending table and calculate those values on the fly instead though.
Another option is to let the GPU handle the whole operation though a pixel shader.
lordarkam
lordarkam
Try with assembly. I think that it´s the best solution. Good luck!
S1CA
S1CA
1) as mentioned, a GPU is designed for work like this, you should be using that unless there's a very good reason. The blend you're doing should even be possible on graphics chips without shader support.


2) if at all possible, you should run your code through a profiler - optimisation without any profiling data is guesswork, and usually half of your guesses will be wrong. Ideally use something like VTune which can give you data on your cache use.


3) obvious stuff: only profile Release builds with release/retail versions of all libraries you're using, remove any frame rate limiters (including vsync waits), and ensure the way you display the frame rate/time isn't actually costing you performance (e.g. a GetDC() on a DirectDraw surface performs a Lock() internally and so kills parallelism and stalls everything).


4) accessing physical memory is one of, if not the single most expensive things you can do on a modern CPU - that's why modern CPUs have caches; if the memory you're accessing is in the cache (a cache hit), then performance is good, if the memory you're accessing isn't in the cache, then the CPU needs to go and get some main memory to fill the cache line you're accessing... The patterns you access memory in make a huge difference to cache performance.

A good profiler will tell you when you're getting awful cache performance (i.e. those cases where you should investigate access patterns and cache prefetching).


5) where are the images located/how were they allocated? - if they're D3D/OpenGL textures or DirectDraw surfaces [or similar], then if they're in AGP or video memory, *reads* from that memory won't use the cache and so will be *sloooow* - writes are better, but still not as fast as to system memory. If at all possible, put all images/surfaces you'll be accessing with the CPU in plain system memory.


6) using assembly language doesn't automatically make your code any quicker; well written C++ code compiled with a good optimising compiler can be many times faster than naively written assembly code (assuming none of the sins in #3 above are comitted). Hand tuned and well written assembly language can be faster, but only if you take account of the higher level issues like memory access patterns etc as well as lower level issues like pipelining and branch prediction.


7) as mentioned SIMD (Single Instruction Multiple Data) code, MMX in particular could be very applicable to your code to process multiple pixels at the same time. Many compilers won't automatically generate MMX code, so you might want to look at using intrinsics to access MMX. You'll find sample code doing things similar to yours on the developer.intel.com website.


8) Bear in mind that a comparison/"if" in your inner loop to avoid doing work may actually be affecting performance - profile it to check.
Simon O'Connor | Technical Director (Newcastle) Lockwood Publishing | LinkedIn | Personal site
Winograd
Winograd
In case you're developing on linux, you should try the Valgrind . It's able of doing cache profiling for you with given cache configuration (you're not bound to your current processor). Another amazing tool is OProfile,but it needs kernel support (found in vanilla branch). OProfile is capable of profiling your entire system (including kernel).
ajas95
ajas95
You don't need assembly. I guess mmx instruction could give you a little speed, but you need to look at WHY your code is slow: all your pointers and references are alias hazards for the compiler. Which means that when you write img3ptr[0], the compiler needs to reload tGreenOnBlack and tInverseBlend, because the compiler has no idea that img3ptr[0] doesn't write the locations those variables reference!

So basically, the compiler can't do any optimization because it's fetching memory every operation. I wouldn't worry that this memory is uncached... you're accessing memory sequentially which allows automatic prefetches. It's just that the compiler can't trust its registers!

bottom line: eliminate all the '&'s .
Kylotan
Kylotan
You may get some speed from removing that if statement. Sometimes it's better to execute a few instructions more than you need every few iterations than to keep checking and branching every iteration.

Another optimisation that shouldn't take too long or make your code uglier than necessary is to try counting your loops backwards instead of forwards. Processors I have studied do less work when comparing a counter to zero than comparing it to some arbitrary value.
birdjames
birdjames
I see that you're not using the x and y variables. Your loop will have to access the value of myImageInfo1.iWidth for each pixel and myImageInfo1.iHeight for each row of your image. You can reduce memory access by removing the two for loops and replacing with one while loop (as shown in the following code):

int iNumPixels = myImageInfo1.iWidth * myImageInfo1.iHeight;while ( iNumPixels-- ){	// Pixel processing code here}

Checking for a non-zero instead of doing an integer comparison each pixel is slightly faster. You could also make iNumPixels a register integer instead of an auto although the compiler is not likely to actually store the variable in a register due to the amount of code you have inside the loop.

(EDIT)
Just noticed post above mine - Kylotan kinda beat me to it!
(/EDIT)

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.