Why is multithreaded quicksort slower than singlethreaded in C# - c#

I know this question has been asked before, and the answers I found were all about the pre-empting & synchronization overhead etc.. But still, I am curious to know the answer of my own situation. So here's the deal.
I am running on Intel Core i7-2670QM CPU (4 cores, 8 threads) and I wrote this code:
using System;
using System.Diagnostics;
using System.Threading;
namespace _T
{
class Program
{
private static void stquicksort(object parameter)
{
object[] parameters = (object[])parameter;
int[] array = (int[])parameters[0];
int left = (int)parameters[1];
int right = (int)parameters[2];
if (left >= right) return;
int temp = (left + right) / 2;
int pivot = array[temp];
array[temp] = array[right];
int j = left;
for (int i = left; i < right; i++)
{
if (array[i] < pivot)
{
if (i != j)
{
temp = array[i];
array[i] = array[j];
array[j++] = temp;
}
else j++;
}
}
array[right] = array[j];
array[j] = pivot;
stquicksort(new object[] { array, left, j - 1 });
stquicksort(new object[] { array, j + 1, right });
}
private static void mtquicksort(object parameter)
{
object[] parameters = (object[])parameter;
int[] array = (int[])parameters[0];
int left = (int)parameters[1];
int right = (int)parameters[2];
if (left >= right) return;
int temp = (left + right) / 2;
int pivot = array[temp];
array[temp] = array[right];
int j = left;
for (int i = left; i < right; i++)
{
if (array[i] < pivot)
{
if (i != j)
{
temp = array[i];
array[i] = array[j];
array[j++] = temp;
}
else j++;
}
}
array[right] = array[j];
array[j] = pivot;
Thread t = new Thread(mtquicksort);
t.Start(new object[] { array, left, j - 1 });
mtquicksort(new object[] { array, j + 1, right });
t.Join();
}
private static void dump(int[] array)
{
Console.Write("Array:");
foreach (int el in array) Console.Write(" " + el);
Console.WriteLine();
}
private static void Main(string[] args)
{
while (true)
{
Console.Write("Enter the number of elements: ");
int count = Convert.ToInt32(Console.ReadLine());
if (count < 0) break;
Random rnd = new Random();
int[] array1 = new int[count];
for (int i = 0; i < array1.Length; i++)
array1[i] = rnd.Next(1, 100);
int[] array2 = (int[])array1.Clone();
Stopwatch sw = new Stopwatch();
sw.Reset(); sw.Start();
stquicksort(new object[] { array1, 0, array1.Length - 1 });
sw.Stop();
Console.WriteLine("[ST] Time needed: " + sw.ElapsedMilliseconds + "ms");
sw.Reset(); sw.Start();
mtquicksort(new object[] { array2, 0, array2.Length - 1 });
sw.Stop();
Console.WriteLine("[MT] Time needed: " + sw.ElapsedMilliseconds + "ms");
}
Console.WriteLine("Press any key to exit . . .");
Console.ReadKey(true);
}
}
}
The stquicksort is the single threaded, mtquicksort is the multi one, and yes, I left the st parameters that way on purpose so the boxing/unboxing overheads are the same on both versions (if any noticable). I've put the solution on release (disabled all debugging), and the output is somewhat sad:
Enter the number of elements: 100
[ST] Time needed: 0ms
[MT] Time needed: 323ms
Enter the number of elements: 1000
[ST] Time needed: 0ms
[MT] Time needed: 7476ms
Enter the number of elements: 1000
[ST] Time needed: 0ms
[MT] Time needed: 7804ms
Enter the number of elements: 1000
[ST] Time needed: 0ms
[MT] Time needed: 7474ms
Enter the number of elements: 10
[ST] Time needed: 0ms
[MT] Time needed: 32ms
Enter the number of elements: 100
[ST] Time needed: 0ms
[MT] Time needed: 339ms
So again, is the problem pre-empting, is it maybe a flaw in code? And more importantly, what would be a proper way to solve this.

Spawning threads is a fairly expensive operation. It's not instantaneous so the massive time you are seeing is not additional time required to perform the sort but the time required to spawn the treads. When you spawn a new thread in order for it to be worth it that thread has to run for a while.
.NET and C# do have a Task system Task's are similar to threads except they operate on a thread pool instead of spawning a new thread every time. This allows you to multi thread tasks without the high cost of creating a new thread for each one.
Try replacing your threading code with this.
Task t = Task.Run(()=>mtquicksort(new object[] { array, left, j - 1 }));
t.Wait();
Note you will have to use the System.Threading.Tasks namespace

