Prime Number smack down.

Alfred Thompson and I tend to have “friendly” talks about programming and for a while we’ve been attempting to figure out who was the better coder.  Someone gave us the challenge of find quickest way to figure out the first prime number after 2.2 billion.  Turns out even if you’re on a cruise ship filled with PhD’s, seemed like no one knew of a better way off the top of their head other than brute force.  Alfred used VB.Net, I used c#.  Alfred’s VB.Net prime number solution is posted over at his blog on MSDN.  Alfred’s solution and my should perform almost exactly the same even though he used Visual Basic and I used c#.  This is since .Net languages all compile down to the same intermediate language.  His tool is VB, mine is c#. 

BUT my solution should win since I cheated.  We both used brute force to solve it BUT I used threads which meant I could get a solution faster than he could since I was maxing out both my cores on my laptop instead of one.  I would have used the Parallel Extensions but I lacked the install and without internet, I had to create them the hard way.

Explaining the brute force method., we go 1 by 1, verifying if a number is divisible by it.  Lets take the number 12345 as it is semi large.  I know it   The number can be factored by 3, 5, 15 … and I stop trying when I hit 111 since 111 * 111 = 12321.  Since I’m only interested in integers, 112 is past 12345 and isn’t a valid number.  So I can check 3 to the floor of the square root of the number I’m attempting to find, incrementing by 2.  Since to determine prime-ness, it must be an odd number or 2.  Wikipedia has a few other, better, faster ways to determine the primality of a number.  The fasted known method is the Elliptic curve primality test.

If you’d like to see the c# source code, you can download it here:  http://www.peacelovecode.com/code/primenumbers/NextBiggestPrimeNumber.zip

What I did for my solution was used an abstract class and had both a threaded and non-threaded solution.  This does the actual computation and notification.  If I wanted to upgrade my solution’s performance, I just needed to tweak here.  I also implemented events so I could update a UI to know that work was actually being executed.

Best of all, since my application uses the abstract, I can do some casting and do a very nice implementation call like this to execute my work:

IModelPrimeNumber prime;

if( chkThreaded.Checked )
	prime = new PrimeNumberThreaded(Convert.ToUInt64(numPrimeNumberStartingPoint.Value), 2);
else
	prime = new PrimeNumber(Convert.ToUInt64(numPrimeNumberStartingPoint.Value));
public abstract class IModelPrimeNumber 
{
	public delegate void PrimeNumberEventHandler(object sender, ulong number);
	public delegate void PrimeNumberNotFoundEventHandler(object sender, ulong number, ulong divisibleBy);
	public event PrimeNumberEventHandler NextAttemptedPrimeNumberEvent;
	public event PrimeNumberEventHandler FoundPrimeNumberEvent;
	public event PrimeNumberNotFoundEventHandler PrimeNumberNotFoundEvent;

	protected bool _speedResult;

	protected void InvokePrimeNumberNotFoundEvent(ulong number, ulong divisibleBy)
	{
		var createdEvent = PrimeNumberNotFoundEvent;
		if (createdEvent != null)
			createdEvent(this, number, divisibleBy);
	}

	protected void InvokeNextAttemptedPrimeNumberEvent(ulong number)
	{
		var createdEvent = NextAttemptedPrimeNumberEvent;
		if (createdEvent != null)
			createdEvent(this, number);
	}

	protected void InvokeFoundPrimeNumberEvent(ulong number)
	{
		var createdEvent = FoundPrimeNumberEvent;
		if (createdEvent != null)
			createdEvent(this, number);
	}

	public virtual TimeSpan StartWorking(bool speedResult)
	{
		return TimeSpan.MaxValue;
	}

	protected bool IsNumberPrime(ulong currentNumberFactoring, ulong calculateStop)
	{	
		bool primeNumber = true;

		if (!_speedResult)
			InvokeNextAttemptedPrimeNumberEvent(currentNumberFactoring);

		for (ulong i = 3; i <= calculateStop; i += 2)
		{
			if (currentNumberFactoring % i != 0)
				continue;

			primeNumber = false;
			if (!_speedResult)
				InvokePrimeNumberNotFoundEvent(currentNumberFactoring, i);
			break;
		}

		return primeNumber;
	}
}

Since this is an abstract and I have virtual functions, I can override them!  Here is how my threaded solution turned out.  Notice how closely the engine is to my queuing system for my bartender.

