分配内存时的线程争用

时间:2018-05-11 11:29:24

标签: c# multithreading memory-management

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

原子操作在于创建一个包含20k个小对象的列表(实际上是一个数组)(为简单起见,这里很长[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个列表的一个子集),它将以2s运行。

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

  • 使用经典的小物件而不是长[4]
  • 使用真正的List而不是数组
  • 使用不同数量的对象

你能解释一下原因吗?内存管理器中是否存在“锁定”。它与垃圾收集有关吗?

代码详情:

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);
    }
}

1 个答案:

答案 0 :(得分:1)

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

你应该:

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

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

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

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

您可以在Fundamentals of Garbage Collection中阅读有关服务器和工作站GC的详细信息。