views:

1434

answers:

4

My compact framework application creates a smooth-scrolling list by rendering all the items to a large bitmap surface, then copying that bitmap to an offset position on the screen so that only the appropriate items show. Older versions only rendered the items that should appear on screen at the time, but this approach was too slow for a smooth scrolling interface.

It occasionally generates an OutOfMemoryException when initially creating the large bitmap. If the user performs a soft-reset of the device and runs the application again, it is able to perform the creation without issue.

It doesn't look like this bitmap is being generated in program memory, since the application uses approximately the same amount of program memory as it did before the new smooth-scrolling methods.

Is there some way I can prevent this exception? Is there any way I can free up the memory I need (wherever it is) before the exception is thrown?

A: 

Your bitmap definitely is being created in program memory. How much memory the bitmap needs depends on how big it is, and whether or not this required size will generate the OutOfMemoryException depends on how much is available to the PDA (which makes this a randomly-occuring error).

Sorry, but this is generally an inadvisable control rendering technique (especially on the Compact Framework) for which there is no fix short of increasing the physical memory on the PDA, which isn't usually possible (and often won't fix the problem anyway, since a CF process is limited to 32MB no matter how much the device has available).

Your best bet is to go back to the old version and improve its rendering speed. There is also a simple technique available on CF for making a control double-buffered to eliminate flicker.