Related

Why is the stopwatch not timing my method correctly?

I am trying to test the performance difference of my quicksort and mergesort methods, but for some reason the first cycle of the loop always show exactly 0.003 milliseconds and the rest 0 milliseconds.
public static void RunDiagnostics(int[] rangeOfLengthsToTest, int numOfTestsPerLength)
{
Stopwatch stopwatch = new Stopwatch();
int[] array;
double totalQuicksortTime, totalMergesortTime;
for (int i = rangeOfLengthsToTest[0]; i <= rangeOfLengthsToTest[1]; i++)
{
totalQuicksortTime = 0;
totalMergesortTime = 0;
for (int k = 0; k < numOfTestsPerLength; k++)
{
array = GetArray(i, new int[] { -9999, 9999 });
stopwatch.Start();
QuickSort((int[])array.Clone());
stopwatch.Stop();
totalQuicksortTime += stopwatch.ElapsedMilliseconds;
stopwatch.Restart();
MergeSort((int[])array.Clone());
stopwatch.Stop();
totalMergesortTime += stopwatch.ElapsedMilliseconds;
}
Console.WriteLine($"Quicksort took an average of {totalQuicksortTime / numOfTestsPerLength} milliseconds to sort arrays with a length of {i}.");
Console.WriteLine($"Mergesort took an average of {totalMergesortTime / numOfTestsPerLength} milliseconds to sort arrays with a length of {i}.");
Console.WriteLine();
}
}
Even when I print the elapsed milliseconds of each run instead of averaging them out, it still only times zeros.
edit: Here are my methods (sorry if they are a bit junk):
static void Main(string[] args)
{
RunDiagnostics(new int[] { 3, 12 }, 1000);
}
public static int[] GetArray(int length, int[] RangeOfNumbers)
{
Random rng = new Random();
int[] arr = new int[length];
for (int i = 0; i < arr.Length; i++)
arr[i] = rng.Next(RangeOfNumbers[0], RangeOfNumbers[1]);
return arr;
}
public static void QuickSort(int[] array)
{
QuickSort(0, array.Length - 1);
void QuickSort(int left, int right)
{
if (right > left)
{
if (right - left == 1)
{
if (array[left] > array[right])
Swap(left, right);
}
else
{
Swap(GetStartingPivotIndex(), right);
int pivot = Partition(left, right - 1);
QuickSort(left, pivot - 1);
QuickSort(pivot + 1, right);
}
}
int GetStartingPivotIndex()
{
if (array[left] > array[right])
{
if (array[left + (right - left) / 2] > array[right])
return right;
else
return left + (right - left) / 2;
}
else
{
if (array[left + (right - left) / 2] > array[left])
return left;
else
return left + (right - left) / 2;
}
}
int Partition(int low, int high)
{
while (low != high)
{
if (array[low] < array[right])
low++;
else if (array[high] > array[right])
high--;
else
Swap(low, high);
}
Swap(low, right);
return low;
}
void Swap(int index1, int Index2)
{
int temp = array[index1];
array[index1] = array[Index2];
array[Index2] = temp;
}
}
}
public static void MergeSort(int[] array)
{
MergeSort(array);
int[] MergeSort(int[] array)
{
int[] array1 = array.Take(array.Length / 2).ToArray();
int[] array2 = array.Skip(array.Length / 2).ToArray();
if (array1.Length > 1)
MergeSort(array1);
if (array2.Length > 1)
MergeSort(array2);
int c1 = 0;
int c2 = 0;
bool flag1 = false;
bool flag2 = false;
for (int i = 0; i < array.Length; i++)
{
if (flag1 && !flag2)
{
array[i] = array2[c2];
if (c2 == array2.Length - 1)
flag2 = true;
else c2++;
}
else if (flag2 && !flag1)
{
array[i] = array1[c1];
if (c1 == array1.Length - 1)
flag1 = true;
else c1++;
}
else if (!flag1 && !flag2)
{
if (array1[c1] < array2[c2])
{
array[i] = array1[c1];
if (c1 == array1.Length - 1)
flag1 = true;
else c1++;
}
else
{
array[i] = array2[c2];
if (c2 == array2.Length - 1)
flag2 = true;
else c2++;
}
}
}
return array;
}
}
This isn't a problem with the Stopwatch.
The very first time your sort code runs, it's taking much longer than subsequent times. This can happen due to JIT compilation, caching, and similar things that are completely outside of your control. It's taking long enough that the ElapsedMilliseconds value has a meaningful integer value (e.g. 3), and so when you divide it by 1000 you end up with a number in the thousands place (e.g. .003).
Every other time the sort code runs, it's taking less than a millisecond. So all the += operations are adding zero to the total. The sum of all those zeroes is zero.
Changing the += stopwatch.ElapsedMilliseconds to += stopwatch.Elapsed.TotalMilliseconds; will fix that particular problem, and give you results more like this:
Quicksort took an average of 0.003297300000000042 milliseconds to sort arrays with a length of 3.
Mergesort took an average of 0.0019986999999999453 milliseconds to sort arrays with a length of 3.
Quicksort took an average of 0.0013175999999999856 milliseconds to sort arrays with a length of 4.
Mergesort took an average of 0.001030500000000005 milliseconds to sort arrays with a length of 4.
Quicksort took an average of 0.001468300000000015 milliseconds to sort arrays with a length of 5.
Mergesort took an average of 0.0011402999999999956 milliseconds to sort arrays with a length of 5.
However, there are other issues to fix.
You're including the time spent cloning the array in your results.
Start() should be switched to Restart(): right now the nth run of QuickSort is including the n-1th run of Mergesort in its time.
The whole strategy of adding thousands of individual run times together still exposes you to rounding errors: they're just smaller with double than they are with int. You see all those 0000000s and 999999s in the results? A better strategy is typically to run the same sort a whole bunch of times and then see how much total time has passed.
In general, you're better off relying on a benchmarking framework rather than writing your own code. There are a lot of issues like these that you are unlikely to consider when writing your own.
As #JosephDaSilva pointed out, stopwatch.ElapsedMilliseconds returns an integer value which means it's going to round down to 0 if it was less then a milisecond. stopwatch.Elapsed.TotalMilliseconds should be used as it returns a floating-point value, which won't get rounded down.

