I am new to C# and have a Java-based application that I would like to port to C#. The application can involve thousands of independent worker threads only one of which is in a runnable state at a particular time. An executive thread coordinates the sequencing of execution of the worker threads. This is not a typical multithreaded application so the normal caveats about too many threads do not apply.
With a prototype Java implementation, I can get a total of ~10000 threads and achieve context switching speeds of ~80000 switches/sec.on a 1.4GHz Athlon based PC with 256MB memory.
I took the Java prototype code and converted it to C# using Microsoft's Java Language Conversion Assitant utility.
When I run the program on the same platform, I get the same 10000 threads but the context switching in C# is on the order of 5000 switches/sec. which is an order of magnitude slower than in Java. Even with few threads, the context switches in C# are performed at ~5K/sec.
There is almost a 1-1 correspondence between the Java implementation and the C# implementation.
It appears both runtime environments use the same underlying Windows OS thread model so why is C# so much slower? It appears the problem is related to the acquistion and relinquishment of an object locks in C#. Following is the sample code. It takes the following two command line arguments:
1) number of threads
2) number of context switches to perform
The executive thread creates the specified number of threads and then simply causes each to execute in order (one at a time) by notifying each and then, in turn, waiting to be notified when the worker thread is finished executing. In this case the worker thread doesn't acutally do anything so the program is effectively measuring the method of sychronization and context switching speed.
Can anyone tell me if there is something obviously inefficient about the C# code produced by the translation utility or is sychronization and context switching an achilles heel of C#?
namespace threadtest
{
using System;
public class ThreadTest
{
internal const bool DEBUG = false;
internal const int DEFAULT_THREAD_COUNT = 100;
internal const long DEFAULT_SWITCH_COUNT = 10;
internal static int threadCounter = 0;
internal static int threadCount = DEFAULT_THREAD_COUNT;
internal static long switches = DEFAULT_SWITCH_COUNT;
internal static System.Threading.Thread executiveThread = null;
[STAThread]
public static void Main(System.String[] args)
{
executiveThread = System.Threading.Thread.CurrentThread;
// *** get command line inputs
try
{
if (args.Length > 0)
{
threadCount = System.Int32.Parse(args[0]);
}
if (args.Length > 1)
{
switches = System.Int64.Parse(args[1]);
}
catch (System.FormatException nfe)
{
System.Console.Error.WriteLine("\nError parsing command
line parameters: " + args[0] + " " + args[1]);
}
// *** spawn threads
System.Threading.Thread[] threadPool;
threadPool = new System.Threading.Thread[threadCount];
try
{
for (int i = 1; i <= threadCount; ++i)
{
System.Console.Out.WriteLine("Creating Thread " + i);
threadPool[i - 1] = new System.Threading.Thread(new
System.Threading.ThreadStart(createWorker().Run));
threadPool[i - 1].Start();
}
}
catch (System.OutOfMemoryException oome)
{
System.Console.Error.WriteLine("Out of Memory."
;
System.Environment.Exit(0);
}
// ** Have executive wait for worker threads to catch up (quick and dirty).
try
{
System.Threading.Thread.Sleep(new System.TimeSpan(10000 * 5000));
}
catch (System.Threading.ThreadInterruptedException ie)
{
System.Console.Error.WriteLine("Executive thread interrupted."
;
System.Environment.Exit(0);
}
// *** start indexing through worker threads
System.Console.Error.WriteLine("Starting test."
;
long startTimeMillis = (System.DateTime.Now.Ticks - 621355968000000000) / 10000;
for (int i = 1; i <= switches; ++i)
{
System.Threading.Thread currentEntity = threadPool[(i - 1) % threadCount];
// *** must have entity lock to notify
lock(currentEntity)
{
if (DEBUG)
{
System.Console.Error.WriteLine("Exec has acquired entity thread " + currentEntity.Name + " lock."
;
System.Console.Error.WriteLine("About to notify entity thread " + currentEntity.Name + "."
;
}
// *** notify worker thread
System.Threading.Monitor.Pulse(currentEntity);
try
{
if (DEBUG)
{
System.Console.Error.WriteLine("Exec about to wait for notification."
;
}
// *** wait for worker to finish
System.Threading.Monitor.Wait(currentEntity);
if (DEBUG)
{
System.Console.Error.WriteLine("Exec after being notified."
;
}
}
catch (System.Threading.ThreadInterruptedException ie)
{
System.Console.Error.WriteLine("\nExecutive Thread was Interrupted!!!"
;
System.Environment.Exit(0);
}
} // end lock block
System.Threading.Monitor.Exit( currentEntity );
} // end for
// *** compute stats
long endTimeMillis = (System.DateTime.Now.Ticks - 621355968000000000) / 10000;
long deltaMillis = endTimeMillis - startTimeMillis;
double rate = (double) switches / (deltaMillis / 1000.0);
System.Console.Error.WriteLine(switches + " context switches on " + threadCount + " threads
performed at " + rate + "/sec."
;
System.Console.Error.WriteLine("Executive all done."
;
System.Environment.Exit(0);
} // end Main( )
internal static Worker createWorker()
{
++threadCounter;
return new Worker( null, "Entity_Thread-" + threadCounter);
}
} // end ThreadTest
// *** Here's the worker thread
public class Worker : SupportClass.IThreadRunnable
{
public virtual System.String Name
{
get
{
return name;
}
set
{
name = value;
}
}
internal const bool DEBUG = false;
private System.String name;
public Worker( SupportClass.IThreadRunnable target, System.String nam)
{
Name = nam;
}
public void Run()
{
System.Threading.Thread myThread = System.Threading.Thread.CurrentThread;
lock(myThread)
{
while (true)
{
try
{
if (DEBUG)
{
System.Console.Error.WriteLine("Entity thread " + Name + " going on hold."
;
}
// *** wait to be notified by the executive
System.Threading.Monitor.Wait(myThread);
if (DEBUG)
{
System.Console.Error.WriteLine("Entity thread " + Name + " off hold."
;
}
}
catch (System.Threading.ThreadInterruptedException ie)
{
System.Console.Error.WriteLine("\nEntity Thread was Interrupted!!!"
;
}
// *** work to be done (if any) goes here
if (DEBUG)
{
System.Console.Error.WriteLine("Entity thread " + Name + " doing work."
;
}
// notify the executive that I'm done
System.Threading.Monitor.Pulse(myThread);
if (DEBUG)
{
System.Console.Error.WriteLine("Notified executive."
;
}
} // end while
} // end synchronized
} // end Run( )
} // end Worker
public class SupportClass
{
public interface IThreadRunnable
{
void Run();
}
}
}
With a prototype Java implementation, I can get a total of ~10000 threads and achieve context switching speeds of ~80000 switches/sec.on a 1.4GHz Athlon based PC with 256MB memory.
I took the Java prototype code and converted it to C# using Microsoft's Java Language Conversion Assitant utility.
When I run the program on the same platform, I get the same 10000 threads but the context switching in C# is on the order of 5000 switches/sec. which is an order of magnitude slower than in Java. Even with few threads, the context switches in C# are performed at ~5K/sec.
There is almost a 1-1 correspondence between the Java implementation and the C# implementation.
It appears both runtime environments use the same underlying Windows OS thread model so why is C# so much slower? It appears the problem is related to the acquistion and relinquishment of an object locks in C#. Following is the sample code. It takes the following two command line arguments:
1) number of threads
2) number of context switches to perform
The executive thread creates the specified number of threads and then simply causes each to execute in order (one at a time) by notifying each and then, in turn, waiting to be notified when the worker thread is finished executing. In this case the worker thread doesn't acutally do anything so the program is effectively measuring the method of sychronization and context switching speed.
Can anyone tell me if there is something obviously inefficient about the C# code produced by the translation utility or is sychronization and context switching an achilles heel of C#?
namespace threadtest
{
using System;
public class ThreadTest
{
internal const bool DEBUG = false;
internal const int DEFAULT_THREAD_COUNT = 100;
internal const long DEFAULT_SWITCH_COUNT = 10;
internal static int threadCounter = 0;
internal static int threadCount = DEFAULT_THREAD_COUNT;
internal static long switches = DEFAULT_SWITCH_COUNT;
internal static System.Threading.Thread executiveThread = null;
[STAThread]
public static void Main(System.String[] args)
{
executiveThread = System.Threading.Thread.CurrentThread;
// *** get command line inputs
try
{
if (args.Length > 0)
{
threadCount = System.Int32.Parse(args[0]);
}
if (args.Length > 1)
{
switches = System.Int64.Parse(args[1]);
}
catch (System.FormatException nfe)
{
System.Console.Error.WriteLine("\nError parsing command
line parameters: " + args[0] + " " + args[1]);
}
// *** spawn threads
System.Threading.Thread[] threadPool;
threadPool = new System.Threading.Thread[threadCount];
try
{
for (int i = 1; i <= threadCount; ++i)
{
System.Console.Out.WriteLine("Creating Thread " + i);
threadPool[i - 1] = new System.Threading.Thread(new
System.Threading.ThreadStart(createWorker().Run));
threadPool[i - 1].Start();
}
}
catch (System.OutOfMemoryException oome)
{
System.Console.Error.WriteLine("Out of Memory."
System.Environment.Exit(0);
}
// ** Have executive wait for worker threads to catch up (quick and dirty).
try
{
System.Threading.Thread.Sleep(new System.TimeSpan(10000 * 5000));
}
catch (System.Threading.ThreadInterruptedException ie)
{
System.Console.Error.WriteLine("Executive thread interrupted."
System.Environment.Exit(0);
}
// *** start indexing through worker threads
System.Console.Error.WriteLine("Starting test."
long startTimeMillis = (System.DateTime.Now.Ticks - 621355968000000000) / 10000;
for (int i = 1; i <= switches; ++i)
{
System.Threading.Thread currentEntity = threadPool[(i - 1) % threadCount];
// *** must have entity lock to notify
lock(currentEntity)
{
if (DEBUG)
{
System.Console.Error.WriteLine("Exec has acquired entity thread " + currentEntity.Name + " lock."
System.Console.Error.WriteLine("About to notify entity thread " + currentEntity.Name + "."
}
// *** notify worker thread
System.Threading.Monitor.Pulse(currentEntity);
try
{
if (DEBUG)
{
System.Console.Error.WriteLine("Exec about to wait for notification."
}
// *** wait for worker to finish
System.Threading.Monitor.Wait(currentEntity);
if (DEBUG)
{
System.Console.Error.WriteLine("Exec after being notified."
}
}
catch (System.Threading.ThreadInterruptedException ie)
{
System.Console.Error.WriteLine("\nExecutive Thread was Interrupted!!!"
System.Environment.Exit(0);
}
} // end lock block
System.Threading.Monitor.Exit( currentEntity );
} // end for
// *** compute stats
long endTimeMillis = (System.DateTime.Now.Ticks - 621355968000000000) / 10000;
long deltaMillis = endTimeMillis - startTimeMillis;
double rate = (double) switches / (deltaMillis / 1000.0);
System.Console.Error.WriteLine(switches + " context switches on " + threadCount + " threads
performed at " + rate + "/sec."
System.Console.Error.WriteLine("Executive all done."
System.Environment.Exit(0);
} // end Main( )
internal static Worker createWorker()
{
++threadCounter;
return new Worker( null, "Entity_Thread-" + threadCounter);
}
} // end ThreadTest
// *** Here's the worker thread
public class Worker : SupportClass.IThreadRunnable
{
public virtual System.String Name
{
get
{
return name;
}
set
{
name = value;
}
}
internal const bool DEBUG = false;
private System.String name;
public Worker( SupportClass.IThreadRunnable target, System.String nam)
{
Name = nam;
}
public void Run()
{
System.Threading.Thread myThread = System.Threading.Thread.CurrentThread;
lock(myThread)
{
while (true)
{
try
{
if (DEBUG)
{
System.Console.Error.WriteLine("Entity thread " + Name + " going on hold."
}
// *** wait to be notified by the executive
System.Threading.Monitor.Wait(myThread);
if (DEBUG)
{
System.Console.Error.WriteLine("Entity thread " + Name + " off hold."
}
}
catch (System.Threading.ThreadInterruptedException ie)
{
System.Console.Error.WriteLine("\nEntity Thread was Interrupted!!!"
}
// *** work to be done (if any) goes here
if (DEBUG)
{
System.Console.Error.WriteLine("Entity thread " + Name + " doing work."
}
// notify the executive that I'm done
System.Threading.Monitor.Pulse(myThread);
if (DEBUG)
{
System.Console.Error.WriteLine("Notified executive."
}
} // end while
} // end synchronized
} // end Run( )
} // end Worker
public class SupportClass
{
public interface IThreadRunnable
{
void Run();
}
}
}