MusiGenesis
Are you sure about it being created in program memory? I have GC.GetTotalMemory(false) output to debug before and after bitmap creation and get the following:Bitmap Size:320x12450Mem before:211676Mem after:211996Seems awful small.
Jake Stevenson
320x12450 = 4 million pixels. at 16bpp, that 8MB of memory required to hold that image alone. You're wondering why you're getting an OOM?
ctacke
I realized that memory usage isn't always noted by the GC, since bitmaps may include unmanaged memory. So I used GlobalMemoryStatus() to check available physical memory instead. On a VGA device, this gave me a 640x8650 bitmap, with a loss of 1.3 megabytes of available physical memory.
Jake Stevenson
On the PDA, there isn't anywhere the bitmap could be created other than program memory (there isn't a swap file).
MusiGenesis
Jake, that sounds more like it. But your bitmaps are way bigger than I even thought they were.
MusiGenesis
Think of your OOME as one of the ghosts in Dickens' A Christmas Carol. :)
MusiGenesis
Ok, it has to come out of program memory. But it might not be included in THIS program's memory according to http://blogs.msdn.com/scottholden/archive/2006/08/22/713056.asp. I am making a DDB which might be allocated to gwes.exe or even in dedicated video RAM.
Jake Stevenson
I'm getting a page not found error on that link.
MusiGenesis
The Bitmap class in .NET does appear to be just a managed wrapper around a block of unmanaged memory used to hold the bitmap data, which is why your GetTotalMemory call showed such a tiny increase after creating your bitmap.
MusiGenesis
Here's the link: http://blogs.msdn.com/scottholden/archive/2006/08/22/713056.aspx
Jake Stevenson
But this makes me feel like making individual bitmaps won't help either, unless I find some way to swap them in/out of memory quick enough to make the scrolling smooth.
Jake Stevenson
Cool link, thanks. I didn't know the bitmap was created in a different place based on streams or heightxwidth. All of my OOMEs have been with the heightxwidth constructor, like you're using.
MusiGenesis
There won't be any speed difference between the one-big-bitmap and the many-little-bitmaps approaches. The same amount of image-copying from memory to the drawing surface takes place either way.
MusiGenesis
If this is a problem, BTW, you can speed up this part of the operation considerably by PInvoking the BitBlt API function. This is rarely necessary, however, especially on the small PDA screen.
MusiGenesis
I'm more concerned about the memory usage. 50 smaller bitmaps vs 1 giant bitmap doesn't really solve my issue, which is running out of memory in the gwes.exe process space. I could offload some of the smaller ones (that aren't on-screen right now) to disk, but I'm worried about the speed.
Jake Stevenson
Try a quick test app: one version that tries to create one huge bitmap, and another version that tries to create a collection of smaller bitmaps that add up to the first. See which one generates OOMEs.
MusiGenesis
How complex is the rendering of each item? Is it just text, or are there some expensive graphics on each one too?
MusiGenesis
It's several lines of text in one font, another line of text in a smaller font, a 96x96 image, and possibly a few other lines. The rendering time is significant enough to be noticed.
Jake Stevenson
I stand corrected. I just did a test app like I suggested, and creating a big list of small bitmaps eventually throws the OOME.
MusiGenesis
I'm afraid it looks like my first answer was right. If there was some way to force the OS to allocate more memory to bitmaps, that might kinda work, but this looks like it's just a device limitation.
MusiGenesis
I had an idea, but way too long for here. See my next answer.
MusiGenesis
+1  A: 

And just as soon as I posted I thought of something you can do to fix your problem with the new version. The problem you have is one of CF trying to find one block of contiguous memory available for the huge bitmap, and this is occasionally a problem.

Instead of creating one big bitmap, you can instead create a collection of smaller bitmaps, one for each item, and render each item onto its own little bitmap. During display, you then just copy over the bitmaps you need. CF will have a much easier time creating a bunch of little bitmaps than one big one, and you shouldn't have any memory problems unless this is a truly enormous bunch of items.

I should avoid expressions like "there is no fix".

One other important point: make sure you call Dispose() on each bitmap when you're finished with it.

MusiGenesis
Isn't part of garbage collection memory compaction? And would there be even more memory overhead from having 100 seperate bitmap objects over one single one?It's still better than any other ideas I've got, so I may try it out over the next couple of days. I do appreciate your feedback.
Jake Stevenson
GC isn't guaranteed to collect at any particular time, so you might sometimes have enough memory to create a bitmap but the GC hasn't collected it yet - OutOfMemoryException.
MusiGenesis
Per-object memory overhead in .NET is pretty trivial compared to the drawing surface needs, even in CF, so that's not something to worry about. I've done lots of work with bitmaps in CF, and creating a huge bitmap is ALWAYS trouble.
MusiGenesis
One other thing to remember: an OutOfMemoryException does not necessarily mean your process is really out of memory, just that that's the type of exception that was thrown. I suspect in this case that CF sometimes just decides that a bitmap will take up too much available memory and says no.
MusiGenesis
A: 

Since it appears you've run into a device limitation that is restricting the total size of Bitmap space you can create (these are apparently created in video RAM rather than general program memory), one alternative is to replace the big Bitmap object used here with a plain-old block of Windows memory, accessing it for reading and writing by PInvoking the BitBlt API function.

Initially creating the memory block is tricky, and you'd probably want to ask another SO question about that (GCHandle.Alloc can be used here to create a "pinned" object, which means .NET isn't allowed to move it around in memory, which is critical here). I know how to do it, but I'm not sure I do it correctly and I'd rather have an expert's input.

Once you've created the big block, you'd iterate through your items, render each to one small bitmap that you keep re-using (using your existing .NET code), and BitBlt it to the appropriate spot in your memory block.

After creating the entire cache, your rendering code should work just like before, with the difference that instead of copying from the big bitmap to your rendering surface, you BitBlt from your cache block. The arguments for BitBlt are essentially the same as for DrawImage (destination, source, coordinates and sizes etc.).

Since you're creating the cache out of regular memory this way instead of specialized video RAM, I don't think you'll run into the same problem. However, I would definitely get the block creation code working first and test to make sure it can create a big enough block every time.

Update: actually, the ideal approach would be to have a collection of smaller memory blocks rather than one big one (like I thought was the problem with the Bitmap approach), but you already have enough to do. I've worked with CF apps that deal with 5 and 10MB objects and it's not a huge problem anyway (although it might be a bigger problem when that chunk is pinned - I dunno). BTW, I've always been surprised by the OOMEs on BitMap creation because I knew the bitmaps were much smaller than the available memory, as did you - now I know why. Sorry I thought this was an easy solve at first.

