Large number of soft page faults when assigning a TJpegImage to a TBitmap
- by Robert Oschler
I have a Delphi 6 Pro application that processes incoming jpeg frames from a streaming video server. The code works but I recently noticed that it generates a huge number of soft page faults over time. After doing some investigation, the page faults appear to be coming from one particular graphics operation. Note, the uncompressed bitmaps in question are 320 x 240 or about 300 KB in size so it's not due to the handling of large images. The number of page faults being generated isn't tolerable. Over an hour it can easily top 1000000 page faults.
I created a stripped down test case that executes the code I have included below on a timer, 10 times a second. The page faults appear to happen when I try to assign the TJpegImage to a TBitmap in the GetBitmap() method. I know this because I commented out that line and the page faults do not occur. The assign() triggers a decompression operation on the part of TJpegImage as it pushes the decompressed bits into a newly created bitmap that GetBitmap() returns. When I run Microsoft's pfmon utility (page fault monitor), I get a huge number of soft page fault error lines concerning RtlFillMemoryUlong, so it appears to happen during a memory buffer fill operation.
One puzzling note. The summary part of pfmon's report where it shows which DLL caused what page fault does not show any DLL names in the far left column. I tried this on another system and it happens there too.
Can anyone suggest a fix or a workaround? Here's the code. Note, IReceiveBufferForClientSocket is a simple class object that holds bytes in an accumulating buffer.
function GetBitmap(theJpegImage: TJpegImage): Graphics.TBitmap;
begin
Result := TBitmap.Create;
Result.Assign(theJpegImage);
end;
// ---------------------------------------------------------------
procedure processJpegFrame(intfReceiveBuffer: IReceiveBufferForClientSocket);
var
theBitmap: TBitmap;
theJpegStream, theBitmapStream: TMemoryStream;
theJpegImage: TJpegImage;
begin
theBitmap := nil;
theJpegImage := TJPEGImage.Create;
theJpegStream:= TMemoryStream.Create;
theBitmapStream := TMemoryStream.Create;
try // 2
// ************************ BEGIN JPEG FRAME PROCESSING
// Load the JPEG image from the receive buffer.
theJpegStream.Size := intfReceiveBuffer.numBytesInBuffer;
Move(intfReceiveBuffer.bufPtr^, theJpegStream.Memory^, intfReceiveBuffer.numBytesInBuffer);
theJpegImage.LoadFromStream(theJpegStream);
// Convert to bitmap.
theBitmap := GetBitmap(theJpegImage);
finally
// Free memory objects.
if Assigned(theBitmap) then
theBitmap.Free;
if Assigned(theJpegImage) then
theJpegImage.Free;
if Assigned(theBitmapStream) then
theBitmapStream.Free;
if Assigned(theJpegStream) then
theJpegStream.Free;
end; // try()
end;
// ---------------------------------------------------------------
procedure TForm1.Timer1Timer(Sender: TObject);
begin
processJpegFrame(FIntfReceiveBufferForClientSocket);
end;
// ---------------------------------------------------------------
procedure TForm1.FormCreate(Sender: TObject);
var
S: string;
begin
FIntfReceiveBufferForClientSocket := TReceiveBufferForClientSocket.Create(1000000);
S := loadStringFromFile('c:\test.jpg');
FIntfReceiveBufferForClientSocket.assign(S);
end;
// ---------------------------------------------------------------
Thanks,
Robert