【发布时间】:2023-03-26 15:54:02
【问题描述】:
当通过WriteableBitmap 处理小 文件 (
虽然这在小型应用程序中不是一个重大瓶颈,但当内存中存在数千个对象时(例如:EntityFramework 上下文加载了许多实体和关系)。
综合测试:
var objectCountPressure = (
from x in Enumerable.Range(65, 26)
let root = new DirectoryInfo((char)x + ":\\")
let subs =
from y in Enumerable.Range(0, 100 * IntPtr.Size)
let sub =new {DI = new DirectoryInfo(Path.Combine(root.FullName, "sub" + y)), Parent = root}
let files = from z in Enumerable.Range(0, 400) select new {FI = new FileInfo(Path.Combine(sub.DI.FullName, "file" + z)), Parent = sub}
select new {sub, files = files.ToList()}
select new {root, subs = subs.ToList()}
).ToList();
const int Size = 32;
Action<int> handler = threadnr => {
Console.WriteLine(threadnr + " => " + Thread.CurrentThread.ManagedThreadId);
for (int i = 0; i < 10000; i++) {
var wb = new WriteableBitmap(Size, Size, 96, 96, PixelFormats.Bgra32, null);
wb.Lock();
var stride = wb.BackBufferStride;
var blocks = stride / sizeof(int);
unsafe {
var row = (byte*)wb.BackBuffer;
for (int y = 0; y < wb.PixelHeight; y++, row += stride)
{
var start = (int*)row;
for (int x = 0; x < blocks; x++, start++)
*start = i;
}
}
wb.Unlock();
wb.Freeze(); }
};
var sw = Stopwatch.StartNew();
Console.WriteLine("start: {0:n3} ms", sw.Elapsed.TotalMilliseconds);
Parallel.For(0, Environment.ProcessorCount, new ParallelOptions{MaxDegreeOfParallelism = Environment.ProcessorCount}, handler);
Console.WriteLine("stop : {0:n2} s", sw.Elapsed.TotalSeconds);
GC.KeepAlive(objectCountPressure);
我可以使用“const int Size = 48”运行这个测试十几次:它总是在大约 1.5 秒内返回,而“# Induced GC”有时会增加 1 或 2。
当我将“const int Size = 48”更改为“const int Size = 32”时,发生了一件非常非常糟糕的事情:“#Induced GC”每秒增加 10 次,现在总运行时间超过一分钟:~80 秒!
[在 8GB RAM 的 Win7x64 Core-i7-2600 上测试 // .NET 4.0.30319.237 ]
WTF!?
要么框架有一个非常严重的错误,要么我做错了什么。
顺便说一句:
我不是通过进行图像处理来解决这个问题,而是通过 DataTemplate 对某些数据库实体使用包含图像的工具提示:
这工作正常(快速),而 RAM 中不存在太多对象 - 但是当存在数百万其他对象(完全不相关)时,显示工具提示总是延迟几秒钟,而其他一切都正常工作。
【问题讨论】:
-
即使为“好”尺寸分配的内存量也比预期的 4*width*height 高很多(10 倍左右)
-
在 WritableBitmap.GetEstimatedSize 上使用反射器,它是
return (long) ((((pixelWidth * pixelHeight) * pixelFormat.InternalBitsPerPixel) / 8) * 2);-> 所以对于 32x32,它正好是 0x2000 (8kb)。再往下看,SafeMILHandleMemoryPressure 的 ctor 决定在 > 0x2000 处做一些不同的事情——低于或等于 0x2000 它总是调用 GC.AddMemoryPressure(long) ,这似乎总是触发 GC.Collect(2);即使每秒发生一千次。 -
我怀疑在某些情况下他们忘记将
GC.AddMemoryPressure呼叫与GC.RemoveMemoryPressure配对。因此内存压力继续上升,一旦超过一定水平,每次调用AddMemoryPressure都会触发一次完整的GC。 -
有趣的是,当我在 LinqPad 中运行时,效果比运行普通版本的程序时大得多。
-
无法想象这是“设计使然”。它必须是一个错误。我认为将此报告给 MS-CONNECT 是一件好事。
标签: .net wpf garbage-collection