BitMap

本文探讨了Compact Framework中Bitmap类的两种构造方式:通过流构造Device Independent Bitmap (DIB) 和通过尺寸构造Device Dependent Bitmap (DDB)。这两种方式在内存管理和资源释放上存在显著差异,可能导致应用性能问题及内存溢出。

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.

  1. Creating a bitmap using the stream constructor will construct a DIB (Device Independent Bitmap).
  2. Creating a bitmap using the width/height constructor will construct a DDB (Device Dependent Bitmap).
  3. DIB's are allocated out of the virtual address space of the application.
  4. 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.
  5. 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.

### Bitmap 技术与使用方法 Bitmap 是一种常见的图像表示方式,它将图像存储为像素矩阵,每个像素点包含颜色信息。以下是关于 Bitmap 的技术细节和使用方法的详细说明。 #### 1. Bitmap 的基本概念 Bitmap 图像由一组像素组成,每个像素的颜色值通过 RGB 或 ARGB 表示[^2]。在 Android 开发中,Bitmap 常用于加载、处理和显示图片。例如,可以通过 `BitmapFactory` 类提供的方法从资源文件、文件系统或网络流中解码图片。 #### 2. 创建和加载 Bitmap 在 Android 中,可以使用以下方法创建和加载 Bitmap: ```java // 从资源文件加载 Bitmap Bitmap bitmapFromResource = BitmapFactory.decodeResource(getResources(), R.drawable.image); // 从文件路径加载 Bitmap Bitmap bitmapFromFile = BitmapFactory.decodeFile("/path/to/image.jpg"); // 从字节数组加载 Bitmap byte[] imageData = ...; // 图像数据 Bitmap bitmapFromByteArray = BitmapFactory.decodeByteArray(imageData, 0, imageData.length); ``` 这些方法允许开发者根据需求选择不同的数据源来生成 Bitmap 对象[^2]。 #### 3. Bitmap 的压缩与优化 为了减少内存占用,可以对 Bitmap 进行压缩。以下是一个示例代码,展示如何通过设置 `BitmapFactory.Options` 来调整图片大小和质量: ```java BitmapFactory.Options options = new BitmapFactory.Options(); options.inSampleSize = 2; // 缩小图片尺寸为原来的 1/2 Bitmap compressedBitmap = BitmapFactory.decodeFile("/path/to/image.jpg", options); ``` 上述代码通过设置 `inSampleSize` 参数实现图片的缩放,从而降低内存消耗。 #### 4. Bitmap 在用户人群分析中的应用 在用户数据分析领域,Bitmap 可以用于高效存储和操作大规模布尔型数据集。例如,在 Starrocks 数据库中,Bitmap 被广泛应用于用户群体的交集、并集和差集计算。这种技术能够显著提高查询性能,尤其是在处理海量数据时[^1]。 #### 5. RoaringBitmap 扩展 RoaringBitmap 是一种高效的 Bitmap 实现方案,特别适用于稀疏数据集的压缩和操作。在 PostgreSQL 中,`pg_roaringbitmap` 扩展提供了对 RoaringBitmap 的支持,允许开发者在数据库层面进行复杂的集合运算[^4]。 #### 6. BitmapFont 渲染技术 BitmapFont 是一种用于游戏开发和其他图形界面的字体渲染技术。它通过预渲染的字符图像和 `.fnt` 文件定义字符位置,实现高效的文字绘制。以下是加载 BitmapFont 的示例代码: ```java BitmapFont font = new BitmapFont(Gdx.files.internal("font.fnt"), Gdx.files.internal("font.png"), false); font.draw(batch, "Hello World", 100, 100); ``` 这段代码展示了如何加载一个 BitmapFont 并将其绘制到屏幕上[^5]。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值