" + Thread.Current-6ren">
gpt4 book ai didi

.net - 处理小图像(<=4k 像素数据)时强制进行 GC?

转载 作者:行者123 更新时间:2023-12-02 03:14:57 36 4
gpt4 key购买 nike

当通过 WriteableBitmap< 处理文件(<= 32x32)时,我看到性能计数器“# Induced GC”(在完美的应用程序中应保持为零)快速增加.

虽然这在小型应用程序中并不是一个重要的瓶颈,但当内存中存在数千个对象时,它就会成为一个非常大的问题(应用程序在 99.75%“% Time in GC”中每一步卡住几秒钟)(例如: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 上测试]

什么鬼!?

要么框架有一个非常严重的错误,要么我做的事情完全错误。

顺便说一句:
我不是通过进行图像处理来解决这个问题,而是通过 DataTemplate 使用包含针对某些数据库实体的图像的工具提示:当 RAM 中不存在太多对象时,这工作得很好(很快)——但是当存在数百万个其他对象(完全不相关)时,显示工具提示总是延迟几秒钟,而其他一切都工作正常。

最佳答案

TL;DR: 可能最好的解决方案是创建一个 WriteableBitmaps 的小型池。并重复使用它们,而不是创建它们然后扔掉它们。

因此,我开始使用 WinDbg 进行探索,看看是什么导致了收集的发生。

首先,我添加了对 Debugger.Break() 的调用到 Main 的开头让事情变得更容易。我还添加了自己的调用 GC.Collect()作为健全性检查,以确保我的断点工作正常。然后在WinDbg中:

0:000> .loadby sos clr
0:000> !bpmd mscorlib.dll System.GC.Collect
Found 3 methods in module 000007feee811000...
MethodDesc = 000007feee896cb0
Setting breakpoint: bp 000007FEEF20E0C0 [System.GC.Collect(Int32)]
MethodDesc = 000007feee896cc0
Setting breakpoint: bp 000007FEEF20DDD0 [System.GC.Collect()]
MethodDesc = 000007feee896cd0
Setting breakpoint: bp 000007FEEEB74A80 [System.GC.Collect(Int32, System.GCCollectionMode)]
Adding pending breakpoints...
0:000> g
Breakpoint 1 hit
mscorlib_ni+0x9fddd0:
000007fe`ef20ddd0 4154 push r12
0:000> !clrstack
OS Thread Id: 0x49c (0)
Child SP IP Call Site
000000000014ed58 000007feef20ddd0 System.GC.Collect()
000000000014ed60 000007ff00140388 ConsoleApplication1.Program.Main(System.String[])

所以断点工作正常,但是当我让程序继续时,它再也没有被击中。 GC 例程似乎是从更深的地方调用的。接下来我走进GC.Collect()函数来查看它正在调用什么。为了更轻松地做到这一点,我添加了第二个调用 GC.Collect()紧接着第一个之后,就进入了第二个。这避免了单步执行所有 JIT 编译:

Breakpoint 1 hit
mscorlib_ni+0x9fddd0:
000007fe`ef20ddd0 4154 push r12
0:000> p
mscorlib_ni+0x9fddd2:
000007fe`ef20ddd2 4155 push r13
0:000> p
...
0:000> p
mscorlib_ni+0x9fde00:
000007fe`ef20de00 4c8b1d990b61ff mov r11,qword ptr [mscorlib_ni+0xe9a0 (000007fe`ee81e9a0)] ds:000007fe`ee81e9a0={clr!GCInterface::Collect (000007fe`eb976100)}

稍微走了一步后,我注意到对 clr!GCInterface::Collect 的引用这听起来很有希望。不幸的是,它的断点从未触发。进一步挖掘GC.Collect()我发现clr!WKS::GCHeap::GarbageCollect事实证明这是真正的方法。断点揭示了触发收集的代码:

0:009> bp clr!WKS::GCHeap::GarbageCollect
0:009> g
Breakpoint 4 hit
clr!WKS::GCHeap::GarbageCollect:
000007fe`eb919490 488bc4 mov rax,rsp
0:006> !clrstack
OS Thread Id: 0x954 (6)
Child SP IP Call Site
0000000000e4e708 000007feeb919490 [NDirectMethodFrameStandalone: 0000000000e4e708] System.GC._AddMemoryPressure(UInt64)
0000000000e4e6d0 000007feeeb9d4f7 System.GC.AddMemoryPressure(Int64)
0000000000e4e7a0 000007fee9259a4e System.Windows.Media.SafeMILHandle.UpdateEstimatedSize(Int64)
0000000000e4e7e0 000007fee9997b97 System.Windows.Media.Imaging.WriteableBitmap..ctor(Int32, Int32, Double, Double, System.Windows.Media.PixelFormat, System.Windows.Media.Imaging.BitmapPalette)
0000000000e4e8e0 000007ff00141f92 ConsoleApplication1.Program.<Main>b__c(Int32)

所以WriteableBitmap的构造函数间接调用 GC.AddMemoryPressure ,最终导致集合(顺便说一句, GC.AddMemoryPressure 是模拟内存使用情况的更简单方法)。但这并不能解释尺寸从 33 变为 32 时行为的突然变化。

ILSpy在这里帮忙。特别是,如果您查看 SafeMILHandleMemoryPressure 的构造函数(由 SafeMILHandle.UpdateEstimatedSize 调用)你会看到它只使用 GC.AddMemoryPressure如果要添加的压力 <= 8192。否则它使用自己的自定义系统来跟踪内存压力并触发集合。具有 32 位像素的 32x32 位图大小低于此限制,因为 WriteableBitmap估计内存使用量为 32 * 32 * 4 * 2(我不确定为什么会有额外的因子 2)。

总之,您所看到的行为似乎是框架中启发式的结果,该框架不太适合您的情况。 您可以通过创建比您需要的尺寸更大或像素格式更大的位图来解决这个问题,以便位图的估计内存大小> 8192。

事后思考:我想这也表明集合是由于 GC.AddMemoryPressure 触发的。是否计入“# Induced GC”?

关于.net - 处理小图像(<=4k 像素数据)时强制进行 GC?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7331735/

36 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com