多线程比单线程慢

时间:2012-09-12 14:28:15

标签: c# multithreading performance

我有以下代码(控制台应用程序的'Program.cs'的完整内容)。 'countUp'直到'countUp4'的单线程执行需要13秒,多线程执行21秒..

我有一台Intel Core i5-2400 @ 3.10 GHz,8 GB Ram,Windows 7 64 Bit。那么为什么mutli线程执行比单线程执行慢?

多线程对于不阻塞简单c#应用程序的主程序有用吗?多线程什么时候给我一个执行速度的优势?

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace ConsoleApplication1
{
    class Program
    {
        static int counter = 0;
        static int counter2 = 0;
        static int counter3 = 0;
        static int counter4 = 0;

        static void Main(string[] args)
        {
            Console.WriteLine("Without multithreading:");
            Console.WriteLine("Start:" + DateTime.Now.ToString());

            countUp();
            countUp2();
            countUp3();
            countUp4();

            Console.WriteLine("");
            Console.WriteLine("With multithreading:");
            Console.WriteLine("Start:" + DateTime.Now.ToString());

            Thread thread1 = new Thread(new ThreadStart(countUp));
            thread1.Start();
            Thread thread2 = new Thread(new ThreadStart(countUp2));
            thread2.Start();
            Thread thread3 = new Thread(new ThreadStart(countUp3));
            thread3.Start();
            Thread thread4 = new Thread(new ThreadStart(countUp4));
            thread4.Start();

            Console.Read();
        }

        static void countUp()
        {
            for (double i = 0; i < 1000000000; i++)
            {
                counter++;
            }

            Console.WriteLine(counter.ToString());
            Console.WriteLine(DateTime.Now.ToString());
        }

        static void countUp2()
        {
            for (double i = 0; i < 1000000000; i++)
            {
                counter2++;
            }

            Console.WriteLine(counter2.ToString());
            Console.WriteLine(DateTime.Now.ToString());
        }

        static void countUp3()
        {
            for (double i = 0; i < 1000000000; i++)
            {
                counter3++;
            }

            Console.WriteLine(counter3.ToString());
            Console.WriteLine(DateTime.Now.ToString());
        }

        static void countUp4()
        {
            for (double i = 0; i < 1000000000; i++)
            {
                counter4++;
            }

            Console.WriteLine(counter4.ToString());
            Console.WriteLine(DateTime.Now.ToString());
        }
    }
}

6 个答案:

答案 0 :(得分:20)

以下是您可能看不到的原因:false sharing因为这4个整数并排在内存中。

更新 - 前几年的MSDN mags现在仅作为.chm文件提供 - 因此您必须抓住'October 2008' edition of the MSDN Mag from here,并且在下载后,您必须记住右键单击并且'在打开之前,从Windows资源管理器中的文件属性对话框(其他操作系统可用!)中取消阻止该文件。您正在寻找由Stephen Toub,Igor Ostrovsky和Huseyin Yildiz撰写的名为“.Net Matters”的专栏

这篇文章(全部阅读 - 很精彩)展示了内存中并排的值如何在更新时最终导致阻塞,因为它们都位于同一缓存行中。这是非常低级别的阻止,您无法从.Net代码禁用。但是,您可以强制将数据间隔得更远,以便保证或至少增加每个值将位于不同缓存行上的可能性。

这篇文章使用数组 - 但这可能会影响到你。

要跟进以下建议,您可以通过稍微更改代码来证明/反驳这一点:

class Program 
{ 
    class CounterHolder {
       private int[] fakeInts = new int[1024];
       public int Value = 0;
    }
    static CounterHolder counter1 = new CounterHolder(); 
    static CounterHolder counter2 = new CounterHolder(); 
    static CounterHolder counter3 = new CounterHolder(); 
    static CounterHolder counter4 = new CounterHolder(); 

然后修改您的线程函数以操纵每个计数器持有者的公共字段Value

我让那些阵列真的比它们需要的大得多,希望它能证明它更好:)

答案 1 :(得分:5)

