【发布时间】:2015-05-19 13:39:59
【问题描述】:
我正在为 NoSQL 数据库编写 C# SDK,为了处理请求(等序列化或反序列化、签名),我必须创建很多(小)对象。
我在一个简单的程序中测试了这个新 SDK 的性能,该程序创建了指定数量的线程并在每个线程中循环调用 API 作为播放负载。使用 1 个线程,QPS 达到 6K+,但随着线程数的增加,整体 QPS 下降而不是成倍增加。
为了找出原因,我简化了我的测试程序并将有效负载简化为非常简单的代码,而不是实际调用我的 SDK 接口:
for (int i = 0; i < 100000; i ++) {
double a = Math.Pow(3.14, 0.5);
}
性能结果还算OK:
1 线程 132 QPS
2 线程 261 QPS
4 线程 1028 QPS
8 线程 1826 QPS
但是当我把payload改成:
for (int i = 0; i < 100000; i ++) {
var c = new string('X', 50);
}
表现如下:
1 线程 300 QPS
2 线程 497 QPS
4 线程 596 QPS
8 线程 518 QPS
如果我在有效负载中创建一些其他对象,结果将是相同的(不是线性的)。
(以上两种情况都没有达到CPU和内存限制)
为什么?而且我的 SDK 会自然地创建一个对象,所以有办法解决吗?
我的一些猜测:
- .NET 框架的内存分配存在性能瓶颈,因此当您并行创建对象时,您会受到影响。
- .NET 框架的 GC 开始工作并显着降低了性能。
【问题讨论】:
-
线程并不便宜。你是如何创建这些新线程的?你在利用异步 IO 吗?
-
@Tejas 在这两种情况下,我没有做任何涉及异步的事情。在主线程中,我创建了 1 - 8 个线程来循环调用我的 SDK 接口并等待它们完成。
-
一般而言,如果您要进行持续的高分配,您会调查对象池和重用等工具 - 明智且适当地完成 (不太矫枉过正),你可以削减大量的分配和集合 - 但你不想走得太远,因为这样做也会人为地分割内存空间。但可以肯定:分配不是免费,即使它们是便宜
-
@MarcGravell 我知道分配不是免费的,但是在多线程场景中它们会变得更加昂贵吗?我的问题是如何通过线程线性提高性能。
-
@Haowei 直到你真正分析,这是一个猜谜游戏;是的,如果需要,分配可能是个问题,but there are ways of addressing that。正如我所提到的,池化是另一种重要的方法。我觉得你提到 protobuf 很有趣,因为这也是我涉足的领域 - 并且 protobuf-net 在内部维护微池,用于诸如工作缓冲区之类的事情。
标签: c# multithreading performance