【问题标题】:How to improve performance of an application which creates a lot objects with multi-threading?如何提高使用多线程创建大量对象的应用程序的性能?
【发布时间】: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 会自然地创建一个对象,所以有办法解决吗?

我的一些猜测:

  1. .NET 框架的内存分配存在性能瓶颈,因此当您并行创建对象时,您会受到影响。
  2. .NET 框架的 GC 开始工作并显着降低了性能。

【问题讨论】:

  • 线程并不便宜。你是如何创建这些新线程的?你在利用异步 IO 吗?
  • @Tejas 在这两种情况下,我没有做任何涉及异步的事情。在主线程中,我创建了 1 - 8 个线程来循环调用我的 SDK 接口并等待它们完成。
  • 一般而言,如果您要进行持续的高分配,您会调查对象池和重用等工具 - 明智且适当地完成 (不太矫枉过正),你可以削减大量的分配和集合 - 但你不想走得太远,因为这样做也会人为地分割内存空间。但可以肯定:分配不是免费,即使它们是便宜
  • @MarcGravell 我知道分配不是免费的,但是在多线程场景中它们会变得更加昂贵吗?我的问题是如何通过线程线性提高性能。
  • @Haowei 直到你真正分析,这是一个猜谜游戏;是的,如果需要,分配可能是个问题,but there are ways of addressing that。正如我所提到的,池化是另一种重要的方法。我觉得你提到 protobuf 很有趣,因为这也是我涉足的领域 - 并且 protobuf-net 在内部维护微池,用于诸如工作缓冲区之类的事情。

标签: c# multithreading performance


【解决方案1】:

多线程编程的主要规则是您创建的线程数不应超过CPU cores + 1。这是因为你创建了许多线程,它们都试图同时完成它们的工作,而你的 CPU 和操作系统通过context switching 降低了性能。这是一个非常耗时的操作 - 最好在同一个线程中完成两件工作,而不是由两个线程完成一件工作。

有很多技术可以提高您的应用程序的性能,例如,work stealing,但您应该自己研究一下,因为这是一个非常广泛的主题。

【讨论】:

  • 嗯,我的电脑有 24 个 CPU 核心,我最多只能启动 8 个线程。所以这不是我的情况。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-08-25
  • 2011-08-02
相关资源
最近更新 更多