Bitmaps in the Compact Framework
来源:互联网 发布:中教数据库官网 编辑:程序博客网 时间:2024/05/17 09:24
昨天遇到的在Windows Mobile上一直出现的Out of memory的Exception,下边是一老外的分析测试,Very helpful!
The Bitmap class in the Compact Framework is a confusing thing, largely because it has abstracted what the OS is doing underneath a little too far. For example, look at the following code:
Bitmap bmp1 = new Bitmap(fileStream);
Bitmap bmp2 = new Bitmap(200, 200);
Let's assume that fileStream is a valid stream to a resource bitmap file that is 100x100 in size. So is there any difference between bmp1 and bmp2, other than the fact bmp1 presumably has some color data in it? The answer is yes - there's a very big difference, and that difference can have a huge impact on application performace as well as cause exceptions.
So let's look at this a little deeper with some examples. Here's the first:
int iterations = 0;
while (true)
{
try
{
iterations++;
Bitmap b = new Bitmap(GetImageStream());
if (iterations % 100 == 0)
{
Debug.WriteLine(string.Format("{0} objects", iterations));
}
}
catch
{
Debug.WriteLine(string.Format("Failed after {0} objects", iterations));
Debugger.Break();
}
}
If I run this code (GetImageStream() just pulls an image from an embedded resource), the app will run forever, occasionally spitting out the debug port how many hundreds of objects it's created. If you run RPM on it you'll see memory getting allocated, the GC firing occasionally and resources being freed up. All is well in the world of managed code and everything is working as expected. Hooray.
Now let's change that ever so slightly to this:
int iterations = 0;
while (true)
{
try
{
iterations++;
Bitmap b = new Bitmap(200, 200); // <--- CHANGED HERE
if (iterations % 100 == 0)
{
Debug.WriteLine(string.Format("{0} objects", iterations));
}
}
catch
{
Debug.WriteLine(string.Format("Failed after {0} objects", iterations));
Debugger.Break();
}
}
Note the single change where the bitmap is created. Try running this and after a few iterations - the exact number depends on available device memory - it will OOM (throw an out of memory exception). On the device in front of me it was about 40.
So the first thing to do is theorize why this would happen. Seems like the Bitmap's resources aren't getting freed after it goes out of scope at the end of the while block. An explicit call to Dispose() may solve it if that's the case, so let's try another test.
int iterations = 0;
Bitmap b = null;
while (true)
{
if (b != null) // explicit disposal
b.Dispose();
try
{
iterations++;
b = new Bitmap(200, 200);
if (iterations % 100 == 0)
{
Debug.WriteLine(string.Format("{0} objects", iterations));
}
}
catch
{
Debug.WriteLine(string.Format("Failed after {0} objects", iterations));
Debugger.Break();
}
}
Sure enough, when we run this, it behaves like the first. Strange that the Bitmap behaves differently depending on which constructor we use - this is contrary to common sense, right?
So let's think a little more. A Bitmap has a large area of unmanaged resources and some managed resources. It seems that when we create a bitmap using the size ctor, the finalizer doesn't get run when an OOM happens. Let's test again and see if that really is what's going on. We'll remove the explicit Dispose call and wait for the finalizers and try again when we OOM.
int iterations = 0;
while (true)
{
try
{
iterations++;
Bitmap b = new Bitmap(200, 200);
if (iterations % 100 == 0)
{
Debug.WriteLine(string.Format("{0} objects", iterations));
}
}
catch (OutOfMemoryException)
{
Debug.WriteLine("Waiting for finalizers to run..."));
GC.WaitForPendingFinalizers();
Bitmap b = new Bitmap(GetImageStream());
}
}
When we run this one, again all is well in managed code land, though we see it waiting for finalizers to run a lot, and that catch is an expensive one for perf (as all exceptions are). At this point I think "well that surely has to be a bug" but I often like a second opinion, so I went right to the source and asked the CF team about the behavior. The response from them is actually quite informative. Their response in in italics below.
I think you are probably seeing is several interactions that can be quite confusing.
- Creating a bitmap using the stream constructor will construct a DIB (Device Independent Bitmap).
- Creating a bitmap using the width/height constructor will construct a DDB (Device Dependent Bitmap).
- DIB's are allocated out of the virtual address space of the application.
- DDB's are allocated by the driver. This typically means that they are allocated in the virtual address space of gwes.exe. Alternatively, the driver could allocate these in dedicated video ram.
- Creating a bitmap with the stream constructor will generate a fair amount of garbage as it copies data from one buffer to the other.
When we perform a GC because of an OOM in the stream constructor case, we will almost certainly have some amount of garbage that we can free back to the OS immediately. This will also trigger the finalizer to run on another thread as soon as possible. That should help the next call to bitmap creation.
When we perform a GC because of an OOM in the width/height constructor case, it is fairly likely that the OOM is caused because of virtual memory exhaustion in gwes.exe. Thus freeing memory in our process will not help the memory condition in gwes.exe. We need the bitmap finalizer to run before this would actually free memory in a way that would help this scenario. While the finalizer thread would certainly have been triggered to start, it most likely will not get a chance to free bitmaps before we OOM while trying to allocate a bitmap immediately after triggering a GC on the initial thread.
In short, we have 2 different types of Bitmap in our runtime with varying performance and allocation characteristics. DDBs are generally faster to manipulate and draw to the screen than DIBs, but they are constructed in an external memory space that can cause allocation confusion and cause the performance of calls to LockBits or Save to be slow. If a DIB is desired and you wish to construct it based on width and height, we provide a function that constructs a Bitmap with a width, height, and pixelformat specified. This function will construct a DIB instead of a DDB.
I personally still consider this a bug in the implementation - the CF should catch these occasions and handle it for us rather than OOMing all the way back to the app to wait for the Finalizers and retry - that's an implementation that should be done below us.
Still the answer sheds light on the fact that how we create a Bitmap should be highly dependent on how we intend to use that Bitmap.
- Bitmaps in the Compact Framework
- How to hide the OK button in the dialog in .Net Compact Framework application?
- P/invoke in .NET Compact Framework
- The intro of .NET Compact Framework
- Application Blocks for the .NET Compact Framework
- Using MyXaml on the Compact Framework
- MemMaker for the .NET Compact Framework
- The Definitive Guide to the .NET Compact Framework
- .NET Compact Framework 3.5 in Platform Builder 6.0 (and 5.0)
- P/invoke in .NET Compact Framework [CF Skills]
- Creating Self-Updating Applications With the .NET Compact Framework
- Developing Smart Device WiFi Applications with the .NET Compact Framework
- Using the Microsoft .NET Compact Framework MessageWindow Class
- SQL Server CE Database Development with the .NET Compact Framework
- Building Graphically Advanced Applications with the .NET Compact Framework 3.5
- Compact framework 备忘录 1
- .net compact framework
- .NET Compact Framework 编程
- sql语句延时执行或者是指定时间执行
- 博客系统周志
- BCD码、十六进制与十进制互转
- U-boot添加命令的两个版本
- OGRE中的数据驱动
- Bitmaps in the Compact Framework
- 常见Http Header返回状态详解
- 解决DataGridView绑定List后不能排序的问题
- 10个效果最佳的编程字体
- 转- oracle10g-如何提高pl/sql程序的性能
- oracle日志维护
- Web 2.0 –基于服务的下一代互联网
- 汇编与C的基本逻辑语句对应关系
- GameEngineArchitecture读书笔记(三)