What's wrong with this simple method to sample from multinomial in C#?

I wanted to implement a simple method to sample from a multinomial distribution in C# (the first argument is an array of integers we want to sample and the second one is the probabilities of selecting each of those integers).
When I do this with numpy in python, the results make sense.
np.random.choice(np.array([1,2,3,4,5,6]),p=np.array([.624,.23,.08,.04, .02, .006]),size=len(b))
I get a lot of 1's (probability 62%), a bunch of 2's, some 3's etc.
However, when I try the implementation below in C# (pretty straightforward inverse transform sampling for multinomial, only relies on a uniform random variable), I get really weird results. For all 1000 samples, I'll often find all 1's. Sometimes, I'll find all 3's (!!??). The results never look like what you would expect (and what you get from the python function - try running it yourself a few times). This is really scary since we rely on these primitives. Does anyone have insight into what might be wrong with the C# version?
static void Main(string[] args)
{
int[] iis = new int[7];
int[] itms = new int[] { 1, 2, 3, 4, 5, 6 };
double[] probs = new double[] { .624, .23, .08, .04, .02, .006 };
for (int i = 0; i < 1000; i++)
{
iis[MultinomialSample(itms, probs)] += 1;
}
foreach (var ii in iis)
{
Console.Write(ii + ",");
}
Console.Read();
}
private static int MultinomialSample(int[] s, double[] ps)
{
double[] cumProbs = new double[ps.Length];
cumProbs[0] = ps[0];
for (int i = 1; i < ps.Length; i++)
{
cumProbs[i] = cumProbs[i - 1] + ps[i];
}
Random random = new Random();
double u = random.NextDouble();
for (int i = 0; i < cumProbs.Length - 1; i++)
{
if (u < cumProbs[i])
{
return s[i];
}
}
return s[s.Length - 1];
}
You're initializing Random each time you call MultinomialSample. If these calls are very close together, Random will be initialized with the same seed (based on the system clock). Try either making Random a private class field: private static Random random = new Random(); or pass it into the method as an argument from Main, where it would be initialized only once:
private static Random random = new Random();
private static int MultinomialSample(IReadOnlyList<int> sample,
IReadOnlyList<double> probabilities)
{
var cumProbs = new double[probabilities.Count];
cumProbs[0] = probabilities[0];
for (var i = 1; i < probabilities.Count; i++)
{
cumProbs[i] = cumProbs[i - 1] + probabilities[i];
}
for (var i = 0; i < cumProbs.Length - 1; i++)
{
if (random.NextDouble() < cumProbs[i])
{
return sample[i];
}
}
return sample[sample.Count - 1];
}
private static void Main()
{
var iis = new int[7];
var items = new[] {1, 2, 3, 4, 5, 6};
var probabilities = new[] {.624, .23, .08, .04, .02, .006};
for (int i = 0; i < 1000; i++)
{
iis[MultinomialSample(items, probabilities)] ++;
}
Console.WriteLine(string.Join(", ", iis));
Console.WriteLine("\nDone!\nPress any key to exit...");
Console.ReadKey();
}
I used Rufus' code in a simulation I was working on and noticed there is still a problem, even after seeding the random number generator just once (which is the correct thing to do). You will notice that as we are iterating, the call to random.NextDouble() generates a new random number each time. This is wrong.
for (var i = 0; i < cumProbs.Length - 1; i++)
{
if (random.NextDouble() < cumProbs[i])
{
return sample[i];
}
}
The random number should be generated outside of the loop, as follows:
var r = random.NextDouble();
for (var i = 0; i < cumProbs.Length - 1; i++)
{
if (r < cumProbs[i])
{
return sample[i];
}
}
You can compare it to the Excel algorithm given on Wikipedia: https://en.wikipedia.org/wiki/Multinomial_distribution. When I made the above change to Rufus' code, I got the desired frequency distribution as specified by the probabilities array.

