2019独角兽企业重金招聘Python工程师标准
.Net 调式案例—实验4 高CPU High CPU 回顾
现在开始第四个实验。
前面的案例和设置指导
请参看前面发布的文章。
再现问题
1 重新启动IIS iisreset
2 浏览http://localhost/BuggyBits/ AllProducts.aspx 这大概会花费5秒或更多的时间 这取决于你的机器性能。
3 打开页面的过程中 看看任务管理器中的cpu使用量。
Q 在这个请求的处理过程中 cpu的使用量是怎么样的
A 很高 接近100%。
设置性能计数器日志
1 打开一个性能计数器 从开始/运行 中输入 perfmon.exe。
2 右键在点击“性能日志和警告/日志计数器”上点击“新建日志设置” 在弹出的对话框中输入 Lab3-Mem。
3 在接下来弹出的对话框中点击“添加对象…”。
4 添加对象“.NET CLR Memory”和“Process” 点击关闭。
5 把时间间隔设置成1秒 因为我们这个例子运行的时间比较短。
6 修改运行方式 把“ 默认 ”修改成“domain\\username”(把默认这个东西删除掉 把你机器的用户名输入进去) 然后点击设置密码 把你的用户名对应的密码输入进去。这样做的目的是你要以管理员 administrator 或具有对w3wp.exe 调试权限的用户来运行日志计数器。否则将得不到.net 计数器的值。
7 点击确认 这个将会启动性能监视器对进程的监控。
准备收集内存dump文件
1 打开一个命令窗口 切换到调试器目录 输入如下的命令 但不要敲回车
adplus -hang -pn w3wp.exe -quiet
再现问题并收集dump文件
1 在命令提示符下 切换到TinyGet目录下 输入命令 启动5个线程 每次1个请求。
tinyget -srv:localhost -uri:/BuggyBits/AllProducts.aspx -threads:5 -loop:1
2 当cpu很高的时候 在adplus 的命令中 敲下回车键。
3 停止性能监视器。
打开dump文件并看看里面正在做什么
步骤和前面实验中讲的一样 这里略过。
检验一下你是在恰当的时候获取了这个dump文件
1 运行 !threadpool 来看看cpu的使用量 确信你在恰当的时候抓取到了这个dump文件。
Q cpu的使用率是多少
A 根据命令的输出 使用率是98%。
0:027 !threadpool
CPU utilization 98%
Worker Thread: Total: 5 Running: 5 Idle: 0 MaxLimit: 200 MinLimit: 2
Work Request in Queue: 4
AsyncTimerCallbackCompletion TimerInfo 001c9990
…..
--------------------------------------
Number of Timers: 7
--------------------------------------
Completion Port Thread:Total: 1 Free: 1 MaxFree: 4 CurrentLimit: 0 MaxLimit: 200 MinLimit: 2
Q 根据进程的cpu使用率的关系 这里的cpu使用率是怎么显示的
A 这是一个有点矛盾的问题 cpu的使用率显示的是在该系统上这个进程使用了多少的cpu 或其他某个进程使用了多少cpu。通常的 在一个专用的web服务器上 你可以使用这个来作为一个参考来指示cpu占用率是不是很高 但最好的方法还是查看性能技术器的日志。
Q 如果你运行tinyget 你可能会看到一些请求队列 例如 Work Request in Queue: 4 为什么请求会排成队列呢 为什么他们没有按计划的来执行 和Running worker threads 比较 还有 worker threads 的最大限制
A 在这里例子中 我其实运行了tinyget 两次 我有5个工作线程 worker threads 多有的请求 尽管他们比最大工作线程要小很多 100 线程/1处理器 2处理器 200线程 当cpu使用率超过80%的时候没有新的线程被启动。所以在这以后的导致了100%的请求进来后会被放到等待队列中 等待新的线程被启动或其他线程空闲。
查找是那些线程消耗了大部分的cpu资源
1 运行 .time 来看看uptime 和进程的 cpu user time。
0:027 .time
Debug session time: Fri Feb 22 12:26:55.000 2008 (GMT 1)
System Uptime: 8 days 9:17:00.157
Process Uptime: 0 days 0:01:07.000
Kernel time: 0 days 0:00:06.000
User time: 0 days 0:00:45.000
2 运行 !runaway 来看看所有线程的 usertime。
0:027 !runaway
User Mode Time
Thread Time
18:704 0 days 0:00:17.843
19:9f4 0 days 0:00:13.328
16:1948 0 days 0:00:10.718
26:a7c0 days 0:00:01.375
24:114 0 days 0:00:01.093
27:d54 0 days 0:00:00.390
28:1b70 0 days 0:00:00.328
0:b7c0 days 0:00:00.171
25:3f8 0 days 0:00:00.000
…..
Q 从输出中 看看是哪些线程消耗了大部分的cpu
A 看看线程 18 19 和16 号 他们总共消耗了大约42秒的时间
Q 被进程消耗的总的cpu时间是多少 从 .time 来看
A 从 .time 命令的输出看出 进城消耗了大约 1分钟 7秒的时间 45秒消耗在了用户模式下 这个时间是两个cpu的总的有效时间。
注意 看看 !runaway 的输出是有点滑稽的 有下面几个理由
首先 在一个多cpu或多核的cpu的机器上 你必须记住 用户模式时间 usermode time 花在用户模式下的时钟周期 是所有处理器上的cpu的时间的总和 因此 把用户模式下的线程时间加起来的总和会比 .time 中看到的流逝的时间还要大。
其次 !runaway输出的是线程启动后就开始计时的用户模式时间。在一个asp.net环境中 一个线程会被多个请求拿来重用 所以在一个线程上一个很高的用户模式的时间并不意味着一定是运行于该线程上的请求导致了高cpu的问题。
最后 在一些线程中 比如GC线程 在多处理器中 serverGC 进程 会在正在的进程周期中保持存在 所以它们有更高的可能性相对其他工作线程来说 占用了更多的用户模式时间。所以查看两个连续的dump文件 比较用户模式里面的线程的不同是更加有意义的。
3 选择一个有高cpu占用时间的线程 看看它的调用堆栈。
~#s (切换线程 请把# 换成 !runaway 中实际的线程号)
kb 2000 (查看本地 原生 调用堆栈)
!clrstack (查看 dotnet 调用堆栈)
0:018 kb
ChildEBP RetAddr Args to Child
0290fd90 7d4d8f0e ffffffff 0290fde8 00000000 ntdll!ZwAllocateVirtualMemory 0x12
0290fddc 7d4d8f51 ffffffff 0aec1000 0030d000 kernel32!VirtualAllocEx 0x41
0290fdf8 79e7495b 0aec1000 0030d000 00080000 kernel32!VirtualAlloc 0x18
0290fe38 79e74929 0aec1000 0030d000 00080000 mscorwks!EEVirtualAlloc 0x104
0290fe50 79e7499b7a3b45f0 0aec1000 0030d000
mscorwks!CExecutionEngine::ClrVirtualAlloc 0x14
0290fe6c79fbdce8 0aec1000 0030d000 00080000 mscorwks!ClrVirtualAlloc 0x1a
0290fe88 79fc2efa 0aec0b18 0030d6b0 0aec0b18 mscorwks!SVR::reset_memory 0x3e
0290fea4 79fc2eb6 0aec0b18 0030d6b0 00000001
mscorwks!SVR::gc_heap::make_unused_array 0x1b
0290fec4 79fc3d1d 0aec0b18 0030d6b0001a8970 mscorwks!SVR::gc_heap::thread_gap 0x30
0290fef0 79fc3c15 00000000001a8800 00000002
mscorwks!SVR::gc_heap::sweep_large_objects 0xd4
0290ff28 79efa9ba 00000002 00000000001a8800
mscorwks!SVR::gc_heap::mark_phase 0x3c5
0290ff54 79efaf60 ffffffff 00000000001a8800 mscorwks!SVR::gc_heap::gc1 0x46
0290ff74 79efa72f00000000 00000000001a8800
mscorwks!SVR::gc_heap::garbage_collect 0x246
0290ff98 79fc8583 79fc855979f95b5c79fc857c
mscorwks!SVR::gc_heap::gc_thread_function 0x6a
0290ffb8 7d4dfe21001a8800 00000000 00000000
mscorwks!SVR::gc_heap::gc_thread_stub 0x92
0290ffec 00000000 79fc8538001a8800 00000000 kernel32!BaseThreadStart 0x34
0:016 kb
ChildEBP RetAddr Args to Child
0252f084 7d4d8c82 00000308 00000000 00000000 ntdll!ZwWaitForSingleObject 0x15
0252f0f4 79e789c6 00000308 ffffffff 00000000 kernel32!WaitForSingleObjectEx 0xac
0252f138 79e7898f00000308 ffffffff 00000000 mscorwks!PEImage::LoadImage 0x1af
0252f188 79e78944 ffffffff 00000000 00000000 mscorwks!CLREvent::WaitEx 0x117
0252f19c 79fbc82affffffff 00000000 00000000 mscorwks!CLREvent::Wait 0x17
0252f1b0 79fbc6da 00000000 58ec767e 00000000
mscorwks!SVR::GCHeap::WaitUntilGCComplete 0x34
0252f1ec 79fc2945 58ec75d2 00032cff0000909f
mscorwks!Thread::RareDisablePreemptiveGC 0x1a0
0252f240 79fc2b07 00000000 0b376f34 7936d6e7
mscorwks!Thread::RedirectedHandledJITCase 0x13d
0252f2cc66f1298006f00f7006f53d34 6628efd2
mscorwks!Thread::RedirectedHandledJITCaseForGCThreadControl 0x7
0252f2d8 6628efd206f00f70 02ef3cec06f53d24
System_Web_RegularExpressions_ni 0x12980
0252f2ec 6613cb0406f00f70 02ef3cec 02eee62cSystem_Web_ni 0x36efd2
0252f300 6613cb500252f50c02ef3cec 02ef3cec System_Web_ni 0x21cb04
0252f4e86614c717 02ef11ac00000000 02ee35fc System_Web_ni 0x21cb50
0252f50c 6614d8c3 00000001 02eee62c00000000 System_Web_ni 0x22c717
0252f544 6614d80f00000001 06eb01e4 02eb446cSystem_Web_ni 0x22d8c3
0252f580 6614d72f02ef3cec 6614d6c2 00000000 System_Web_ni 0x22d80f
0252f5a0 65fe6bfb 660ee55406f0a36c02eee050 System_Web_ni 0x22d72f
0252f5d4 65fe3f51 00000000 0000006e 00000000 System_Web_ni 0xc6bfb
0252f610 65fe77330252f63806f00110 02eee35cSystem_Web_ni 0xc3f51
0252f664 65fccbfe0252f6b4 02ef0f6c06f00fe8 System_Web_ni 0xc7733
Q 它们在干什么 你看这这些 能假设是什么导致了gaocpu么
A 18 和 19 号 是两个GC 线程 gc_heap::gc_thread_stub 16号是运行托管代码的 但 当前正在等待GC完成 GCHeap::WaitUntilGCComplete 所以在这点上 16号是空闲的 idle 所以现在看来大部分的cpu是消耗在了GC线程上了。
4 这个仅适用在多处理器的机器上 因为他们有专用的GC线程 把 !runaway 中的用户模式下的GC 线程的时间加起来 除以 .time 中的用户模式的时间。
Q 在用户模式的时间中 GC线程花费了多少的时间
A 18 和19 号 线程使用了超过21秒的cpu时间 大约50%的cpu总时间 那是在垃圾收集中花费了非常多的时间。
检查性能监视器的日志
1 打开性能监视器 添加技术器 .NET CLR Memory/% Time in GC, # Gen 0 Collections, #Gen 1 Collections, #Gen 2 Collections 和 Large Object Heap Size。
Q % Time in GC 这个技术器是衡量什么指标的
A 这个计数器是 表示自从最后一个GC回收开始 花费在垃圾收集上的时间占所流逝的时间的百分比。这个计数器通常是表示应用程序回收合并内存过程中垃圾收集器所作工作的一个效能指示。在每个GC运行结束后 这个计数器会更新 计数器的值代表最后一次观察到的值 它不是平均值。换句话说 在Gen 2 收集之后 曲线肯定会有一个跃起 关键是这里 这个GC的时间比较平均起来没有在很高的地方结束。
Q 在压力测试中 % Time in GC 的平均值是多少
A 在我这里大概是50%-- 60% 这个在很大程度上取决于你是单处理器还是多处理器 因为 server GC 和workstation GC 是根据这个不同而进行不同优化的。运行在多处理器上的asp.net 服务中的 Server GC 会做更多完全的收集 重新分配和合并内存块 workstation版的会在高内存消耗的时候省略掉一些做法。
Q % Time in GC的合适的值是多少
A 这是一个很滑稽的问题 有些人说5 有些说是30 但我觉得在大多数的asp.net应用中5%是很难达到的 特别是这种高内存使用量的来说。然而 基于经验 我认为 如果在一个进程中30%的cpu使用被用来做垃圾收集 那是多么愚蠢的。虽然这样说 但是低的% Time in G也是非常狡猾的 因为它会改变应用的内存分配方式的改变。所以说任何的优化 尽量做到把它调到合适的水平 不要做的过度。
Q 在Gen 0, Gen 1 和 Gen 2 collections 之间的比率是多少 什么样的情况是可以接受的 为什么
A Gen 0 的收集基本上是不需要消耗什么的 免费的。Gen 1 的收集 包含Gen 0 的收集 也是非常廉价的 因为我们仍然使用着很少数量的要分配的或回收的内存等。Gen 2 的收集从另一方面来说将是非常非常地昂贵的 因为它处理的是整个dotnet 的GC 堆。最理想的比率是 每100次Gen0 的收集 10次的Gen 1 收集 1次的Gen 2 的收集 即 100 10 1 在这里例子中是
5853 Gen 0 Collections
5830 Gen 1 Collections
5430 Gen 2 Collections
这些值说明了 在非常多次的每一次的收集行为中 Gen 2 发生了很多次 因为这个页触发 Gen 0 和Gen 1 的收集 。这是非常坏的 因为我们并没有使用到GC给我们带来的好处 这不是一个一般意义上的GC 而是它总是不停的要寻找整个内存 释放回收……。
Q 是什么东西会导致象上面这样的比率出现
A 通常情况下 两种事情导致这样的比率。一个是非常频繁的分配大对象 以致于每次LOH的段都用完了 二是你经常的直接调用GC.Collect方法 或者GC.GetTotalMemory(true) 这样的方法。如果有人手动的调用了这些方法 那.NET CLR Memory/# Induced GC 这个计数器就会上升 但在这里 你可以看见它保持平稳 这就是说 我们可能我们在一直不停的申请大对象。
查看dump文件 找出在GC中是什么导致了高cpu
1 运行 ~* kb 2000 得到所有的本地调用堆栈 寻找线程中触发GC的函数 mscorwks!SVR::GCHeap::GarbageCollectGeneration
27 Id: bcc.d54 Suspend: 1 Teb: fff06000 Unfrozen
ChildEBP RetAddr Args to Child
103eed84 7d4d8c82 00000314 00000000 00000000 ntdll!ZwWaitForSingleObject 0x15
103eedf4 79e789c6 00000314 ffffffff 00000000 kernel32!WaitForSingleObjectEx 0xac
103eee38 79e7898f00000314 ffffffff 00000000 mscorwks!PEImage::LoadImage 0x1af
103eee88 79e78944 ffffffff 00000000 00000000 mscorwks!CLREvent::WaitEx 0x117
103eee9c79efafc5 ffffffff 00000000 00000000 mscorwks!CLREvent::Wait 0x17
103eeec0 79efad3d ffffffff 001ab0e0 001ab188 mscorwks!SVR::gc_heap::wait_for_gc_done 0x62
103eeee8 79efa339 00000000 00000000 001ab0e0 mscorwks!SVR::GCHeap::GarbageCollectGeneration 0x1b5
103eef78 79ef98cf 103eefb4 00047470 00000003 mscorwks!SVR::gc_heap::try_allocate_more_space 0x136
103eef9479f20ee4 103eefb4 00047470 00000003 mscorwks!SVR::gc_heap::allocate_more_space 0x2e
103eefd879f3d20e 00047470 00000000 00000000 mscorwks!SVR::gc_heap::allocate_large_object 0x5a
103eeff4 79e7510e0f468840 00047470 00000000 mscorwks!SVR::GCHeap::Alloc 0x8d
103ef010 79e86713 00047470 00000000 00000000 mscorwks!Alloc 0x60
103ef04c79e865b900023a304a807762 0773cd58 mscorwks!SlowAllocateString 0x29
103ef0f0 79378b7d 0773cd5800023a2f00000008 mscorwks!FramedAllocateString 0xa0
103ef104 79378af4 103ef14c0d36b268 0773cd1cmscorlib_ni 0x2b8b7d
103ef12c7a4a88d6 00000000 031df118 031deef0 mscorlib_ni 0x2b8af4
103ef14c66f1298006f00098 031deaec 6628efd2 System_ni 0x688d6
103ef38c6614d8c3 00000001 03198328 00000000 System_Web_RegularExpressions_ni 0x12980
103ef3c4 6614d80f00000001 06eb01e4 02eb446cSystem_Web_ni 0x22d8c3
103ef400 6614d72f031ddfd8 6614d6c2 00000000 System_Web_ni 0x22d80f
0:016 !clrstack
OS Thread Id: 0x1948 (16)
ESP EIP
0252f208 7d61c828 [RedirectedThreadFrame:0252f208]
0252f24c 79363058 System.String.wstrcpy(Char*, Char*, Int32)
0252f258 7936d6e7 System.String.FillStringChecked(System.String, Int32, System.String)
0252f278 79378b9fSystem.String.ConcatArray(System.String[], Int32)
0252f28c 79378af4 System.String.Concat(System.Object[])
0252f2a0 0fe90a2aAllProducts.Page_Load(System.Object, System.EventArgs)
0252f2d866f12980 System.Web.Util.CalliHelper.EventArgFunctionCaller(IntPtr, System.Object, System.Object, System.EventArgs)
0252f2e8 6628efd2 System.Web.Util.CalliEventHandlerDelegateProxy.Callback(System.Object, System.EventArgs)
0252f2f8 6613cb04 System.Web.UI.Control.OnLoad(System.EventArgs)
0252f308 6613cb50 System.Web.UI.Control.LoadRecursive()
0252f31c 6614e12d System.Web.UI.Page.ProcessRequestMain(Boolean, Boolean)
0252f518 6614d8c3 System.Web.UI.Page.ProcessRequest(Boolean, Boolean)
0252f550 6614d80fSystem.Web.UI.Page.ProcessRequest()
0252f588 6614d72fSystem.Web.UI.Page.ProcessRequestWithNoAssert(System.Web.HttpContext)
0252f590 6614d6c2 System.Web.UI.Page.ProcessRequest(System.Web.HttpContext)
0252f5a4 0fe90205 ASP.allproducts_aspx.ProcessRequest(System.Web.HttpContext)
0252f5a8 65fe6bfb System.Web.HttpApplication CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
0252f5dc 65fe3f51 System.Web.HttpApplication.ExecuteStep(IExecutionStep, Boolean ByRef)
0252f61c 65fe7733 System.Web.HttpApplication ApplicationStepManager.ResumeSteps(System.Exception)
0252f66c 65fccbfe System.Web.HttpApplication.System.Web.IHttpAsyncHandler.BeginProcessRequest(System.Web.HttpContext, System.AsyncCallback, System.Object)
0252f688 65fd19c5 System.Web.HttpRuntime.ProcessRequestInternal(System.Web.HttpWorkerRequest)
0252f6bc 65fd16b2 System.Web.HttpRuntime.ProcessRequestNoDemand(System.Web.HttpWorkerRequest)
0252f6c8 65fcfa6d System.Web.Hosting.ISAPIRuntime.ProcessRequest(IntPtr, Int32)
0252f8d879f047fd [ContextTransitionFrame:0252f8d8]
0252f90c 79f047fd [GCFrame:0252f90c]
0252fa6879f047fd [ComMethodFrame: 0252fa68]
Q 为什么GC 会被触发
A 我们尝试去分配一个大对象 gc_heap::allocate_large_object 为了达到目的 我们需要更多的空间 那就触发了垃圾收集。那个要分配的对象大小是0x47470 291952 bytes 那就是位于LOH结尾处的东西。
Q 它正在分配的是什么样类型的对象
A 基于托管栈 它看起来象是一个 char[] 或是一个string 因为我们包含strings 它在内部做wstrcpy。
Q 线程正在干什么
A 它正在处理一个AllProducts.aspx页面的请求 调用了Page_Load 函数 里面有字符串连接。
Q 是这个导致了 Gen 0 Gen1 和Gen2 的比率不正常么 为什么
A 包含大的字符串会导致高的cpu 这是众所周知的。关于原因 我会再写一篇文章。这里不深入讨论。如果你在循环中做 str str1 str2 你会创建一个新的对象 而不是把原来的对象扩大 如果不停的循环 strings会变大 你就不要不停的创建新的strings 那么一个就比一个大 这就导致LOH段被频繁的扩大 最终结果导致很多GC的发生和高cpu。
2 找出LOH上面有什么东西 如果你不幸 你抓到的GG正在计划或重新分配 plan or relocate 阶段 那 !dumpheap 的输出就会步正确。如果那样的话 你要!dumpheap -min 85000。
0:016 !dumpheap -min 85000
The garbage collector data structures are not in a valid state for traversal.
It is either in the plan phase, where objects are being moved around, or
we are at the initialization or shutdown of the gc heap. Commands related to
displaying, finding or traversing objects as well as gc heap segments may not
work properly. !dumpheap and !verifyheap may incorrectly complain of heap
consistency errors.
------------------------------
Heap 0
AddressMTSize
03481a14001a86d8 136876 Free
0aec0b18001a86d8 3200688 Free
0b1ce1c8 790fd8c4 246832
0b20a608 790fd8c4 246992
0b246ad8 790fd8c4 462032
0b2b77a8001a86d8 784256 Free
0b376f28 790fd8c4 416272
0b3dc938001a86d8 336960 Free
0b42ed78 7912dae8 1048592
0b52ed88 790fd8c4 416432
total 10 objects
------------------------------
Heap 1
AddressMTSize
object 06eb0038: does not have valid MT
curr_object : 06eb0038
----------------
0ceb0048001a86d8 1180896 Free
0cfd0528 790fd8c4 590672
0d060878001a86d8 3189232 Free
0d36b268 790fd8c4 291792
total 4 objects
------------------------------
total 14 objects
Statistics:
MT Count TotalSize Class Name
7912dae8 1 1048592 System.Byte[]
790fd8c4 7 2671024 System.String
001a86d8 6 8828908 Free
Total 14 objects
Q LOH上面有什么
A 在这里 因为我们在GC的里面 所以我们不能真的横贯GG 堆 这就是说 这个命令输出中没有把所有的大对象显示给我们 但根据我们的理论 始终如一的主要的是Byte[] 和Strings 它们包含字符串连接。
Q 在字符串的大小里面是不是有什么规律
A 仅仅在这里可以做为一个证据 并不是在所有的案例中都可以。这里有一对stirngs string 246832 bytes and 246992 bytes 和另外一对strings stings 416272 and 416432 bytes 它们的大小相差160Bytes。如果你恰好得到一个dump文件 堆已经停止运转 你会看到一个很长的strings 的列 list 。这些strings大家不断的以一定的数字增加 这个基本上是因为每个都不断地和那个数字长度的string连接。
2 把一些strings 输出来看看内容
0:016 !do 0b1ce1c8
Name: System.String
MethodTable: 790fd8c4
EEClass: 790fd824
Size: 246832(0x3c430) bytes
(C:\\WINDOWS\\assembly\\GAC_32\\mscorlib\\2.0.0.0__b77a5c561934e089\\mscorlib.dll)
String:
table tr td B Product ID /B /td td B Product Name /B /td td B Description /B /td /tr tr td 0 /td td Product 0 /td td Description for Product 0 /td
/tr tr td 1 /td td Product 1 /td td Description for Product 1 /td /tr tr td 2 /td td Product 2 /td td Description for Product 2 /td /tr tr td 3 /td
td Product 3 /td td Description for Product 3 /td /tr tr td 4 /td td Product 4 /td td Description for Product ...
Q 基于栈 垃圾收集的知识 和字符串的内容 你能不能找到哪里的代码有问题
A 前面提到过是Page_load 的里面有字符串相连接。导致了高cpu。这个代码看起来像是组织一个产品的表格。
检查代码 证实你的假设
1 打开AllProducts.aspx.cs 看看代码
protected void Page_Load(object sender, EventArgs e)
{
DataTable dt ((DataLayer)Application[ DataLayer ]).GetAllProducts();
string ProductsTable table tr td B Product ID /B /td td B Product Name /B /td td B Description /B /td /tr
foreach (DataRow dr in dt.Rows)
{
ProductsTable tr td dr[0] /td td dr[1] /td td dr[2] /td /tr
}
ProductsTable /table
tblProducts.Text ProductsTable;
}
Q 怎么解决 为什么这样能解决问题
A 使用StringBuilder 来附加数据 而不是使用字符串连接。因为string builder 有一个缓冲区 它可以放字符串 不用每次都创建新的对象 当它需要扩展缓冲区的时候 它会加倍的申请 随着100 1000的因子不同 而预分配缓冲区 所以应用程序需要在LOH分配新的内存的次数就会降低。如果你使用了stringBuilder你会发现cpu使用率就降下来了 CC调用的次数也会降下来 你会发现垃圾收集的次数就不是5000多次而是少于10次完全的收集。
本文链接: http://ccollectionsb2b.immuno-online.com/view-749207.html