Click here to Skip to main content
Click here to Skip to main content
Add your own
alternative version
Go to top

PetriDish: Multi-threading for performance in C#

, 28 May 2008
An investigation into advanced concurrent programming.
petridish_bin.zip
System.Threading.dll
PetriDish.exe
petridish_src.zip
PetriDish
PetriDish
Common
Images
key.png
Properties
Settings.settings
System.Threading.dll
#define LOAD
#define SAVEX

#define DrawImage

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.Diagnostics;
using System.IO;
using System.Reflection;

using Common;

namespace PetriDish
{
	public partial class Form1 : Form
	{
		//static string Filepath = Path.Combine( "../../..", "State." + Engine.X + "x" + Engine.Y + ".bin" );
		static string Filepath = "State." + Engine.X + "x" + Engine.Y + ".bin";

		static int ProcessorCount = Environment.ProcessorCount;

		SynchronizationContext _UIContext = null;

		Stopwatch _Stopwatch = new Stopwatch();
		TimeSpan _PreviousStopwatch = TimeSpan.Zero;

		Process _Process = Process.GetCurrentProcess();
		TimeSpan _InitialProcessor = TimeSpan.Zero;
		TimeSpan _PreviousProcessor = TimeSpan.Zero;
		Modal<int> _ProcessorUsage = new Modal<int>();
		int _ProcessorUsageAverage = 0;

		class Modal<T>
		{
			T[] _Data = new T[ 42 ];
			int _Index = 0;

			public void Add( T o )
			{
				_Data[ _Index ] = o;
				_Index = ( _Index + 1 ) % _Data.Length;
			}

			public T Average { get { return _Data.Modal(); } }
		}

		Modal<double> _FPS = new Modal<double>();
		Modal<long> _PPS = new Modal<long>();
		Modal<int> _SPPPC = new Modal<int>();

		double _afps = 0;
		long _apps = 0;
		int _aspp = 0;
		double _afpspc = 0;
		long _appspc = 0;
		int _aspppc = 0;
		int _Frame = 0;

		public Form1()
		{
			InitializeComponent();

			_UIContext = SynchronizationContext.Current;

			PictureBox box = new PictureBox();
			using ( Stream s = Assembly.GetExecutingAssembly().GetManifestResourceStream( "PetriDish.Images.key.png" ) )
				box.Image = Image.FromStream( s );

			ToolStripItem item = new ToolStripControlHost( box );
			item.Width = box.Image.Width;

			_ShowKey.DropDownItems.Add( item );

			Load += Form1_Load;
			FormClosing += Form1_Closing;
			DoubleBuffered = true;
		}

		private void Form1_Load( object sender, EventArgs e )
		{
			ProcessorCount = ( Engine.ThreadCount < Environment.ProcessorCount ? Engine.ThreadCount : Environment.ProcessorCount );

			Text += " ( " + Engine.X + " x " + Engine.Y + ")";

			_Stopwatch.Start();
			_InitialProcessor = _Process.TotalProcessorTime;

#if LOAD
			if ( File.Exists( Filepath ) ) Engine.Load( Filepath );
#endif
			Engine.Update += Updated;
			Engine.Start();

			int width = Engine.X;
			if ( width < 500 ) width = 500;

			FormPosition.Restore( this );

			ClientSize = new Size( width, _ToolStrip.Height + 100 + Engine.Y );
		}

		private void Form1_Closing( object sender, EventArgs e )
		{
			Engine.Stop();

			FormPosition.Save( this );
		}

		void Updated( object sender, EventArgs e )
		{
			if ( _Frame == 1764 )
#if !SAVE
				if ( !File.Exists( Filepath ) )
#endif
					Engine.Save( Filepath );

			//_UIContext.Post( Updated, null );
			Updated( null );
		}