Andreas Zaltan就是答案。拿代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Diagnostics;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    class Program
    {
        //static int counter = 0;
        //static int counter2 = 0;
        //static int counter3 = 0;
        //static int counter4 = 0;

        class CounterHolder
        {
            private int[] fakeInts = new int[1024];
            public int Value = 0;
        }
        static CounterHolder counter1 = new CounterHolder();
        static CounterHolder counter2 = new CounterHolder();
        static CounterHolder counter3 = new CounterHolder();
        static CounterHolder counter4 = new CounterHolder(); 

        static void Main(string[] args)
        {
            Console.WriteLine("Without multithreading:");
            Console.WriteLine("Start: " + DateTime.Now.ToString());

            Stopwatch sw = new Stopwatch();
            sw.Start();

            countUp();
            countUp2();
            countUp3();
            countUp4();

            sw.Stop();
            Console.WriteLine("Time taken = " + sw.Elapsed.ToString());

            Console.WriteLine("\nWith multithreading:");
            Console.WriteLine("Start: " + DateTime.Now.ToString());
            sw.Reset();
            sw.Start();

            Task task1 = Task.Factory.StartNew(() => countUp());
            Task task2 = Task.Factory.StartNew(() => countUp2());
            Task task3 = Task.Factory.StartNew(() => countUp3());
            Task task4 = Task.Factory.StartNew(() => countUp4());
            var continuation = Task.Factory.ContinueWhenAll(new[] { task1, task2, task3, task4 }, tasks =>
            {
                Console.WriteLine("Total Time taken = " + sw.Elapsed.ToString());
            });
            Console.Read();
        }

        static void countUp()
        {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            for (double i = 0; i < 1000000000; i++)
                counter1.Value++;
            sw.Stop();
            Console.WriteLine("Task countup took: " + sw.Elapsed.ToString());
        }

        static void countUp2()
        {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            for (double i = 0; i < 1000000000; i++)
                counter2.Value++;
            sw.Stop();
            Console.WriteLine("Task countUP2 took: " + sw.Elapsed.ToString());
        }

        static void countUp3()
        {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            for (double i = 0; i < 1000000000; i++)
                counter3.Value++;
            sw.Stop();
            Console.WriteLine("Task countUP2 took: " + sw.Elapsed.ToString());
        }

        static void countUp4()
        {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            for (double i = 0; i < 1000000000; i++)
                counter4.Value++;
            sw.Stop();
            Console.WriteLine("Task countUP2 took: " + sw.Elapsed.ToString());
        }
    } 
}

使用intergers运行它,你得到的多线程版本运行速度稍慢。

Serial: 13.88s
Multi-threaded: 14.01

使用上面的建议运行它,您将获得以下内容

enter image description here

为了清楚起见,我发布了这个...

答案 2 :(得分:1)

我用StopWatch重写了你的代码。多线程比我的计算机上的单线程更快(次数以下)。

此外,您需要在线程上调用 Join 方法,以确保它们在退出程序之前完成。

没有多线程的时间过去了:00:00:21.6897179

多线程经过的时间:: 00:00:14.7893703

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Diagnostics;

namespace ConsoleApplication1
{
    class Program
    {
        static int counter = 0;
        static int counter2 = 0;
        static int counter3 = 0;
        static int counter4 = 0;

        static void Main(string[] args)
        {
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();

            countUp();
            countUp2();
            countUp3();
            countUp4();

            stopwatch.Stop();
            Console.WriteLine("Time elapsed without multithreading:: {0}",
        stopwatch.Elapsed);

            stopwatch.Reset();
            stopwatch.Start();

            Thread thread1 = new Thread(new ThreadStart(countUp));
            thread1.Start();
            Thread thread2 = new Thread(new ThreadStart(countUp2));
            thread2.Start();
            Thread thread3 = new Thread(new ThreadStart(countUp3));
            thread3.Start();
            Thread thread4 = new Thread(new ThreadStart(countUp4));
            thread4.Start();

            thread1.Join();
            thread2.Join();
            thread3.Join();
            thread4.Join();

            stopwatch.Stop();
            Console.WriteLine("Time elapsed with multithreading:: {0}",
        stopwatch.Elapsed);

            Console.Read();
        }

        static void countUp()
        {
            for (double i = 0; i < 1000000000; i++)
            {
                counter++;
            }
        }

        static void countUp2()
        {
            for (double i = 0; i < 1000000000; i++)
            {
                counter2++;
            }
        }

        static void countUp3()
        {
            for (double i = 0; i < 1000000000; i++)
            {
                counter3++;
            }
        }

        static void countUp4()
        {
            for (double i = 0; i < 1000000000; i++)
            {
                counter4++;
            }
        }
    }
}

答案 3 :(得分:0)

我不是多线程专家,但我认为你在那里所做的只是将工作从UI线程中移开。

如果您有一些长期或密集的工作要做,这绝不是一件坏事,因为它允许您为最终用户保留响应式UI。为了更快地运行这样的事情,如果我的记忆能够正确地为我服务,你将需要研究并行处理。

答案 4 :(得分:0)

首先,使用System.Runtime.Diagnostic命名空间中的StopWatch类进行测量,而不是使用DateTime。

其次,在同步执行后,您不会清除“计数器”。

你应该对所有线程使用并行化,而不是更快!初始化新线程的成本很高。顺便说一下,你可以使用ThreadPool。

答案 5 :(得分:0)

正如Joeb454所说,在这种情况下你必须寻找并行处理。 您的多线程只会减慢执行速度,因为创建一个新线程会花费很长时间。