public class PrimeNumberThreaded : IModelPrimeNumber
{
	readonly ulong _startingPoint;
	public bool IsRunning { get; set; }

	public PrimeNumberThreaded(ulong startingPoint, int totalThreads)
	{
		_startingPoint = startingPoint;

		if (_startingPoint % 2 == 0)
			_startingPoint++;

		_totalThreads = totalThreads;
		for (int i = 0; i < totalThreads; i++)
			ThreadPool.QueueUserWorkItem(FakeWorkToStartUpThreads);
	}

	private static void FakeWorkToStartUpThreads(object state)
	{
		return;
	}

	readonly AutoResetEvent _workWait = new AutoResetEvent(false);
	int _workerThreads;
	readonly int _totalThreads;
	public override TimeSpan StartWorking(bool speedResult)
	{
		var watch = new Stopwatch();
		var currentNumberFactoring = _startingPoint;
		_speedResult = speedResult;

		watch.Start();
	
		while (!_foundPrime)
		{
			ThreadPool.QueueUserWorkItem(DoWork, currentNumberFactoring);
			currentNumberFactoring += 2;
			if (_workerThreads > _totalThreads)
				_workWait.WaitOne();
			_workerThreads++;
		}

		watch.Stop();
		return watch.Elapsed;
	}

	bool _foundPrime;
	private void DoWork(object state)
	{
		var currentNumberFactoring = (ulong)state;
		var calculateStop = Convert.ToUInt64(Math.Floor(Math.Sqrt(currentNumberFactoring)));
		var primeNumber = IsNumberPrime(currentNumberFactoring, calculateStop);

		if (_workerThreads < _totalThreads)
			_workerThreads--;

		if (!primeNumber)
		{
			_workWait.Set();
			return;
		}

		_foundPrime = true;
		InvokeFoundPrimeNumberEvent(currentNumberFactoring);
		_workWait.Set();
	}
}

Ben Murrell May 6, 2009 @ 7:35 PM

# re: Prime Number smack down.
What kind of times were you getting with this? I implemented a version of the Sieve of Eratosthenes with MPI in plain C++. With 8 compute nodes, it took 11.85 seconds to determine all of the primes between 1 and 1 billion.

I just rebuilt the code on the newly upgraded Missouri S&T cluster, and I've queued jobs for 16 and 32 compute nodes to compute all of the primes between 1 and 2.2 billion (with a little modification, I could shoot for the next prime after 2.2 billion... but I have a final tomorrow :P).

It'll be interesting to see if with 32 compute nodes, I can calculate all of the primes from 1 to 2.2 billion faster than 2 cores with shared memory can find the next prime after 2.2 billion.

Ben Murrell May 6, 2009 @ 7:47 PM

# re: Prime Number smack down.
Nevermind - I ran your code, and since the first prime is only 9 after 2.2 billion, the brute force approach isn't bad at all. With 32 compute nodes, I was getting to 512 million in 1.58 seconds.

I'll still post back with the results after my jobs get through the queue though.

Clint May 6, 2009 @ 8:24 PM

# re: Prime Number smack down.
@Ben, I did find the next prime number, I can alter it to find the all the primes between 3 and 1 billion.

Times are relative as your processor could be faster or slower than mine. I should try this out on my quadcore desktop.

Bob Rutkas May 7, 2009 @ 1:19 PM

# re: Prime Number smack down.
This gives me a heartburn!

Alfred Thompson May 13, 2009 @ 9:49 AM

# re: Prime Number smack down.
I really like how you added the treading option. I am thinking of doing something like that were when I look for a group of primes I use a second tread to do the output so I see intermediate results sooner and with less impact (I hope) on the computing thread. Of course that tread is so CPU intensive I'm not sure how well that will work.

Clint Jun 4, 2009 @ 9:08 PM

# re: Prime Number smack down.
@Alfred I'm betting a Sieve of Eratosthenes would be pretty fast even for 2.2 billion. I don't think you'd have a out of memory issue ... You'd only have to an array to lets say 48,000 to be safe then cycle each odd number past 2.2 billion verifying it isn't divisible by any number in that array. If it is, then you know it isn't prime. Far faster than what we did.

Post a Comment

Please add 1 and 1 and type the answer here: