分配内存时的线程争用



在C#中,我运行一个创建许多小对象的玩具代码(我知道理想情况下应该避免 - 我只想研究这个问题(。对于创建的对象总数相同,一个线程的运行速度比每个处理器一个线程 (Parallel.For( 快。

原子操作包括创建一个包含 20k 个小对象的列表(实际上是一个数组((为简单起见,此处为 long[4](:

private static void CreateList()
{
long[][] list = new long[20000][];
for (var i = 0; i < 20000; i++)
list[i] = new long[4];
}

如果我在单个线程中创建 1000 个列表,它会在 1.5 秒内运行。如果我创建 1000 个具有多个线程的列表(每个线程负责 1000 个列表的一个子集(,它会在 2 秒内运行。

在以下情况下,行为基本相同:

  • 使用经典的小物体代替长[4]
  • 使用实数列表而不是数组
  • 使用不同数量的对象

你能解释一下为什么吗?内存管理器中是否有"锁"。它与垃圾回收有关吗?

代码的详细信息:

public static void Main()
{
Benchmark(1000, CreateList);
}    
private static void Benchmark(int repeat, Action action)
{
Console.WriteLine("Single thread");
Benchmark(delegate ()
{
for (int i = 0; i < repeat; i++)
action();
});
Console.WriteLine("Multi thread");
Benchmark(delegate ()
{
Parallel.For(0, repeat, i => action());
});
}
private static void Benchmark(Action action)
{
for (int i = 0; i < 10; i++)
{
Stopwatch sw = new Stopwatch();
sw.Start();
action();
sw.Stop();
Console.WriteLine("Time : " + sw.Elapsed.TotalSeconds);
}
}

尽管内存管理器使用某种信号量是正常的,但具有许多内存分配的多线程应用程序在默认 C# 垃圾回收器下工作得非常糟糕。有了适当的垃圾收集器,情况会好得多。

您应该:

  • 启用服务器 GC
  • (可能(禁用并发 GC

服务器 GC 将允许线程之间更好程度的并行化,因为内存分配是部分独立的。在这种情况下,具有多个内核的计算机上的性能可能会发生根本性的变化。

简而言之,将其添加到项目的配置文件中:

<runtime>
<gcServer enabled="true"/>
<gcConcurrent enabled="false" />
</runtime>

您可以在垃圾回收基础知识中阅读有关服务器和工作站 GC 的详细信息。

最新更新