Contents
At the end, after a long time, I've just finished the second part of the SFMT in Action series. The first part of this series was about producing SFMT (SIMD-oriented Fast Mersenne Twister) DLLs and generating pseudo random numbers using these DLLs. The most important point of the first article was the acceleration of generating integers via the SSE2 support of CPU. Also, the first SFMT In Action article was essential to continue on my work. You can find the first article here. Today, I'll explain how the SFMT DLLs written in C/C++ can be used from C# projects efficiently.
In this article, I'll add new Visual Studio 2008 projects to my solution. I'll describe all of the projects and their details below. The new project outputs (DLLs) will be used by other projects. Also, I'll describe and explain the object-oriented structure of my project. At the end of the article, you'll able to generate random SFMT integers from your C# applications via the new PRNG library.
Below, you can see the early version of the C# SFMT class. It's quite primitive and faraway from being in an object-oriented structure. The current SFMT class structure and algorithm was changed highly, but its concept of using SSE2 is the same as in the old SFMT class concept. This old one was a reference to my current project. My aim to publish this algorithm here is to give you some idea about it.
Please remember this: in a C# project, you couldn't call a C/C++ DLL directly. Code in the C/C++ DLL is unmanaged. Also, you couldn't add a C/C++ DLL as a reference to a C# project. Therefore, before starting, I needed a bridge between my C# library and my C/C++ SFMT DLLs. Some experienced developers know that this is easy with a [DllImport(_dllLocation)]
attribute in C#. But I need an object-oriented design based on a much stronger concept than just adding this attribute.
In the middle of 2009, I heard about a framework named All-In-One Code Framework. All-In-One Code Framework code-named CodeFx delineates the framework and skeleton of most Microsoft development techniques (e.g., COM, Data Access, IPC) using typical sample codes in different programming languages (e.g., Visual C#, VB.NET, Visual C++). You can look into this framework here. In this framework, there is the CSLoadLibrary project in C# which dynamically loads a native DLL in it. In this project, the UnmanagedLibrary
class is designed very well, and has everything for calling C/C++ DLL functions from C# code easily. You can find the entire content of the class here . I'll use this class in my project as a bridge between SFMT DLLs and my C# code. Let's talk about the important parts of this class:
#region Using directives
using Microsoft.Win32.SafeHandles;
using System;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using System.Text;
#endregion
public sealed class UnmanagedLibrary : IDisposable
{
#region Safe Handles and Native imports
[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
sealed class SafeLibraryHandle : SafeHandleZeroOrMinusOneIsInvalid
{
private SafeLibraryHandle() : base(true) { }
protected override bool ReleaseHandle()
{
return NativeMethods.FreeLibrary(handle);
}
}
static class NativeMethods
{
const string s_kernel = "kernel32";
[DllImport(s_kernel, CharSet = CharSet.Auto,
BestFitMapping = false, SetLastError = true)]
public static extern SafeLibraryHandle LoadLibrary(string fileName);
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
[DllImport(s_kernel, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool FreeLibrary(IntPtr hModule);
[DllImport(s_kernel, EntryPoint = "GetProcAddress")]
public static extern IntPtr GetProcAddress(
SafeLibraryHandle hModule, String procname);
}
#endregion // Safe Handles and Native imports
public UnmanagedLibrary(string fileName)
{
m_hLibrary = NativeMethods.LoadLibrary(fileName);
if (m_hLibrary.IsInvalid)
{
int hr = Marshal.GetHRForLastWin32Error();
Marshal.ThrowExceptionForHR(hr);
}
}
public TDelegate GetUnmanagedFunction<TDelegate>(string functionName)
where TDelegate : class
{
IntPtr p = NativeMethods.GetProcAddress(m_hLibrary, functionName);
if (p == IntPtr.Zero)
{
return null;
}
Delegate function = Marshal.GetDelegateForFunctionPointer(
p, typeof(TDelegate));
object o = function;
return (TDelegate)o;
}
#region IDisposable Members
public void Dispose()
{
if (!m_hLibrary.IsClosed)
{
m_hLibrary.Close();
}
}
SafeLibraryHandle m_hLibrary;
#endregion
}
Code Logic
- P/Invoke the API
LoadLibrary
to dynamically load a native DLL. - P/Invoke the API
GetProcAddress
to get the function pointer of a specified function in the DLL's export table. - Call
Marshal.GetDelegateForFunctionPointer
to convert the function pointer to a delegate object. - Call the delegate.
- Call
FreeLibrary
on the unmanaged DLL.
Usage Example
delegate void aMethod();
UnmanagedLibrary _unmanagedLibrary;
AMethod _aMethod = null;
public AClass()
{
_unmanagedLibrary = new UnmanagedLibrary(_dllLocation);
_aMethod = _unmanagedLibrary.GetUnmanagedFunction<aMethod>("aMethod");
}
~AClass()
{
_unmanagedLibrary.Dispose();
}
As you can see, UnmanagedLibrary
implements System.IDisposable
. So, when we make an instance of the class, we can call the Dispose
method easily to free the memory immediately.
As you know, my aim is to provide a fast pseudo random number generator using the SSE2 instruction set. Therefore, it's critical to detect SSE2 support of the OS and CPU. In the Program Files --> Microsoft Visual Studio 9.0 --> Samples folder, you can find CPUID.vcproj. This project is a C language project, and can be used to determine the capabilities of the CPU being run. Microsoft's original CPUID project includes two important files: cpuid.h and cpuid.c.
I opened my old SFMT solution (you can download it here) in Visual Studio 2008. Then, I clicked on it and added a new C++ project to my SFMT solution using Add --> New project --> Other Languages --> Visual C++ --> Class Library. I renamed my new C++ project to CPUID. After that, I added the cpuid.h and cpuid.c files to this CPUID project. You can see the screenshot below.
I also created a new file called SSE2.cpp in the CPUID project and added three important methods to it. Let's see the SSE2.cpp content:
#include <windows.h>
#include "cpuid.h"
_p_info info;
#ifdef __cplusplus
extern "C" {
#endif
DllExport void getCPUProperties();
DllExport int checkSSE2Feature();
DllExport int checkSSE2Os();
void getCPUProperties()
{
_cpuid(&info);
}
int checkSSE2Feature()
{
__try {
if (!info.checks & _CPU_FEATURE_SSE2) {
return 0;
}
if (!info.feature & _CPU_FEATURE_SSE2) {
return 0;
}
if (info.checks & _CPU_FEATURE_SSE2) {
if (info.feature = _CPU_FEATURE_SSE2) {
return 1;
}
}
}
__except (EXCEPTION_EXECUTE_HANDLER) {
return 0;
}
}
int checkSSE2Os()
{
__try {
if (!info.checks & _CPU_FEATURE_SSE2) {
return 0;
}
if (!info.os_support & _CPU_FEATURE_SSE2) {
return 0;
}
if (info.checks & _CPU_FEATURE_SSE2) {
if (info.os_support = _CPU_FEATURE_SSE2) {
return 1;
}
}
}
__except (EXCEPTION_EXECUTE_HANDLER) {
return 0;
}
}
#ifdef __cplusplus
}
#endif
The first function, getCPUProperties
, gets the CPU properties of the machine. The second one, checkSSE2Feature
, checks SSE2 support of your CPU and must be called after getCPUProperties
. The last one, checkSSE2Os
, checks the SSE2 support of the Operating System and must be called after getCPUProperties
. Because of using the DllExport
structure, all these methods can be called outside of CPUID.dll. I used these methods in the CpuCapabilities
class. Let's look into this class:
CpuCapabilities
CpuCapabilities
is a C# class that can access CPUID.dll and its methods using UnmanagedLibrary
. I wrote this class before writing and calling any SFMT class structure. I organized the structure of the CpuCapabilities
class to allow me to use it from other project classes. Below, you can see the entire content of the CpuCapabilities
class:
public class CpuCapabilities
{
#region Delegate Methods
delegate void getCPUProperties();
delegate int checkSSE2Feature();
delegate int checkSSE2Os();
#endregion
#region Fields
protected UnmanagedLibrary _unmanagedLibrary;
getCPUProperties _getCpuProperties = null;
checkSSE2Feature _checkSse2Feature = null;
checkSSE2Os _checkSse2Os = null;
#endregion
public CpuCapabilities()
{
_unmanagedLibrary = new UnmanagedLibrary("CPUID");
_getCpuProperties =
_unmanagedLibrary.GetUnmanagedFunction<getCPUProperties>("getCPUProperties");
_checkSse2Feature =
_unmanagedLibrary.GetUnmanagedFunction<checkSSE2Feature>("checkSSE2Feature");
_checkSse2Os = _unmanagedLibrary.GetUnmanagedFunction<checkSSE2Os>("checkSSE2Os");
}
~CpuCapabilities()
{
_unmanagedLibrary.Dispose();
}
public bool CheckSse2Suppport()
{
_getCpuProperties();
bool support = false;
if ((_checkSse2Feature() == 1) & (_checkSse2Os() == 1))
{
support = true;
}
return support;
}
}
The methods of CPUID.dll can be used from the CpuCapabilities
class via delegate methods. When a new instance of the class is initialized, it gets all the methods from CPUID.dll.
The method CheckSse2Support
is the most important of this class. It uses other methods of the class such as _checkSse2Feature
and _checkSse2Os
. If both of them support SSE2, then the method returns true
; otherwise, it returns false
. Returning false
means that you can't use SSE2 support when generating random numbers. In addition, in the class, you have to call the _getCpuProperties
method before calling the _checkSse2Feature
or _checkSse2Os
methods.
As I said before, the CpuCapabilities
class uses the UnmanagedLibrary
class to call the CPUID.dll methods. In OOP terms, the relationship between the CpuCapabilities
class and the UnmanagedLibrary
class is called composition. As you can see below, the composition icon denoting a containment relationship appears as an association with a filled diamond at the end denoting the aggregate. UnmanagedLibrary
is a part of the CpuCapabilities
class, and the lifecycle of the UnmanagedLibrary
instance is managed by the lifecycle of the CpuCapabilities
class. In other words, when a new instance of CpuCapabilities
is created, a new instance of UnmanagedLibrary
is created in the constructor of the CpuCapabilities
class, and it's passed to the _unmanagedLibrary
field of the CpuCapabilities
class. Similarly, when the CpuCapabilities
instance is destroyed, the UnmanagedLibrary
instance (a field of the CpuCapabilities
class) is destroyed automatically, too. For more information about composition, please visit this link.
PrngLib is my main project. To accomplish it, first of all, I opened my old SFMT solution (you can download it here) in Visual Studio 2008. Then, I clicked on it and added a new C# project to my SFMT solution using Add --> New project --> Visual C# --> Class Library commands, respectively. I renamed my new C# project to PrngLib. My PrngLib project will generate a DLL called PrngLib.dll, and this DLLl can be used by C# projects easily.
Again, I clicked PrngLib and added the UnmanagedLibrary
class using Add-->Existing item-->UnmanagedLibrary.cs to the PrngLib project, and similarly added the CpuCapabilities
class, too. Now, it's time to code for the SFMT classes in the project.
Design patterns are a must have for developing high-quality software. It's based on OO design, and today, they're in use with flying colors at the software industry.
In my PrngLib project, I'll use the Strategy pattern. It's in the Behavioral Patterns group, and it basically consists of decoupling an algorithm from its host, and encapsulating the algorithm into a separate class. More simply, an object and its behaviour are separated, and they're into two different classes. This allows you to switch the algorithm that you are using at any time. The general UML diagram is as shown in the figure:
In my side, this can give me the power of changing between the SFMTc and SFMTsse2 algorithms (DLLs) easily. In addition, I can add and use new algorithms, if they're introduced (may be new and faster SFMT algorithms in future).
SfmtStrategy Class
In the beginning, I added a new class which is named SfmtStrategy
to my PrngLib project. This class is an abstract class and a kind of prototype to SFMT algorithms. You can see the whole code of this class below. As you can see, all the random number generation methods are +1 overloaded.
namespace PrngLib
{
public abstract class SfmtStrategy
{
protected string _dllName = "";
protected UnmanagedLibrary _unmanagedLibrary;
~SfmtStrategy()
{
_unmanagedLibrary.Dispose();
}
public abstract string DllName
{
get;
}
public abstract void MakeSeed();
public abstract int Generate32BitInt();
public abstract bool Generate32BitInt(int[] intArray);
public abstract bool Generate32BitUInt(uint[] intArray);
public abstract uint Generate32BitUInt();
public abstract bool Generate64BitInt(long[] longArray);
public abstract bool Generate64BitUInt(ulong[] longArray);
public abstract long Generate64BitInt();
public abstract ulong Generate64BitUInt();
}
}
_dllName
field: protected
, and will be filled in derived classes._unmanagedLibrary
field: protected
, and will be initialized in derived classes.DllName
property: to get name of the DLL that is used by the current SfmtStrategy
class instance.MakeSeed()
method: reference method to make a new SFMT period. It will be implemented in derived classes. Before making a new call to any generation method, you must call this method.Generate32BitInt()
methods: generates 32 bit integer and returns. The method will be implemented in derived classes.Generate32BitInt(int[] intArray)
method: generates 32 bit integers and fills the intArray
parameter with these integers. The method will be implemented in derived classes.Generate32BitUInt()
method: generates a 32 bit unsigned integer and returns. It will be implemented in derived classes.Generate32BitUInt(uint[] intArray)
method: generates 32 bit unsigned integers and fills the intArray
parameter with these integers. The method will be implemented in derived classes.Generate64BitInt()
method: generates a 64 bit integer and returns. It will be implemented in derived classes.Generate64BitInt(long[] longArray)
method: generates 64 bit integers and fills the longArray
parameter with these integers. The method will be implemented in derived classes.Generate64BitUInt()
method: generates a 64 bit unsigned integer and returns. It will be implemented in derived classes.Generate64BitUInt(ulong[] longArray)
method: generates 64 bit unsigned integers and fills the longArray
parameter with these integers. The method will be implemented in derived classes.
Now, it's time to add the derived classes and implement each abstract method in these classes. To achieve this, I added two new classes to my PrngLib project: SfmtC
and SfmtSse2
. These classes are inherited from the SfmtStrategy
class, and both of them allow to implement their own algorithms via our C/C++ DLLs (by calling SfmtC.dll and Sfmtsse2.dll, of course).
SfmtC Class
This class implements the SfmtStrategy
abstract class, and uses SfmtC.dll to generate integers. UnmanagedLibrary
is a part of the SfmtC
class and the relationship between these is composition. This class uses the SfmtC.dll code, and does not take advantage of hardware acceleration. The whole code is like this:
namespace PrngLib
{
public class SfmtC : SfmtStrategy
{
#region Delegate Functions
delegate void init_gen_rand(uint seed);
delegate uint gen_rand32();
delegate ulong gen_rand64();
delegate int fill_array32(int[] array, int size);
delegate int fill_arrayU32(uint[] array, int size);
delegate int fill_array64(long[] array, int size);
delegate int fill_arrayU64(ulong[] array, int size);
#endregion
#region Fields
init_gen_rand _initGenRand = null;
gen_rand32 _genRand32 = null;
gen_rand64 _genRand64 = null;
fill_array32 _fillArray32 = null;
fill_arrayU32 _fillArrayU32 = null;
fill_array64 _fillArray64 = null;
fill_arrayU64 _fillArrayU64 = null;
#endregion
#region Constructors
public SfmtC()
{
_dllName = "SFMTc";
_unmanagedLibrary = new UnmanagedLibrary(_dllName);
_initGenRand =
_unmanagedLibrary.GetUnmanagedFunction<init_gen_rand>("init_gen_rand");
_genRand32 =
_unmanagedLibrary.GetUnmanagedFunction<gen_rand32>("gen_rand32");
_genRand64 =
_unmanagedLibrary.GetUnmanagedFunction<gen_rand64>("gen_rand64");
_fillArray32 =
_unmanagedLibrary.GetUnmanagedFunction<fill_array32>("fill_array32");
_fillArrayU32 =
_unmanagedLibrary.GetUnmanagedFunction<fill_arrayU32>("fill_array32");
_fillArray64 =
_unmanagedLibrary.GetUnmanagedFunction<fill_array64>("fill_array64");
_fillArrayU64 =
_unmanagedLibrary.GetUnmanagedFunction<fill_arrayU64>("fill_array64");
}
#endregion
#region Properties
public override string DllName
{
get { return _dllName; }
}
#endregion
#region Methods
public override void MakeSeed()
{
_initGenRand((uint)DateTime.Now.Millisecond);
}
public override int Generate32BitInt()
{
return (int)_genRand32();
}
public override bool Generate32BitInt(int[] intArray)
{
bool result = false;
int r = _fillArray32(intArray, intArray.Length);
if (r == 1)
result = true;
return result;
}
public override bool Generate32BitUInt(uint[] intArray)
{
bool result = false;
int r = _fillArrayU32(intArray, intArray.Length);
if (r == 1)
result = true;
return result;
}
public override uint Generate32BitUInt()
{
return _genRand32();
}
public override bool Generate64BitInt(long[] longArray)
{
bool result = false;
int r = _fillArray64(longArray, longArray.Length);
if (r == 1)
result = true;
return result;
}
public override bool Generate64BitUInt(ulong[] longArray)
{
bool result = false;
int r = _fillArrayU64(longArray, longArray.Length);
if (r == 1)
result = true;
return result;
}
public override long Generate64BitInt()
{
return (long)_genRand64();
}
public override ulong Generate64BitUInt()
{
return _genRand64();
}
#endregion
}
}
SfmtSse2 Class
Like the SfmtC
class, this class implements the SfmtStrategy
abstract class, and uses SfmtSse2.dll to generate integers. UnmanagedLibrary
is a part of the SfmtSse2
class, and the relationship between these is composition. This class uses the SfmtSse2.dll code, and can take advantage of hardware acceleration. The whole code is like this:
namespace PrngLib
{
public class SfmtSse2 : SfmtStrategy
{
#region Delegate Functions
delegate void init_gen_rand(uint seed);
delegate uint gen_rand32();
delegate ulong gen_rand64();
delegate int fill_array32(int[] array, int size);
delegate int fill_arrayU32(uint[] array, int size);
delegate int fill_array64(long[] array, int size);
delegate int fill_arrayU64(ulong[] array, int size);
#endregion
#region Fields
init_gen_rand _initGenRand = null;
gen_rand32 _genRand32 = null;
gen_rand64 _genRand64 = null;
fill_array32 _fillArray32 = null;
fill_arrayU32 _fillArrayU32 = null;
fill_array64 _fillArray64 = null;
fill_arrayU64 _fillArrayU64 = null;
#endregion
#region Constructors
public SfmtSse2()
{
_dllName = "SFMTsse2";
_unmanagedLibrary = new UnmanagedLibrary(_dllName);
_initGenRand =
_unmanagedLibrary.GetUnmanagedFunction<init_gen_rand>("init_gen_rand");
_genRand32 =
_unmanagedLibrary.GetUnmanagedFunction<gen_rand32>("gen_rand32");
_genRand64 =
_unmanagedLibrary.GetUnmanagedFunction<gen_rand64>("gen_rand64");
_fillArray32 =
_unmanagedLibrary.GetUnmanagedFunction<fill_array32>("fill_array32");
_fillArrayU32 =
_unmanagedLibrary.GetUnmanagedFunction<fill_arrayU32>("fill_array32");
_fillArray64 =
_unmanagedLibrary.GetUnmanagedFunction<fill_array64>("fill_array64");
_fillArrayU64 =
_unmanagedLibrary.GetUnmanagedFunction<fill_arrayU64>("fill_array64");
}
#endregion
#region Properties
public override string DllName
{
get { return _dllName; }
}
#endregion
#region Methods
public override void MakeSeed()
{
_initGenRand((uint)DateTime.Now.Millisecond);
}
public override int Generate32BitInt()
{
return (int)_genRand32();
}
public override bool Generate32BitInt(int[] intArray)
{
bool result = false;
int r = _fillArray32(intArray, intArray.Length);
if (r == 1)
result = true;
return result;
}
public override bool Generate32BitUInt(uint[] intArray)
{
bool result = false;
int r = _fillArrayU32(intArray, intArray.Length);
if (r == 1)
result = true;
return result;
}
public override uint Generate32BitUInt()
{
return _genRand32();
}
public override bool Generate64BitInt(long[] longArray)
{
bool result = false;
int r = _fillArray64(longArray, longArray.Length);
if (r == 1)
result = true;
return result;
}
public override bool Generate64BitUInt(ulong[] longArray)
{
bool result = false;
int r = _fillArrayU64(longArray, longArray.Length);
if (r == 1)
result = true;
return result;
}
public override long Generate64BitInt()
{
return (long)_genRand64();
}
public override ulong Generate64BitUInt()
{
return _genRand64();
}
#endregion
}
}
Below, you can see the class diagram and the relationship between the classes. Here, UnmanagedLibrary
is a part of the SfmtC
class, and the lifecycle of the UnmanagedLibrary
instance is managed by the lifecycle of the SfmtC
class. Also, this is the same for the SfmtSse2
-UnmanagedLibrary
classes, too. SfmtC
and SfmtSse2
classes are inherited from SfmtStrategy
, and in OOP terms, this kind of relationship is named Generalization (also known as an is a relationship). The generalization icon appears as an association with a closed arrowhead. The arrowhead points to the superclass, and the opposite end of the association designates the subclass. The SfmtStrategy
class is the superclass, and its subclasses are SfmtC
and SfmtSse2
. For more information about Generalization, please visit this link.
The Sfmt
class will be a front-end class, and C# applications will use this class and its methods to generate random integer numbers. To achieve this structure, first of all, I added a new C# abstract class, which is named Prng
, to my PrngLib project. You can see the code below:
namespace PrngLib
{
public abstract class Prng
{
protected Boolean _isNumberGenerated;
public abstract Boolean IsNumberGenerated
{
get;
}
public abstract void MakeSeed();
}
}
IsNumberGenerated
: It indicates if the PRN was generated successfully.MakeSeed
: Generates a seed for PRNGs.
After that, I added a new C# class and renamed it to Sfmt
. I made an is a relationship between the Prng
class and the Sfmt
class using the Visual Studio IDE class diagram, so the Sfmt
class is derived from the Prng
abstract class. As I said before, the Sfmt
class is an important class in my project and I added some methods to it. Now, I'll try to explain this structure to you:
namespace PrngLib
{
public class Sfmt : Prng
{
#region Fields
private const int _mexp = 19937;
private SfmtStrategy _sfmtStrategy;
private CpuCapabilities _cpuCapabilities;
#endregion
#region Constructors
public Sfmt()
{
this._cpuCapabilities = new CpuCapabilities();
_isNumberGenerated = false;
}
public Sfmt(SfmtStrategy sfmtStrategy)
: this()
{
SetSfmtStrategy(sfmtStrategy);
}
#endregion
#region Properties
public int Mexp
{
get
{
return _mexp;
}
}
public override bool IsNumberGenerated
{
get { return _isNumberGenerated; }
}
#endregion
#region Methods
public void SetSfmtStrategy(SfmtStrategy sfmtStrategy)
{
this._sfmtStrategy = sfmtStrategy;
}
public void AutoSfmtStrategy()
{
if (_cpuCapabilities.CheckSse2Suppport())
SetSfmtStrategy(new SfmtSse2());
else
SetSfmtStrategy(new SfmtC());
}
public override void MakeSeed()
{
if (_sfmtStrategy != null)
{
_sfmtStrategy.MakeSeed();
}
_isNumberGenerated = false;
}
public int Generate32BitInt()
{
_isNumberGenerated = false;
int number = 0;
try
{
if (_sfmtStrategy != null)
{
number = _sfmtStrategy.Generate32BitInt();
_isNumberGenerated = true;
}
}
catch (Exception)
{
throw;
}
return number;
}
public bool Generate32BitInt(int[] intArray)
{
_isNumberGenerated = false;
try
{
if (_sfmtStrategy != null)
{
if (_sfmtStrategy.Generate32BitInt(intArray))
{
_isNumberGenerated = true;
}
}
}
catch (Exception)
{
throw;
}
return _isNumberGenerated;
}
public bool Generate32BitUInt(uint[] intArray)
{
_isNumberGenerated = false;
try
{
if (_sfmtStrategy != null)
{
if (_sfmtStrategy.Generate32BitUInt(intArray))
{
_isNumberGenerated = true;
}
}
}
catch (Exception)
{
throw;
}
return _isNumberGenerated;
}
public uint Generate32BitUInt()
{
_isNumberGenerated = false;
uint number = 0;
try
{
if (_sfmtStrategy != null)
{
number = _sfmtStrategy.Generate32BitUInt();
_isNumberGenerated = true;
}
}
catch (Exception)
{
throw;
}
return number;
}
public bool Generate64BitInt(long[] longArray)
{
_isNumberGenerated = false;
try
{
if (_sfmtStrategy != null)
{
if (_sfmtStrategy.Generate64BitInt(longArray))
{
_isNumberGenerated = true;
}
}
}
catch (Exception)
{
throw;
}
return _isNumberGenerated;
}
public bool Generate64BitUInt(ulong[] longArray)
{
_isNumberGenerated = false;
try
{
if (_sfmtStrategy != null)
{
if (_sfmtStrategy.Generate64BitUInt(longArray))
{
_isNumberGenerated = true;
}
}
}
catch (Exception)
{
throw;
}
return _isNumberGenerated;
}
public long Generate64BitInt()
{
_isNumberGenerated = false;
long number = 0;
try
{
if (_sfmtStrategy != null)
{
number = _sfmtStrategy.Generate64BitInt();
_isNumberGenerated = true;
}
}
catch (Exception)
{
throw;
}
return number;
}
public ulong Generate64BitUInt()
{
_isNumberGenerated = false;
ulong number = 0;
try
{
if (_sfmtStrategy != null)
{
number = _sfmtStrategy.Generate64BitUInt();
_isNumberGenerated = true;
}
}
catch (Exception)
{
throw;
}
return number;
}
#endregion
}
}
Members of the Sfmt
class and their acts are listed below. I'll talk about them in the A Windows Forms Application: SfmtClient section.
_mexp
field: it's a constant named Mersenne Twister exponent. It's equivalent to 19937._sfmtStrategy
field: to manipulate the SfmtStrategy
instance._cpuCapabilities
field: to manipulate the CpuCapabilities
instance.Mexp
property: to get the MEXP constant used by our Sfmt
class.Sfmt()
constructor: default constructor. After using this constructor, you should call the SetSfmtStrategy
method.Sfmt(SfmtStrategy sfmtStrategy)
constructor: Overloaded constructor.SetSfmtStrategy(SfmtStrategy sfmtStrategy)
method: to set and change the SFMT number generation strategy.AutoSfmtStrategy()
method: detects the most efficient number generation strategy according to OS/CPU, and sets it automatically.IsNumberGenerated()
method: to detect if the last attempt for generating a number succeeded.MakeSeed()
method: derived from the Prng
class, and implements this method. You must call this method when trying to call any new number generation method.Generate32BitInt()
methods: generates a 32 bit integer and returns. It uses the sequential call concept of SFMT.Generate32BitInt(int[] intArray)
method: generates 32 bit integers, and fills the intArray
parameter with these integers. It uses the block call concept of SFMT.Generate32BitUInt()
method: generates a 32 bit unsigned integer and returns. It uses the sequential call concept of SFMT.Generate32BitUInt(uint[] intArray)
method: generates 32 bit unsigned integers, and fills the intArray
parameter with these integers. It uses the block call concept of SFMT.Generate64BitInt()
method: generates a 64 bit integer and returns. It uses the sequential call concept of SFMT.Generate64BitInt(long[] longArray)
method: generates 64 bit integers, and fills the longArray
parameter with these integers. It uses the block call concept of SFMT.Generate64BitUInt()
method: generates a 64 bit unsigned integer and returns. It uses the sequential call concept of SFMT.Generate64BitUInt(ulong[] longArray)
method: generates 64 bit unsigned integers, and fills the longArray
parameter with these integers. It uses the block call concept of SFMT.
In the screenshot below, you can see the Sfmt
class and its relationship with other classes. Sfmt
is derived from the Prng
class (is a relationship = generalization). CpuCapabilities
is part of Sfmt
(part of relationship = composition). Sfmt
has a SfmtStrategy
, and in object oriented analysis and design, the relationship between SfmtStrategy
and Sfmt
is named aggregation (also known as a has a relationship). The aggregation icon appears as an association with an unfilled diamond at the end denoting the aggregate. In aggregation relationships, the lifecycle of the class instances are independent. SfmtStrategy
is part of the Sfmt
class, but the SfmtStrategy
instance isn't managed by the lifecycle of the Sfmt
class. Because, the SfmtStrategy
instance must be created out of the Sfmt
class, before an Sfmt
instance, which will use the SfmtStrategy
instance, is created. In other words, in aggregation, the object may only contain a reference or pointer to the other object. For more information, please visit this link.
Other term in object oriented methodology is multiplicity. Multiplicity specifies the number of classes that can be connected across an association (such as aggregation or composition). When determining multiplicity for an end of an association, ask yourself the question "How many instances of a class at this end may be associated with a single instance of the class at the other end"? Multiplicity symbols and their meanings are like this:
0..1 | No instances, or one instance (optional) |
1 | Exactly one instance |
0..* or * | Zero or more instances |
1..* | One or more instances (at least one) |
In my side, a SfmtStrategy
instance may be associated with zero or more (* symbol) Sfmt
instances. Also, a Sfmt
class instance needs only one (as shown with an 1 symbol below) SfmtStrategy
class instance. A CpuCapabilities
instance must be associated with only one (1) Sfmt
instance (because the CpuCapabilities
instance lifecycle is managed by the Sfmt
instance).
Now, after all of these, I built my PrngLib project and got a PrngLib.dll. This DLL can be used by C# applications easily. I had to write a Windows Forms application to test and use my new DLL.
To get a Windows Forms application, first of all, I added a new project (Add --> New Project --> Visual C# --> Windows Forms Application) in my SFMT solution. After that, on my form, I added some components from the Toolbox and wrote some piece of code to test my SFMT library. Before running my application, I put all necessary DLLs (SfmtC.dll, SfmtSse2.dll, CPUID.dll, PrngLib.dll) to the same directory of SfmtClient. The screenshot is shown below and also my C# code:
using PrngLib;
namespace SFMTClient
{
public partial class SfmtClientForm : Form
{
public PrngLib.Sfmt Sfmt;
public SfmtClientForm()
{
InitializeComponent();
Sfmt = new Sfmt();
}
private void SfmtClientForm_Load(object sender, EventArgs e)
{
nud1.Value = 50000;
}
private void btnGenerate_Click(object sender, EventArgs e)
{
string result;
int count;
if (cb1000Number.Checked)
count = 1000;
else
count = Convert.ToInt32(nud1.Text);
tb1.Hide();
tb1.Text = "";
this.Cursor = Cursors.WaitCursor;
lblProcessing.Show();
lblResult.Hide();
Application.DoEvents();
try
{
if (rbPureC.Checked)
Sfmt.SetSfmtStrategy(new PrngLib.SfmtC());
else if (rbSSE2.Checked)
Sfmt.SetSfmtStrategy(new PrngLib.SfmtSse2());
else if (rbAuto.Checked)
Sfmt.AutoSfmtStrategy();
DateTime Anow = DateTime.Now;
Sfmt.MakeSeed();
if ((rbSeparate.Checked))
{
if (rb32i.Checked)
for (int i = 0; i < count; i++)
Sfmt.Generate32BitInt();
else
if (rb32ui.Checked)
for (int i = 0; i < count; i++)
Sfmt.Generate32BitUInt();
else
if (rb64i.Checked)
for (int i = 0; i < count; i++)
Sfmt.Generate64BitInt();
else
if (rb64ui.Checked)
for (int i = 0; i < count; i++)
Sfmt.Generate64BitUInt();
}
else
if ((rbFillingArray.Checked))
{
if (rb32i.Checked)
{
int[] myArray = new int[count];
Sfmt.Generate32BitInt(myArray);
if (cb1000Number.Checked)
{
foreach (int item in myArray)
tb1.AppendText(item.ToString() + Environment.NewLine);
tb1.Show();
}
}
else
if (rb32ui.Checked)
{
uint[] myArray = new uint[count];
Sfmt.Generate32BitUInt(myArray);
if (cb1000Number.Checked)
{
foreach (uint item in myArray)
tb1.AppendText(item.ToString() + Environment.NewLine);
tb1.Show();
}
}
else
if (rb64i.Checked)
{
long[] myArray = new long[count];
Sfmt.Generate64BitInt(myArray);
if (cb1000Number.Checked)
{
foreach (long item in myArray)
tb1.AppendText(item.ToString() + Environment.NewLine);
tb1.Show();
}
}
else
if (rb64ui.Checked)
{
ulong[] myArray = new ulong[count];
Sfmt.Generate64BitUInt(myArray);
if (cb1000Number.Checked)
{
foreach (ulong item in myArray)
tb1.AppendText(item.ToString() + Environment.NewLine);
tb1.Show();
}
}
}
}
DateTime Bnow = DateTime.Now;
result = Convert.ToString(Bnow - Anow);
lblResult.Text = count.ToString() +
" count integers generated in " + result;
}
finally
{
this.Cursor = Cursors.Default;
lblProcessing.Hide();
lblResult.Show();
}
}
private void cb1000Number_CheckedChanged(object sender, EventArgs e)
{
nud1.Enabled = !cb1000Number.Checked;
gb3.Enabled = !cb1000Number.Checked;
if (cb1000Number.Checked)
rbFillingArray.Checked = true;
}
}
}
As you can see, SfmtClient
can use all the methods of the Sfmt
class. You can generate 32 - 64 bit integers, and 32 - 64 bit unsigned integers easily. Also, you can choose the generation method (Separate integers or Filling array). Just by passing your array to the Sfmt
methods, you can fill your array with integers easily. Don't forget that the filling array method is always much faster than the separate integers method. The filling array method uses and needs RAM. So, if you have a lot of available memory, you can generate more numbers using the filling array method. On the other hand, the separate integers method is slow, and this concept uses CPU power to generate numbers. In addition, you can determine a strategy. The available strategies are Pure C (in the background, this uses SfmtC.dll and C/C++ code), SSE2 support (in the background, this uses SfmtSse2.dll and the power of the CPU's instruction set), and Auto (detects the most efficient number generation strategy according to the OS/CPU and sets it automatically via Sfmt
's AutoSfmtStrategy
method). Don't forget that, if it's possible to use the SSE2 strategy on the machine, using this strategy is always faster than using the Pure C strategy. Below, you can see how the SFMT strategy can be easily changed on an instance of the Sfmt
class:
if (rbPureC.Checked)
Sfmt.SetSfmtStrategy(new PrngLib.SfmtC());
else if (rbSSE2.Checked)
Sfmt.SetSfmtStrategy(new PrngLib.SfmtSse2());
else if (rbAuto.Checked)
Sfmt.AutoSfmtStrategy();
This new Sfmt library has some benefits and I'd like to mention them:
- Generating numbers is a must for statistical analysis and forecasting. Lots of Monte Carlo Simulations and similar problems need faster random number generation algorithms. The C# SFMT library enables you to add SFMT (SIMD-oriented Fast Mersenne Twister) power to your .NET applications.
- By coding just in C#, you can take advantage of the CPU's SSE2 support.
- Day by day, 64 bit support for applications is growing. The SFMT library can generate 64 bit numbers for you easily. In addition, as well as generating integer numbers, the SFMT library can generate unsigned integer numbers, too.
- Generation limitations of the SFMT library only depends on your machine limitations such as your RAM or the power of the CPU. Some SFMT implementations can't generate some integers. For example, rules like "array size must be a multiple of 4 or multiple of 2" and "array size must be greater than or equal to (MEXP / 128 + 1) * 4 or (MEXP / 128 + 1) * 2" are avoided to generate 1313 integers using the filing array method. But, generating a total 1313 integers is a piece of cake for the SFMT library.
- When working with the SFMT library, always use
MakeSeed()
before using any generation method or before changing between generation methods. This resets and clears other necessary things. - The filling array methods are always faster than the separate integers methods. The filling methods are 20-35 times faster than the separate integers methods. Especially, using the SSE2 power with the filling array methods can give you approximately 35-40% performance gain compared to using Pure C with filling array methods. You can see the performance comparison test results which I have achieved on my machine, here:
Machine Configuration and Other Parameters:
- Windows Vista Home Premium 32 bit
- AMD Turion 64X2 Mobile Technology TL-60 2.00 GHz
- 2046 MB RAM
- Integer count that was generated in this test is 20.000.000
Results:
| 32 bit integers Pure C | 32 bit integers SSE2 support | 64 bit integers Pure C | 64 bit integers SSE2 support |
Separate integers | 00:00:08.780 s | 00:00:08.700 s | 00:00:09.050 s | 00:00:08.790 s |
Filling array | 00:00:00.343 s | 00:00:00.250 s | 00:00:00.650 s | 00:00:00.450 s |
May be a Monte Carlo Simulation using C#.
Don't hesitate to ask me about this article and the SFMT library.
See you later.
History
- October 31, 2009: First release.