How do I avoid stackoverflowexception within a finite loop (C#)

I'm trying to write a code to find prime numbers within a given range. Unfortunately I'm running into some problems with too many repetitions that'll give me a stackoverflowexception after prime nr: 30000. I have tried using a 'foreach' and also not using a list, (doing each number as it comes) but nothing seems to handle the problem in hand.
How can I make this program run forever without causing a stackoverflow?
class Program
{
static void Main(string[] args)
{
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
List<double> Primes = new List<double>();
const double Start = 0;
const double End = 100000;
double counter = 0;
int lastInt = 0;
for (int i = 0; i < End; i++)
Primes.Add(i);
for (int i =0;i< Primes.Count;i++)
{
lastInt = (int)Primes[i] - RoundOff((int)Primes[i]);
Primes[i] = (int)CheckForPrime(Primes[i], Math.Round(Primes[i] / 2));
if (Primes[i] != 0)
{
Console.Write(", {0}", Primes[i]);
counter++;
}
}
stopwatch.Stop();
Console.WriteLine("\n\nNumber of prime-numbers between {0} and {1} is: {2}, time it took to calc this: {3} (millisecounds).\n\n" +
" The End\n", Start, End, counter, stopwatch.ElapsedMilliseconds);
}
public static double CheckForPrime(double Prim, double Devider)
{
if (Prim / Devider == Math.Round(Prim / Devider))
return 0;
else if (Devider > 2)
return CheckForPrime(Prim, Devider - 1);
else
return Prim;
}
public static int RoundOff(int i)
{
return ((int)Math.Floor(i / 10.0)) * 10;
}
}

Creating a new array, discluding an item is faster than creating a list and removing it?

I was desperately trying to find a faster way of creating an array without one of the items in it, opposed to creating a list of those objects and removing that item.
The reason being is that my array has like 5 items on average, less most times, and creating a list just to remove one item that I no longer wanted in there, seemed way too much to me.
Since I coded my own profiler a while back, I decided to put that method, and another one I thought of, under a test, and heck are the results significant.
int[] a1 = new int[] { 1, 2, 3 };
Method 1 (creating a list of the objects and removing the 2nd item):
Profiler.Start("Method 1");
List<int> a2 = new List<int>(a1);
a2.RemoveAt(1);
Profiler.Stop("Method 1");
Method 2 (creating an array of the same size but one, and discluding the 2nd item):
Profiler.Start("Method 2");
int[] a3 = new int[a1.Length - 1];
int l = 0;
for (int i = 0; i < a1.Length; i++) if (a1[i] != 2) { a3[l] = a1[i]; l++; }
Profiler.Stop("Method 2");
Profiler results:
http://i.stack.imgur.com/al8i6.png
The question
Why is the difference in performance so significant?
I never actually took the time to study the differences between an array and a list.
It seems that the overhead is coming from the initialisation for the contents of the list rather than from the call to List.RemoveAt().
In other words, the line List<int> a2 = new List<int>(a1); is dominating the time.
Here's some code that attempts to eliminate the time taken for List.RemoveAt() and Array.Copy().
The results from a release build on my PC are:
LIST Elapsed: 00:00:00.4724841
ARRAY Elapsed: 00:00:00.4670488
LIST Elapsed: 00:00:00.4714016
ARRAY Elapsed: 00:00:00.4675552
LIST Elapsed: 00:00:00.4703538
ARRAY Elapsed: 00:00:00.4698310
which shows the times to be fairly similar.
The test program is:
using System;
using System.Collections.Generic;
using System.Diagnostics;
namespace Demo
{
public static class Program
{
public static void Main()
{
int[] a1 = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 };
int trials = 3;
int iterations = 10000000;
Stopwatch sw = new Stopwatch();
for (int i = 0; i < trials; ++i)
{
for (int j = 0; j < iterations; ++j)
{
List<int> a2 = new List<int>(a1);
sw.Start();
a2.RemoveAt(1);
sw.Stop();
}
Console.WriteLine("LIST Elapsed: " + sw.Elapsed);
sw.Reset();
for (int j = 0; j < iterations; ++j)
{
int[] a3 = new int[a1.Length - 1];
int l = 0;
sw.Start();
for (int k = 0; k < a1.Length; k++)
{
if (a1[k] != 2)
{
a3[l] = a1[k];
l++;
}
}
sw.Stop();
}
Console.WriteLine("ARRAY Elapsed: " + sw.Elapsed);
sw.Reset();
}
}
}
}

