为什么多线程会在这里表现更差?

时间:2013-08-08 00:33:25

标签: c# multithreading

我通过比特数设置枚举每秒都为假。

现在我想通过将其分成两个线程来加快速度......但是出于一些奇怪的原因,每个线程执行 HALF 工作量的时间需要64% 更多时间,我想知道为什么会这样?

这可能是由于某种CPU缓存效应吗?我该怎么做呢?

我以前用lambda表达式尝试了8个线程,但总是大约1400毫秒,但是在单线程中我常常得到850毫秒。此外,当我让一个线程完成所有工作时,我花了830毫秒。我只是不明白,有人知道原因吗?

代码:

    class Program
    {
        static int count = 0x10000000;
        static int half = count / 2;
        static BitArray bitArray = new BitArray(count);

        static unsafe void Main(string[] args)
        {
            Stopwatch sw = Stopwatch.StartNew();

#if SINGLE
            for (int i = 0; i < bitArray.Count; i += 2)
                bitArray.Set(i, true);
#else
            Thread thread1 = new Thread(Thread1);
            Thread thread2 = new Thread(Thread2);
            thread1.Start();
            thread2.Start();
            thread1.Join();
            thread2.Join();
#endif
            sw.Stop();

            Console.WriteLine(sw.ElapsedMilliseconds);
            Console.ReadLine();
        }

        static void Thread1()
        {
            Stopwatch sw = Stopwatch.StartNew();
            for (int i = 0; i < half; i += 2)
                bitArray.Set(i, true);
            sw.Stop();
            Console.WriteLine("Thread1: {0}", sw.ElapsedMilliseconds);
        }

        static void Thread2()
        {
            Stopwatch sw = Stopwatch.StartNew();
            for (int i = half; i < count; i += 2)
                bitArray.Set(i, true);
            sw.Stop();
            Console.WriteLine("Thread2: {0}", sw.ElapsedMilliseconds);
        }
    }

3 个答案:

答案 0 :(得分:9)

BitArray不是线程安全的类。你不应该这样使用它。事实上,除了正确性之外,这很可能是缓慢的原因。原因如下:

如果查看BitArray的源代码,它会包含一个int version字段,该字段会在每次操作时更新,特别是Set(),您可以调用它。

这意味着每个线程不断更新相同的内存位置,这是巨大的性能杀手,因为所有内核在访问此位置时都必须进行通信和同步。在这种情况下,多线程解决方案的性能比单核解决方案更差。

答案 1 :(得分:2)

因为线程并不像看起来那么容易。

首先,正如documentation所述,BitArrays不是线程安全的。这意味着当多个线程同时使用时,它们可能并且将会出现不可预测的行为。

至于性能损失,可能是由于你的两个线程导致的总线流量增加,试图同时修改相同的内存位置,不断地使彼此的缓存无效。

即使您认为您的线程没有修改相同的位置,也可能不是这样。例如,BitArray有一个Count属性。每次将一个位设置为1时,线程很可能会修改一个计数器变量,即支持Count属性。由于race conditions和过时值,这种并发修改是危险的,并且可能会增加总线流量,如前所述。

问题是CPU内核工作在2-3GHz,而RAM和内存总线工作在1Ghz。 ram速度慢得多,RAM访问需要大约100个CPU周期。如果你打破了CPU的缓存机制,很明显性能会下降。

编辑:更不用说线程创建和加入涉及很大的开销。如果你的工作是830ms一次性。您不太可能通过多线程获得重大改进。您还应该尝试去掉线程中的Stopwatches,因为它们也是开销。

答案 2 :(得分:1)

我修改了您的代码,以便测试运行10次并报告结果。使用您的代码,我看到单线程与多线程测试的相似时序(每个线程大约需要1200毫秒)。

但是,正如其他人所说的那样,您不能保证从多个线程使用单个BitArray不会导致线程之间发生争用。

通过为每个线程提供自己的BitArray而不是使用共享的静态BitArray,可以最简单地证明这一点。通过这种方法,我通常会看到每个线程大约需要450毫秒,尽管偶尔会看到更长的时间:

Thread2: 415
Thread1: 420
447
Thread2: 414
Thread1: 420
496
Thread1: 1185
Thread2: 1198
1249
Thread1: 417
Thread2: 421
455
Thread1: 420
Thread2: 415
455
Thread2: 413
Thread1: 417
491
Thread2: 413
Thread1: 417
508
Thread2: 417
Thread1: 441
526
Thread1: 420
Thread2: 415
465
Thread1: 940
Thread2: 1005
1087

最终我认为这表明:

  • 尽管有代码设计,但线程之间的BitArray仍然存在争用效果
  • 即使每个线程都有单独的位数组,代码的时序仍然存在“随机”效应,这表明通过这样的微基准测试,您总是能够有效地进行基准测试,而不仅仅是您编写的代码。您还可以使用GC,CPU缓存,上下文切换,核心跳跃,秒表不准确等功能。
  • 如果您尝试编写的代码的真正目的是尽可能快地填充位数组,那么您可能希望更接近线路,更多手动方法,可能使用其他语言。