		void Updated( object state )
		{
			TimeSpan tsStopwatch = _Stopwatch.Elapsed;
			TimeSpan tsProcessor = _Process.TotalProcessorTime;

			TimeSpan tsStopwatchDelta = tsStopwatch - _PreviousStopwatch;
			TimeSpan tsProcessorDelta = tsProcessor - _PreviousProcessor;

			if ( tsStopwatchDelta > TimeSpan.Zero )
			{
				double fps = 1000 / tsStopwatchDelta.TotalMilliseconds;
				long pps = Engine.PixelsPerSecond;
				int threadCount = Engine.ThreadCount;

				_FPS.Add( fps );
				_PPS.Add( pps );
				_SPPPC.Add( ( int ) ( 1000000000d * threadCount / pps ) );

				_afps = _FPS.Average;
				_apps = _PPS.Average;
				_aspppc = _SPPPC.Average;

				_afpspc = _afps / threadCount;
				_appspc = _apps / threadCount;
				_aspp = _aspppc / threadCount;

				_ProcessorUsageAverage = ( int )
					( 100d * (
					( tsProcessor.TotalSeconds - _InitialProcessor.TotalSeconds ) /
					tsStopwatch.TotalSeconds )
					/ ProcessorCount );

				//if ( tsProcessorDelta > TimeSpan.Zero )
				//{
				//    double percent = 100d * ( tsProcessorDelta.TotalMilliseconds / tsStopwatchDelta.TotalMilliseconds ) / Engine.ThreadCount;
				//    _ProcessorUsage.Add( ( int ) percent );
				//    _ProcessorUsageAverage = _ProcessorUsage.Average;
				//}
			}

			_Frame++;

			_PreviousStopwatch = tsStopwatch;
			_PreviousProcessor = tsProcessor;

			Invalidate();
		}

		protected override void OnPaint( PaintEventArgs e )
		{
			//base.OnPaint( e );

			try
			{
#if DrawImage
				lock ( Engine.BitmapLock )
				{
					int x = ( ClientSize.Width - Engine.X ) / 2;
					e.Graphics.DrawImageUnscaled( Engine.Bitmap, x, _ToolStrip.Height + 100 );
				}
#endif
				Font f = SystemFonts.DialogFont;
				Brush b = SystemBrushes.WindowText;

				int dx = 100;
				int x0 = 20;
				int x1 = x0 + dx;
				int x2 = x1 + dx;
				int x3 = x2 + dx;

				int dy = 20;
				int y0 = _ToolStrip.Height + 10;
				int y1 = y0 + dy;
				int y2 = y1 + dy;
				int y3 = y2 + dy;

				e.Graphics.DrawString( "per core", f, b, x1, y0 );
				e.Graphics.DrawString( Engine.ThreadCount + " cores", f, b, x2, y0 );

				e.Graphics.DrawString( "FPS:", f, b, x0, y1 );
				e.Graphics.DrawString( "P/s:", f, b, x0, y2 );
				e.Graphics.DrawString( "ns/P:", f, b, x0, y3 );

				e.Graphics.DrawString( _afpspc.ToString( "N2" ), f, b, x1, y1 );
				e.Graphics.DrawString( _appspc.ToString( "N0" ), f, b, x1, y2 );
				e.Graphics.DrawString( _aspppc.ToString( "N0" ), f, b, x1, y3 );

				e.Graphics.DrawString( _afps.ToString( "N2" ), f, b, x2, y1 );
				e.Graphics.DrawString( _apps.ToString( "N0" ), f, b, x2, y2 );
				e.Graphics.DrawString( _aspp.ToString( "N0" ), f, b, x2, y3 );

				e.Graphics.DrawString( "frames: " + _Frame.ToString( "N0" ), f, b, x3, y0 );
				e.Graphics.DrawString( "runtime: " + ( _PreviousStopwatch.TotalMilliseconds / 1000f ).ToString( "N1" ), f, b, x3, y1 );
				e.Graphics.DrawString( "mean fps: " + ( _Frame / _PreviousStopwatch.TotalSeconds ).ToString( "N2" ), f, b, x3, y2 );
				e.Graphics.DrawString( "mean cpu: " + _ProcessorUsageAverage.ToString( "N0" ), f, b, x3, y3 );
			}
			finally
			{
				Engine.Painted();
			}
		}
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Nicholas Butler

United Kingdom United Kingdom

I built my first computer, a Sinclair ZX80, on my 11th birthday in 1980.
In 1992, I completed my Computer Science degree and built my first PC.
I discovered C# and .NET 1.0 Beta 1 in late 2000 and loved them immediately.
I have been writing concurrent software professionally, using multi-processor machines, since 1995.
 
In real life, I have spent 3 years travelling abroad,
I have held a UK Private Pilots Licence for 20 years,
and I am a PADI Divemaster.
 
I now live near idyllic Bournemouth in England.
 
If you would like help with multithreading, please contact me via my website:
 
 
I can work 'virtually' anywhere!

| Advertise | Privacy | Mobile
Web02 | 2.8.140916.1 | Last Updated 28 May 2008
Article Copyright 2008 by Nicholas Butler
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid