10

I have the following Code (complete content of 'Program.cs' of console application). The single threaded execution of 'countUp' till 'countUp4' takes 13 sec., the multi threaded execution 21 sec..

I have a Intel Core i5-2400 @ 3.10 GHz, 8 GB Ram, Windows 7 64 Bit. So why is the multi threaded execution slower than the single threaded one?

Is multithreading just useful for not blocking the main routine of simple c# applications? When does multithreading give me an advantage in execution speed?

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

6 に答える 6

21

Here's a cause that you might not see coming: false sharing because those 4 ints all sit side by side in memory.

Update - MSDN mags from previous years are only available as .chm files now - so you have to grab the 'October 2008' edition of the MSDN Mag from here and, after downloading, you must remember to right-click and 'unblock' the file from the file properties dialog in Windows Explorer (other OSs are available!) before opening it. You're looking for a column called '.Net Matters' by Stephen Toub, Igor Ostrovsky, and Huseyin Yildiz

The article (read it all - it's brilliant) shows how values that are side by side in memory can end up causing blocking when updated because they all sit on the same cache line. This is very low-level blocking that you can't disable from your .Net code. You can, however force the data to be spaced further apart so that you guarantee, or at least increase the likelihood, that each value will be on a different cache line.

The article uses arrays - but it's just possible it's affecting you here.

To follow up the suggestion below, you might be able to prove/disprove this by changing your code ever-so-slightly:

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

And then modify your thread functions to manipulate the public field Value on each of the counter holders.

I've made those arrays really much bigger than they need to be in the hope that it'll prove it better :)

于 2012-09-12T14:37:48.497 に答える
5

Andreas Zaltan's is the answer. Take the code

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

Run it with the intergers and you get the multi-thrreadded version running ever so slightly slower.

Serial: 13.88s
Multi-threaded: 14.01

Run it using the suggestion above you get the following

enter image description here

I have posted this for clarity...

于 2012-09-12T15:05:59.483 に答える
1

I rewrote your code with StopWatch. Multi-threading is faster than Single-threading on my computer (times below).

Also, you need to call the method Join on the threads to be sure that they finished before exiting the program.

Time elapsed without multithreading:: 00:00:21.6897179

Time elapsed with multithreading:: 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++;
            }
        }
    }
}
于 2012-09-12T14:45:55.327 に答える
0

Joeb454が言ったように、その場合は並列処理を探す必要があります。新しいスレッドの作成には「長い」時間がかかるため、マルチスレッドは実行を遅くします。

于 2012-09-12T14:40:02.477 に答える
0

I'm no expert on multi-threading, but I think what you're doing there is essentially just moving the work away from the UI thread.

That's by no means a bad thing if you have some long-running or intensive work to do, as it allows you to keep a responsive UI for the end user. To run things like that quicker, you'll want to look into parallel processing, if my memory services me correctly.

于 2012-09-12T14:32:07.347 に答える
0

First, use the StopWatch class from the System.Runtime.Diagnostic namespace for measuring, instead of DateTime.

Second, you do not clear the 'Counters' after you synchronously execution.

You should use parallelisation for all the threads, than it is faster! The initializing new threads are costly. You can use the ThreadPool by the way.

于 2012-09-12T14:35:48.980 に答える