3

I wrote this code in purpose to test multi and single threading speeds. Thanks for all the feedback! I rewrote most of it based on the great comments I received. This now functions properly (maybe has a bug here or there), tests multi threads first, and takes an average to find a more accurate speed: (Scroll to bottom for cont.)

Main method Class

using System;

namespace SingleAndMultiThreading
{
internal class Threads
{
    private static void Main(string[] args)
    {

        long numOfObjCreated;
        int numberOfTests;

        while (true)
        {
            try
            {
                Console.Write("Number of objects to create: ");
                numOfObjCreated = Convert.ToInt64(Console.ReadLine());
                break;
            }
            catch (Exception)
            {
                Console.WriteLine("Invalid input.");
            }
        }


        while (true)
        {
            try
            {
                Console.Write("Number of tests to run: ");
                numberOfTests = Convert.ToInt32(Console.ReadLine());
                break;
            }
            catch (Exception)
            {
                Console.WriteLine("Invalid input.");
            }
        }


        CalculateResults(numOfObjCreated, numberOfTests);

        Console.ReadKey();

    }


    private static void CalculateResults(long numOfObjCreated, int numberOfTests)
    {
        double totalPercentages = 0;
        for (var i = 0; i < numberOfTests; i++)
        {
            totalPercentages += CompleteTests(numOfObjCreated);
        }

        var accuracy = totalPercentages / numberOfTests;

        if ((int)accuracy == 0)
        {
            Console.WriteLine("\nIn this case, neither single threading or multithreading is faster.\n" +
                              "They both run equally well under these conditions.\n");
            return;
        }

        if (accuracy < 0)
        {
            Console.WriteLine("\nIn this case with {0} objects being created, single threading is faster!\n",
                string.Format("{0:#,###0}", numOfObjCreated));
            return;
        }

        Console.WriteLine("\nFrom {0} test(s), {1}% was the average percentage of increased speed in multithreading.\n",
            string.Format("{0:#,###0}", numberOfTests), string.Format("{0:#,###0}", accuracy));
    }

    private static double CompleteTests(long numOfObjCreated)
    {
        Console.WriteLine("Computing...");

        var numOfCores = Environment.ProcessorCount;

        var timeForMultiThread = MultiThread.Run(numOfObjCreated, numOfCores);
        var timeForSingleThread = SingleThread.Run(numOfObjCreated);

        var percentFaster = ((timeForSingleThread / timeForMultiThread) * 100) - 100;

        //note: .NET does its part in assigning a certian thread to its own core

        Console.WriteLine("Using all {0} cores, creating {1} objects is {2}% faster.",
            numOfCores, string.Format("{0:#,###0}", numOfObjCreated), string.Format("{0:#,###0}", percentFaster));

        return percentFaster;
    }
}
}

Single Threading Class

using System;
using System.Diagnostics;

namespace SingleAndMultiThreading
{
internal class SingleThread
{
    public static double Run(long numOfObjCreated)
    {
        var watch = new Stopwatch();

        watch.Start();

        for (long i = 0; i < numOfObjCreated; i++)
        {
            new object();
        }

        watch.Stop();

        var totalTime = watch.ElapsedTicks;

        Console.WriteLine("The time to create {0} objects with 1 thread is: {1} ticks.",
            string.Format("{0:#,###0}", numOfObjCreated), string.Format("{0:#,###0}", totalTime));

        return totalTime;

    }
}
}

Multi Threading Class

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

namespace SingleAndMultiThreading
{
internal class MultiThread
{
    public static double Run(long numOfObjCreated, int numOfCores)
    {
        var watch = new Stopwatch();

        var workerObject = new Worker(numOfObjCreated / numOfCores);

        var listOfThreads = new List<Thread>();


        for (long k = 0; k < numOfCores; k++)
        {
            var workerThread = new Thread(workerObject.DoWork);
            listOfThreads.Add(workerThread);
        }

        watch.Start();
        foreach (var thread in listOfThreads)
        {
            thread.Start();
        }

        byte countOfCompletedThreads = 0;

        while (true)
        {
            foreach (var thread in listOfThreads)
                if (!thread.IsAlive)
                    countOfCompletedThreads++;

            if (countOfCompletedThreads == numOfCores)
                break;
            countOfCompletedThreads = 0;

        }

        watch.Stop();

        var totalTime = watch.ElapsedTicks;

        Console.WriteLine("The time to create {0} objects utilizing all {1} cores is: {2} ticks.",
            string.Format("{0:#,###0}", numOfObjCreated), numOfCores, string.Format("{0:#,###0}", totalTime));

        return totalTime;

    }
}
}

Worker Class

namespace SingleAndMultiThreading
{
public class Worker
{
    private readonly long _numOfObjToCreate;
    public bool IsDone;

    public Worker(long numOfObjToCreate)
    {
        _numOfObjToCreate = numOfObjToCreate;
    }

    public void DoWork()
    {
        for (long i = 0; i < _numOfObjToCreate; i++)
        {
            new object();
        }

        IsDone = true;
    }
}
}

The output of this code is a bit too long to post (I urge you to copy and paste into your own IDE, its really fascinating). I guess the accepted answer that this doesn't give the same result per every test is due to CPU scheduling, other or minor issues like ASLR and such. More than one thing is happening aside from visual studio running this program, and priorities differently. Also thank you for pointing out that running multi threading first helps because of the already-done memory allocation!

Another thing to point out, I found this while running:

Analysis

The spikes are when the process of multi threading takes place.

Eric
  • 444
  • 7
  • 19
  • That's were CPU scheduling takes action. Your app is not the only app in the system! – ichramm Jun 11 '16 at 04:43
  • In that case, how would you recommend getting the most accurate results? Maybe reserving a spot in a core to specifically running threads from this program or something? – Eric Jun 11 '16 at 04:44
  • I don't know, maybe running a couple of thousand times and then computing the average would be a good start. – ichramm Jun 11 '16 at 04:50
  • @Eric You can't "reserve a spot in a core". Windows schedules thread, you don't, you simply request to run them. If you want guaranteed runtime you need a real time operating system, not windows nor .net. Also your code is full of bugs unlike what you seem to think, you're starting threads but never waiting for them to complete so of course it's massively faster, what you're measuring is basically the time it takes for the thread to start nor for it to process it (you need to use thread.join to wait on the thread to be done) – Ronan Thibaudau Jun 11 '16 at 04:51
  • And you're also checking for the same threads (increasing the count multiple time per thread). Basically your whole code is a big bug really, you're not profiling anything except how quickly at least one thread will be activated – Ronan Thibaudau Jun 11 '16 at 04:52
  • Also is there a goal to this expect benchmarking thread for the sake of it? Because even those bugs aside this isn't how to benchmark (and benchmarking isn't just testing a bit of code with a stopwatch). Are you targeting release mode? making sure no debugger is attached? Why aren't you pre running the code before timing it to account for JIT time? Why isn't this run in loops a few million times to average the time of each of the tests? And most of all parallel programming is not trivial, and what you show with many bugs is about as simple an example as it gets, do yourself a favor and – Ronan Thibaudau Jun 11 '16 at 04:55
  • Don't use threads if you don't know how to! You will shoot yourself in the foot and end up with buggy code, use the many constructs .net provide to parallelize your appliation without managing threads directly (Parallel.For / Foreach, Task parallel library, parallel LINQ etc). – Ronan Thibaudau Jun 11 '16 at 04:56
  • I'm a 16 year old student learning c# on my own, no need for the pointless criticism. I wrote this code off of a whim and my interest to program. I really appreciate the little bit of constructive criticism, and explanation to this code. In the future "your whole code is a big bug really" won't help anybody. Learn to communicate before posting. – Eric Jun 11 '16 at 04:59
  • 1
    Generally you can assume that if your code runs superlinear (meaning you get more than n times speedup with n threads) that you're either the lucky victim of better cache utilization, etc or that you have a bug somewhere.. The former can explain maybe a 20% increase not what you're seeing. No reason to for anyone to be aggressive.. At the same time "this code works, don't go finding errors" is a challenge that few will be able to resist and that's unlikely to be true for anyone :) – Voo Jun 11 '16 at 08:03
  • 1
    Fast code like this is excessively difficult to profile reliably. The most basic issue is that the first measurement includes all the cost of getting the code loaded and just-in-time compiled. The multi-threaded test gets the benefit of the first test already having allocated the address space. Simply swap the two tests to see a big difference. The .NET framework supports ASLR strongly, that makes one run not match another. Get ahead by running tests like this at least 10 times. Discard the first result and take the median value as a *rough* guideline. – Hans Passant Jun 11 '16 at 10:53
  • 1
    @Eric That's is very impressive question for 16 year old! :) Very soon you will learn that **no program is free of bugs** (GIST, anyone?), and that you must accept it that you always write bugs that are waiting for the right state. Although it was not the main issue of this topic, this might explain why some of the comments were too offensive. Not to justify them, but I guess your disclaimer seemed ridicules to some of them, and surely they didn't know you're just 16. Just remember to treat the young programmers better when you'll get a grumpy old one like us :) – shay__ Jun 11 '16 at 12:08

0 Answers0