• 公司某资料子系统定期cpu过高的诊断


    背景

    公司里的某负责保存用户文档的子系统有时会忽然cpu很高,过了大约5分钟后又恢复正常水平。领导协调让我帮看一下 (我心里是: 不熟悉这个子系统里面的代码,我尽力哈😓)

    其实确实是这样的,如果熟悉出问题的系统的代码,会对诊断问题起到很大的帮助,否则就需要更多的利用对底层的理解了。

    分析

    打听后知道了这个子系统用.net core写的,可以运行在windows和linux docker上,且这次的cpu高的问题,他们在windows运行也可复现。于是,我让他们在windows上运行,发现cpu高的时候dump一下。(然后windbg就可以准备下地干活了😖)

    在用windbg看了大部分threadpool worker线程的情况后,我发现大部分线程都运行到这样类似的call stack上:

    先大概看一下这个PeopleManagementService.Program+c__DisplayClass2_0.b__0(PeopleManagementService.User)在做什么,用IL反编译工具可以看到:

    再结合call stack,感觉上下文正在从某集合里查找东西呢?(一个题外话,上面那个c__DisplayClass2_0.b__0名字挺奇怪,其实这是C# compiler在build我们的C#代码到IL时帮着生成的一些code。对于目前这个,就对应我们C#程序员写出的lambda )

    找东西会把cpu明显提高么?去看看PrintUserLastEnteredTimeDetail()在干什么吧。(其实子系统team的同事人很好,所以这次我直接找他要对应的代码得👦简化代码:

     看起来是在一个叫users的List中按条件查找东西。看看这个list的size, 用!do试一下:

    复制代码
     1 0:027> !DumpObj /d 0000019a8000d938
     2 Name:        System.Collections.Generic.List`1[[PeopleManagementService.User, PeopleManagementService]]
     3 MethodTable: 00007ff96c313e88
     4 EEClass:     00007ff96c2df530
     5 Tracked Type: false
     6 Size:        32(0x20) bytes
     7 File:        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\6.0.14\System.Private.CoreLib.dll
     8 Fields:
     9               MT    Field   Offset                 Type VT     Attr            Value Name
    10 00007ff96c2fd618  4002099        8     System.__Canon[]  0 instance 0000019a90bc12a0 _items
    11 00007ff96c2a94b0  400209a       10         System.Int32  1 instance           524288 _size
    12 00007ff96c2a94b0  400209b       14         System.Int32  1 instance           524288 _version
    13 00007ff96c2fd618  400209c        8     System.__Canon[]  0   static dynamic statics NYI                 s_emptyArray
    复制代码
    size大约524288,500K的items在list里面,然后多个线程在一起进行这样的查找,所以cpu提高了。

    后记

    在告知同事这个原因前,我还有意无意地查了一下那个保存big list的Dictionary,用!gcroot:

     然后!do看一下:

    复制代码
    1 !do 0000019A8000D818
    2 Name:        System.Collections.Generic.Dictionary`2+Entry[[System.String, System.Private.CoreLib],[System.Collections.Generic.List`1[[PeopleManagementService.User, PeopleManagementService]], System.Private.CoreLib]][]
    3 MethodTable: 00007ff96c322808
    4 EEClass:     00007ff96c322770
    5 Tracked Type: false
    6 Size:        96(0x60) bytes
    7 Array:       Rank 1, Number of elements 3, Type VALUETYPE
    复制代码
    一个保存着那么大list的Dictionary, 其实只有<=3的key value pairs。再看看它的keys是啥。我们用windbg的memory view直接看内存中的"瓤"吧:

    64位的进程运行环境,clr array的第一个item在0000019A8000D818 + 8(mt ptr size) + 4(length field) + 4 (padding),所以看标红的那两个地址。

    复制代码
    1 0:027> !DumpObj /d 0000019a8000cce8
    2 Name:        System.String
    3 MethodTable: 00007ff96c2bd708
    4 EEClass:     00007ff96c299d60
    5 Tracked Type: false
    6 Size:        30(0x1e) bytes
    7 File:        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\6.0.14\System.Private.CoreLib.dll
    8 String:      Male
    复制代码
    复制代码
    1 0:027> !DumpObj /d 0000019a8000cd08
    2 Name:        System.String
    3 MethodTable: 00007ff96c2bd708
    4 EEClass:     00007ff96c299d60
    5 Tracked Type: false
    6 Size:        34(0x22) bytes
    7 File:        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\6.0.14\System.Private.CoreLib.dll
    8 String:      Female
    复制代码

    原来Dictionary按性别分类保存了所有的用户信息。

    我把这些信息告诉同事,他第一反应是没有想到这个运行的结果。按照他的代码,他预期是按性别和用户名一起来当key,然后放到Dictionary,这样查询起来至少时间复杂度不应这么高。于是我和那个同事打开相关的生成key的代码:

    1 private static string GetUserKey(Gender gender, string userName) => string.Join(userName, gender);
    大家发现没?想当然的用String.Join来连接2个object,却没注意其实String.Join的定义是这样,注意第一个参数可是separator啊:

    这么错误的使用,就相当于把user name作为separator,而后面的集合参数其实就传了一个性别,所以生成的key就总是同一个,本想做成user cache的Dictionary的数据最终退化为两个很大的lists😓 然后多个线程一起在这两个大大的list上以O(n)的时间复杂度查找东西,cpu就高起来了……

    总结

    看来大家不能这么随随便便的写C#代码啊,虽然.net sdk会为C#程序员提供便利和安全性,但自己也不该想当然的无脑coding哈。最后同事的脸比较红哈🙈
  • 相关阅读:
    Element UI 多选表格【翻页多选】全能版(含翻页多选数据反显、toggleRowSelection失效的原因解析和解决方案)
    静态&动态&文件通讯录
    Java面试题-Java核心基础-第四天(变量&方法)
    嘉立创 - 多层板常规层压结构
    Redis——zset类型详解
    OpenCV 中Mat.depth()的理解——每个像素的位数——每个像素中每个通道的精度
    qt 汉字输出 中文输出 显示乱码 qDebug() 乱码 解决
    React 如何导出excel
    mysql客户端navicat的一些错误合集
    Node.js
  • 原文地址:https://www.cnblogs.com/dotnet-diagnostic/p/17181455.html