2023年1月

到目前为止,我还没有写过一篇博客文章,给出关于做绩效工作的哲学建议。但是最近我想也许是时候写这样一个条目了,因为我见过很多人认真地看一些性能计数器(通常是不正确的)或其他数据,并问了很多问题,比如“这个分配率太高了吗?”,“在我看来它太高了。”或者“我的size太大了,对吧?",在他们没有足够的证据来证明这样的调查和问题是合理的之前,这似乎是个大问题。
现在,如果你只是问些问题来满足你的好奇心,那就太好了。我很乐意回答您的问题,也很乐意为您指出要阅读的文档。但对于那些被要求调查与绩效相关的问题的人,尤其是在期限临近时,我的建议是“在试图找到解决方案之前,先了解问题所在”。根据证据来决定要看什么,而不是基于你对这个领域的知识的缺乏,除非你已经穷尽了你所知道的领域。在你问与GC相关的问题之前,问问你自己你是否认为GC实际上就是问题所在。如果你不能回答这个问题,那么你就不能好好利用你的时间去问与GC相关的问题。
我见过很多例子,当托管应用程序中出现问题时,人们会立即怀疑GC,而没有任何证据支持这种怀疑。然后他们开始问问题——通常是非常随机的——希望他们能在不了解问题是什么的情况下找到解决问题的方法。这不符合逻辑,是吗?所以别这样!

因此,您如何知道要解决的问题是什么,我建议您:

1、了解基础知识真的很有帮助。

什么是基础?一般来说,性能归结为两件事——内存和CPU。了解这两个方面的基本知识有助于确定要看哪一个方面。显然这需要大量的阅读和实验。我将列出一些内存基础知识,以帮助您入门:内存的一些基本原理。

每个进程都有自己独立的虚拟地址空间;同一台机器上的所有进程共享物理内存(如果有页面文件,还可以加上页文件)。在32位上,默认情况下,每个进程都有一个2GB的用户模式虚拟地址空间。
作为应用程序的作者,您使用的是虚拟地址空间—您永远不会直接操作物理内存。如果您正在编写本机代码,您通常通过某种win32堆API(crt堆或进程堆或您创建的堆)使用虚拟地址空间—这些堆API将代表您分配和释放虚拟内存;如果您正在编写托管代码,GC将代表您分配/释放虚拟内存。
虚拟地址空间可能会变得支离破碎-换句话说,地址空间中可能存在“洞”(空闲块)。当请求一个VM分配时,VM管理器需要找到一个足够大的空闲块来满足分配请求-如果只有几个空闲块的总和足够大,它将无法工作。这意味着即使你有2GB,你也不一定看到所有2GB都被使用了。
虚拟机可以处于不同的状态-空闲、保留和提交。免费很容易。有时,人们会困惑于保守和承诺之间的区别。首先,你需要认识到它们是不同的状态。Reserved是说“我想让这个内存区域供我自己使用”。在您保留了一个VM块之后,该块不能用于满足其他保留请求。此时,您还不能将任何数据存储在该内存块中—要做到这一点,您必须提交它,这意味着您必须用一些物理存储来备份数据,以便在其中存储数据。当您通过性能计数器查看内存时,请确保您看到的是正确的。如果要保留的空间或提交的空间不足,则可能会耗尽内存。
如果你有一个页面文件(默认情况下是这样),即使你的物理内存压力很低,你也可以使用它。当你的物理内存压力第一次变大,操作系统需要在物理内存中腾出空间来存储其他数据时,它会在页面文件中备份当前物理内存中的一些数据。在需要数据之前,这些数据不会被分页,这样您就可以进入物理内存负载非常低的情况下观察分页。

2、了解您的性能要求是必须的。

如果你正在编写一个服务器应用程序,很可能你想使用所有可用的内存和CPU,因为人们会完全精巧地使用机器来运行你的应用程序,那么为什么要浪费资源呢?如果你必须在另一台机器上运行的应用程序完全不同,那么你就知道如何在另一台机器上编写故事了。没有“你必须让你的应用程序尽可能少地使用内存”这样的规则。
当你认为有问题的时候,就去挖掘它,而不是去猜测可能出了什么问题。看看是谁在用你的内存。如果您认为托管堆使用了太多内存,请查看原因。使用过多内存的托管堆通常意味着您在应用程序中存活太多对象。看看那些幸存者是怎么活下来的。

正在悠哉,突然支持的同事过来说,某个用户软件启动不了了。详细情况是:由于出现某些问题,支持同事给这个客户重装了软件,然后就启动不了了,后来把安装目录改了名字,就能运行了。虽然客户能用了,但我很不理解为什么改了个目录名就能运行呢。于是恢复现场,重现故障,不管37二十一,抓个dmp先。

用windbg打开dmp

Windows 7 Version 7601 (Service Pack 1) MP (8 procs) Free x86 compatible
Product: WinNt, suite: SingleUserTS
Machine Name:
Debug session time: Tue Sep  1 14:42:17.000 2020 (UTC + 8:00)
System Uptime: 0 days 0:09:59.840
Process Uptime: 0 days 0:00:05.000
................................................................
...............................................................
This dump file has an exception of interest stored in it.
The stored exception information can be accessed via .ecxr.
(202c.1094): Unknown exception - code c0000374 (first/second chance not available)
eax=00000000 ebx=00000000 ecx=7fffffff edx=00000000 esi=02630000 edi=0000202c
eip=7708f8d1 esp=0020e864 ebp=0020e8e8 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00200246
ntdll!NtWaitForSingleObject+0x15:
7708f8d1 83c404          add     esp,4

可知,在id=1094的线程发生了c0000374(STATUS_HEAP_CORRUPTION ) 异常。

查看下栈,输入kv

0:000> kv
 # ChildEBP RetAddr  Args to Child              
00 0020e864 77118897 00000100 00000001 00000000 ntdll!NtWaitForSingleObject+0x15 (FPO: [3,0,0])
01 0020e8e8 771189c5 0020ea88 0020ead8 00000000 ntdll!RtlReportExceptionEx+0x14b (FPO: [Non-Fpo])
02 0020e940 7713ea7e 0020ea88 0020ead8 00000000 ntdll!RtlReportException+0x86 (FPO: [Non-Fpo])
03 0020e954 7713eafb c0000374 0020e988 770e4fb4 ntdll!RtlpTerminateFailureFilter+0x14 (FPO: [Non-Fpo])
04 0020e960 770e4fb4 00000000 0020efb4 7709d100 ntdll!RtlReportCriticalFailure+0x67 (FPO: [SEH])
05 0020e974 770e4e59 00000000 00000000 00000000 ntdll!_EH4_CallFilterFunc+0x12 (FPO: [Uses EBP] [0,0,4])
06 0020e99c 770d34a1 fffffffe 0020efa4 0020ead8 ntdll!_except_handler4+0x8e (FPO: [Non-Fpo])
07 0020e9c0 770d3473 0020ea88 0020efa4 0020ead8 ntdll!ExecuteHandler2+0x26 (FPO: [Uses EBP] [5,3,1])
08 0020e9e4 770d3414 0020ea88 0020efa4 0020ead8 ntdll!ExecuteHandler+0x24 (FPO: [5,0,3])
09 0020ea70 77080133 0020ea88 0020ead8 0020ea88 ntdll!RtlDispatchException+0x127 (FPO: [Non-Fpo])
0a 0020ea70 7713eaeb 0020ea88 0020ead8 0020ea88 ntdll!KiUserExceptionDispatcher+0xf (FPO: [2,0,0]) (CONTEXT @ 0020ead8)
0b 0020efb4 7713f9f1 c0000374 77174270 0020eff8 ntdll!RtlReportCriticalFailure+0x57 (FPO: [Non-Fpo])
0c 0020efc4 7713fad1 00000002 773ec360 00000000 ntdll!RtlpReportHeapFailure+0x21 (FPO: [Non-Fpo])
0d 0020eff8 770ed97c 00000009 006b0000 007149e2 ntdll!RtlpLogHeapFailure+0xa1 (FPO: [Non-Fpo])
0e 0020f028 6dd431f7 006b0000 00000000 007149e2 ntdll!RtlFreeHeap+0x64 (FPO: [Non-Fpo])
0f 0020f03c 6dd5c6d4 007149e2 00000000 0071c978 apphelp!SdbFree+0x22 (FPO: [Non-Fpo])
10 0020f080 6dd4db99 006b1ff6 0071c978 0020f288 apphelp!SdbpBuildLayerInfo+0x3d9 (FPO: [Non-Fpo])
11 0020f144 6dd4c490 006b1ff6 0071c978 0020f288 apphelp!SdbTraceQueryResult+0xd6 (FPO: [Non-Fpo])
12 0020f260 6dd4c3be 006b1ff6 0071c978 0020f288 apphelp!SeiInit+0xcb (FPO: [Non-Fpo])
13 0020f454 770d2cae 0020f560 00070000 00000000 apphelp!SE_InstallBeforeInit+0x67 (FPO: [Non-Fpo])
14 0020f470 770d2cd0 00718b90 0020f560 00070000 ntdll!LdrpLoadShimEngine+0xdc (FPO: [Non-Fpo])
15 0020f5f8 770b9f31 0020f66c 77070000 773edad0 ntdll!LdrpInitializeProcess+0x137f (FPO: [Non-Fpo])
16 0020f648 770a9799 0020f66c 77070000 00000000 ntdll!_LdrpInitialize+0x78 (FPO: [Non-Fpo])
17 0020f658 00000000 0020f66c 77070000 00000000 ntdll!LdrInitializeThunk+0x10 (FPO: [Non-Fpo])
看来是根兼容性有关阿。栈根我之前遇到问题一样(参考<记一次因兼容性问题崩溃>)。

于是远程客户机设置兼容性,发现设置过兼容性,但不行。于是我打开注册表HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers,发现里面有很多个关于我们程序设置项,先手动删掉这些项。在双击程序,成功跑起来了。

到了这里,我又有点迷惑了,之前是需要设置兼容,为什么这次又要删掉兼容的设置项呢,看来需要研究下,因为这是个问题,我还不懂。有知道的朋友也可以直接告诉我。

 