MusiGenesis
If (!) this approach works, you could even encapsulate it as a BigBitMap which has the same interface as BitMap, and just drop it into your existing code.
MusiGenesis
Er, maybe not. I forgot BitMap implements Image.
MusiGenesis
Any idea how to determine how much memory I need to allocate for the bitmap?
Jake Stevenson
Never mind - this won't work either. I just did some basic tests trying to create about 4MB worth of stuff at once, and .NET CF just can't do it, even 10 400,000-length byte arrays.
MusiGenesis
I had another thought, though: where do the 96x96 images come from? Are they JPEGs stored as files or embedded as resources, or are them custom-draw at caching time?
MusiGenesis
Those are PNGs on disk. I'm being careful to dispose() them when done.
Jake Stevenson
Bing! (I won't add the "o" until I'm sure this is the problem). What are the dimensions of the PNG files? PNGs and JPEGs have a "default" size even though they're resizeable. I'll bet if you open one of these PNGs in Paint, it will show a larger size than 96x96.
MusiGenesis
Also, you don't actually show these images in their full 96x96 size in your custom list, do you? Are they actually displayed as much smaller thumbnails?
MusiGenesis
The size of the avatars depends on the device resolution- 96x96 for VGA and 50x50 for QVGA. I resize them appropriately before caching to disk. They are displayed in the sizes specified, not re-sized during rendering.
Jake Stevenson
Only rendering the items displayed worked ok, and was acceptable. Most users didn't notice it was slow until I tried this new rendering method. Now it's super-smooth and I'm not really ready to lose that. I'm still pursuing the idea of making an unmanaged memory block.
Jake Stevenson
The first CF app I inherited used 300x300 PNGs to display little 8x8 images in a gridview (sometimes 100s). The Bitmaps created from the PNGs for this were each 300x300, and it was incredibly (many minutes) slow. Thought that might be the problem.
MusiGenesis
One way to speed up your original renderer might be to either use .bmp files from the start, or pre-convert the PNGs to temp .bmp files. As a caching strategy, I'm pretty sure it's faster to read a .bmp into memory than a .png.
MusiGenesis
I just tried creating an unmanaged block using Marshal.AllocHGlobal and Marshal.AllocCoTaskMem, and both methods threw OOMEs if the block was much larger than 1MB.
MusiGenesis
Really? I just tried LocalAlloc(LPTR, 4194304) and had no problem. Is my math wrong?
Jake Stevenson
The Marshal.Alloc functions create memory that is unmanaged but still in the application's process. I assume you're PInvoking LocalAlloc, and I think that memory is in a different process (not sure). Can you create a byte array that big in .NET with no problems?
MusiGenesis
On a side note, I just benchmarked loading a 100x100 bitmap from a file and displaying in a picturebox, and it takes about 15 milliseconds on my device (same speed with .bmp or .png), which should be plenty fast for rendering 3 or 4 images at once. The first version may have had a different problem.
MusiGenesis
Interestingly, though, loading from a file here takes about 400-500 ms when the code is run in debug mode from VS, which is a pretty massive difference. Did you notice in your earlier version that it ran better in production than in debug?
MusiGenesis
As you mentioned, I don't think it was really an issue with copying the images. It was the other parsing that had to be done (wrapping text, finding links, etc.) That price has to be paid somewhere, and I'd rather pay it up-front once instead of every time the list item is displayed.
Jake Stevenson
+1  A: 

I'd suggest going back to the old mechanism of rendering only part of the data, as the size of the fully-rendered data is obviously an issue. To help prevent rendering problems I would probably pre-render a few rows above and below the current view so they can be "scrolled" in with limited impact.

ctacke
His real problem is the 96x96 images, which either have to be on disk (which means slow) or in video RAM (which means OOME).
MusiGenesis
Although the pre-rendering is a good idea.
MusiGenesis
I've ended up doing something like this. I test the machine to determine how many items I can safely fit into memory, then render that many to a "window" bitmap that I can use as a fast-scrolling buffer. That window contains some before the current list and some after. Not as good, but still fast.
Jake Stevenson
Thanks for taking the time to come back and let us know what you did and how it worked out.
ctacke

related questions