Click here to Skip to main content
15,885,546 members
Articles / Programming Languages / C#

PetriDish: Multi-threading for performance in C#

Rate me:
Please Sign up or sign in to vote.
4.95/5 (46 votes)
28 May 2008CPOL13 min read 125.3K   1.3K   144  
An investigation into advanced concurrent programming.
#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)


Written By
United Kingdom United Kingdom
I discovered C# and .NET 1.0 Beta 1 in late 2000 and loved them immediately.
I have been writing software professionally in C# ever since

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.

I can work 'virtually' anywhere!

Comments and Discussions