为了开始调试工作,我想向您提供一个工具列表。

以下工具是“Debugging Tools for Windows”的一部分–您肯定需要这些工具:

· windbg

· cdb

· ntsd

· tlist

· gflags

· adplus

· UMDH

· symcheck

Sysinternals提供了一些我们需要的优秀工具:

· Process Explorer

· Process Monitor

· Regmon

· Filemon

· DbgView

· Handle.exe

· Tcpview

· LiveKD

· AutoRuns

· WinObj

“MPS Reports”(MPSRPT_SETUPPerf.EXE)中包含许多工具,但我在这里专门列出Checksym

· Checksym

“WindowsServer2003ResourceKitTools”是另一个很棒的工具集。

桌面堆可能不是你花很多时间考虑的事情,这是件好事。但是,有时您可能会遇到由于桌面堆耗尽而导致的问题,然后了解此资源会有所帮助。让我先说一下,在Vista中,内核地址空间的情况发生了显著的变化,而我今天所说的大部分内容并不适用于Vista。

我想提供一些关于桌面堆的后续信息。在第一篇文章中,我没有讨论64位Windows、3GB或Vista上与桌面堆相关的内存范围的大小。所以,不用再多说了,下面是各种平台上的相关大小。

Windows XP (32-bit)

· 48 MB = SessionViewSize (default registry value, set for XP Professional, x86)

· 20 MB = SessionViewSize (if no registry value is defined)

· 3072 KB = Interactive desktop heap size (defined in the registry, SharedSection 2nd value)

· 512 KB = Non-interactive desktop heap size (defined in the registry, SharedSection 3nd value)

· 128 KB = Winlogon desktop heap size

· 64 KB = Disconnect desktop heap size

Windows Server 2003 (32-bit)

· 48 MB = SessionViewSize (default registry value)

· 20 MB = SessionViewSize (if no registry value is defined; this is the default for Terminal Servers)

· 3072 KB = Interactive desktop heap size (defined in the registry, SharedSection 2nd value)

· 512 KB = Non-interactive desktop heap size (defined in the registry, SharedSection 3nd value)

· 128 KB = Winlogon desktop heap size

· 64 KB = Disconnect desktop heap size

Windows Server 2003 booted with 3GB (32-bit)

· 20 MB = SessionViewSize (registry value has no effect)

· 3072 KB = Interactive desktop heap size (defined in the registry, SharedSection 2nd value)

· 512 KB = Non-interactive desktop heap size (defined in the registry, SharedSection 3nd value)

· 128 KB = Winlogon desktop heap size

· 64 KB = Disconnect desktop heap size

在运行3GB时,您还可能看到堆大小减小。在初始化窗口管理器期间,尝试保留足够的会话视图空间,以容纳给定会话的预期桌面堆数。如果在SharedSection注册表值中指定的堆大小已增大,则保留会话视图空间的尝试可能会失败。当这种情况发生时,窗口管理器会回到桌面堆的一对“安全”大小(交互式为512KB,非交互式为128KB),并再次尝试保留会话空间,使用这些较小的数字。这可以确保即使注册表值对于20MB会话视图空间来说太大,系统仍然能够引导。

Windows Server 2003 (64-bit)

· 104 MB = SessionViewSize (if no registry value is defined; which is the default)

· 20 MB = Interactive desktop heap size (defined in the registry, SharedSection 2nd value)

· 768 KB = Non-interactive desktop heap size (defined in the registry, SharedSection 3nd value)

· 192 KB = Winlogon desktop heap size

· 96 KB = Disconnect desktop heap size

Windows Vista RTM (32-bit)

· Session View space is now a dynamic kernel address range. The SessionViewSize registry value is no longer used.

· 3072 KB = Interactive desktop heap size (defined in the registry, SharedSection 2nd value)

· 512 KB = Non-interactive desktop heap size (defined in the registry, SharedSection 3nd value)

· 128 KB = Winlogon desktop heap size

· 64 KB = Disconnect desktop heap size

Windows Vista (64-bit) and Windows Server 2008 (64-bit)

· Session View space is now a dynamic kernel address range. The SessionViewSize registry value is no longer used.

· 20 MB = Interactive desktop heap size (defined in the registry, SharedSection 2nd value)

· 768 KB = Non-interactive desktop heap size (defined in the registry, SharedSection 3nd value)

· 192 KB = Winlogon desktop heap size

· 96 KB = Disconnect desktop heap size

 

Windows Vista SP1 (32-bit) and Windows Server 2008 (32-bit)

· Session View space is now a dynamic kernel address range. The SessionViewSize registry value is no longer used.

· 12288 KB = Interactive desktop heap size (defined in the registry, SharedSection 2nd value)

· 512 KB = Non-interactive desktop heap size (defined in the registry, SharedSection 3nd value)

· 128 KB = Winlogon desktop heap size

· 64 KB = Disconnect desktop heap size

windowsvista引入了一个新的公共API函数:CreateDesktopEx,它允许调用者指定桌面堆的大小。
此外,GetUserObjectInformation现在包含一个新的标志,用于检索桌面堆大小(UOI_HEAPSIZE)。