central limit theorem

i want to observe central limit theorem and wrote this program.But i confused that,must i observe like that.Is there any wrong ?
xx
xxx
xxxx
xxxxx
xxxxxx
xxx
xxxx
xxx
x
x
namespace ConsoleApplication
{
class Program
{
static void Main(string[] args)
{
Func();
}
public static void Func()
{
Random r = new Random();
int[] d = new int [10];
int sum;
for (int k = 0; k < 5000; k++)
{
sum = 0;
for (int i = 0; i < 50; i++)
sum += r.Next(0, 10000);
Set(d, sum/50);
}
DispResult(d);
}
private static void DispResult(int[] d)
{
for (int i = 0; i < 10; i++)
{
for (int j = 0; j < d[i]; j += 1000)
{
Console.Write("X");
}
Console.WriteLine();
}
}
private static void Set(int[] d, int a)
{
if (a > 9000)
d[9]++;
else if (a > 8000)
d[8]++;
else if (a > 7000)
d[7]++;
else if (a > 6000)
d[6]++;
else if (a > 5000)
d[5]++;
else if (a > 4000)
d[4]++;
else if (a > 3000)
d[3]++;
else if (a > 2000)
d[2]++;
else if (a > 1000)
d[1]++;
else
d[0]++;
}
}
}
It is very unclear what you're asking here but I'll take a stab at it.
Your program simulates rolling a 10000-sided die fifty times and taking the average. You then do that 5000 times and show a histogram of the results.
The Central Limit Theorem states that as the number of rolls increases, the histogram should more closely approximate a Gaussian distribution.
If what you want to do is to observe the truth of the Central Limit Theorem, then I would modify your program as follows: I would make "Func" take an integer n, the number of rolls and then have the body of Main be:
for(int n = 1; n < 10; ++n)
{
Func(n);
Console.WriteLine("-----");
}
Then replace all the "50"s in Func with n.
That way you are simulating rolling 1, 2, 3, 4... 10 dice and taking the average. When you plot the histograms you'll see that for 1, the histogram is rectangular and then it gets more and more bell shaped as n increases. That demonstrates the Central Limit Theorem.

Categories

Resources