SFMT 实战: 第二部分 - C# 中 SIMD 优化快速梅森 Twister 的面向对象实现






4.71/5 (5投票s)
新的SIMD优化快速梅森旋转(SFMT)库是使用面向对象技术开发的,例如UML、组合、聚合、泛化、多重性和设计模式。
目录
- 引言
- 方法
- 早期流程图
- 一体化代码框架和非托管库
- CPUID和CPU功能
- PrngLib项目
- SFMT的策略设计模式
- Sfmt类及其成员
- PrngLib项目的整体Visual Studio类图
- PrngLib项目的类之间的整体关系(UML)
- 一个Windows Forms应用程序:SfmtClient
- SFMT库的好处和重要说明
- 下一步
- 参考文献
引言
最终,经过很长时间,我终于完成了SFMT实战系列的第二部分。本系列的第一部分是关于生成SFMT(SIMD优化快速梅森旋转)DLL以及使用这些DLL生成伪随机数。第一篇文章最重要的亮点是利用CPU的SSE2支持加速生成整数。此外,第一篇SFMT实战文章对于我继续我的工作至关重要。您可以在这里找到第一篇文章。今天,我将解释如何高效地从C#项目中使用C/C++编写的SFMT DLL。
方法
在本文中,我将向我的解决方案添加新的Visual Studio 2008项目。我将在下面描述所有项目及其详细信息。新的项目输出(DLL)将由其他项目使用。此外,我将描述和解释我的项目的面向对象结构。在文章结束时,您将能够通过新的PRNG库从您的C#应用程序生成随机SFMT整数。
早期流程图
下面,您可以看到C# SFMT类的早期版本。它非常原始, far from being in an object-oriented structure. 当前的SFMT类结构和算法已大有改变,但它使用SSE2的概念与旧的SFMT类概念相同。这个旧版本是我当前项目的参考。我在这里发布这个算法的目的是让您对此有所了解。
一体化代码框架和非托管库
请记住:在C#项目中,您不能直接调用C/C++ DLL。C/C++ DLL中的代码是非托管的。此外,您不能将C/C++ DLL作为引用添加到C#项目中。因此,在开始之前,我需要一个连接我的C#库和我的C/C++ SFMT DLL的桥梁。一些有经验的开发人员知道,这很容易通过C#中的[DllImport(_dllLocation)]
属性来实现。但我需要一个基于比仅仅添加此属性更强大概念的面向对象设计。
在2009年中期,我听说了一个名为一体化代码框架的框架。一体化代码框架(代号CodeFx)使用不同编程语言(如Visual C#、VB.NET、Visual C++)中的典型示例代码,描绘了大多数Microsoft开发技术(例如COM、数据访问、IPC)的框架和骨架。您可以在这里查看此框架。在此框架中,有一个C#中的CSLoadLibrary项目,它动态加载其中的一个本地DLL。在此项目中,UnmanagedLibrary
类设计得非常好,并且拥有从C#代码轻松调用C/C++ DLL函数所需的一切。您可以在这里找到该类的全部内容。我将在我的项目中使用此类作为SFMT DLL和我C#代码之间的桥梁。让我们来谈谈这个类的重要部分。
#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
/// <summary>
/// Utility class to wrap an unmanaged DLL and be responsible for freeing it.
/// </summary>
/// <remarks>
/// This is a managed wrapper over the native LoadLibrary, GetProcAddress,
/// and FreeLibrary calls.
/// </example>
/// <see cref=
/// "http://blogs.msdn.com/jmstall/archive/2007/01/06/Typesafe-GetProcAddress.aspx"
/// />
public sealed class UnmanagedLibrary : IDisposable
{
#region Safe Handles and Native imports
// See http://msdn.microsoft.com/msdnmag/issues/05/10/Reliability/
// for more about safe handles.
[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
sealed class SafeLibraryHandle : SafeHandleZeroOrMinusOneIsInvalid
{
/// <summary>
/// Create safe library handle
/// </summary>
private SafeLibraryHandle() : base(true) { } // SafeLibraryHandle
/// <summary>
/// Release handle
/// </summary>
protected override bool ReleaseHandle()
{
return NativeMethods.FreeLibrary(handle);
} // ReleaseHandle()
} // class SafeLibraryHandle
/// <summary>
/// Native methods
/// </summary>
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);
} // class NativeMethods
#endregion // Safe Handles and Native imports
/// <summary>
/// Constructor to load a dll and be responible for freeing it.
/// </summary>
/// <param name="fileName">full path name of dll to load</param>
/// <exception cref="System.IO.FileNotFound">
/// If fileName can't be found
/// </exception>
/// <remarks>
/// Throws exceptions on failure. Most common failure would be
/// file-not-found, or that the file is not a loadable image.
/// </remarks>
public UnmanagedLibrary(string fileName)
{
m_hLibrary = NativeMethods.LoadLibrary(fileName);
if (m_hLibrary.IsInvalid)
{
int hr = Marshal.GetHRForLastWin32Error();
Marshal.ThrowExceptionForHR(hr);
}
} // UnmanagedLibrary(fileName)
/// <summary>
/// Dynamically lookup a function in the dll via kernel32!GetProcAddress.
/// </summary>
/// <param name="functionName">
/// raw name of the function in the export table.
/// </param>
/// <returns>
/// null if function is not found. Else a delegate to the unmanaged
/// function.
/// </returns>
/// <remarks>
/// GetProcAddress results are valid as long as the dll is not yet
/// unloaded. This is very very dangerous to use since you need to
/// ensure that the dll is not unloaded until after you're done with any
/// objects implemented by the dll. For example, if you get a delegate
/// that then gets an IUnknown implemented by this dll, you can not
/// dispose this library until that IUnknown is collected. Else, you may
/// free the library and then the CLR may call release on that IUnknown
/// and it will crash.
/// </remarks>
public TDelegate GetUnmanagedFunction<TDelegate>(string functionName)
where TDelegate : class
{
IntPtr p = NativeMethods.GetProcAddress(m_hLibrary, functionName);
// Failure is a common case, especially for adaptive code.
if (p == IntPtr.Zero)
{
return null;
}
Delegate function = Marshal.GetDelegateForFunctionPointer(
p, typeof(TDelegate));
// Ideally, we'd just make the constraint on TDelegate be
// System.Delegate, but compiler error CS0702
// (constrained can't be System.Delegate)
// prevents that. So we make the constraint system.object and do the
// cast from object-->TDelegate.
object o = function;
return (TDelegate)o;
} // GetUnmanagedFunction(, functionName)
#region IDisposable Members
/// <summary>
/// Call FreeLibrary on the unmanaged dll. All function pointers handed
/// out from this class become invalid after this.
/// </summary>
/// <remarks>
/// This is very dangerous because it suddenly invalidate everything
/// retrieved from this dll. This includes any functions handed out via
/// GetProcAddress, and potentially any objects returned from those
/// functions (which may have an implemention in the dll).
/// </remarks>
public void Dispose()
{
if (!m_hLibrary.IsClosed)
{
m_hLibrary.Close();
}
} // Dispose()
// Unmanaged resource. CLR will ensure SafeHandles get freed, without
// requiring a finalizer on this class.
SafeLibraryHandle m_hLibrary;
#endregion
}
代码逻辑
- P/Invoke API
LoadLibrary
以动态加载本地DLL。 - P/Invoke API
GetProcAddress
以获取DLL导出表中文档化函数的函数指针。 - 调用
Marshal.GetDelegateForFunctionPointer
将函数指针转换为委托对象。 - 调用委托。
- 对非托管DLL调用
FreeLibrary
。
使用示例
//Delegate Method
delegate void aMethod();
//Field
UnmanagedLibrary _unmanagedLibrary;
AMethod _aMethod = null;
public AClass()
{
_unmanagedLibrary = new UnmanagedLibrary(_dllLocation);
_aMethod = _unmanagedLibrary.GetUnmanagedFunction<aMethod>("aMethod");
}
~AClass()
{
_unmanagedLibrary.Dispose();
}
正如您所见,UnmanagedLibrary
实现了System.IDisposable
。因此,当我们创建该类的实例时,我们可以轻松地调用Dispose
方法来立即释放内存。
CPUID和CPU功能
正如您所知,我的目标是提供一个使用SSE2指令集的快速伪随机数生成器。因此,检测操作系统和CPU的SSE2支持至关重要。在Program Files --> Microsoft Visual Studio 9.0 --> Samples文件夹中,您可以找到CPUID.vcproj。这是一个C语言项目,可用于确定正在运行的CPU的功能。Microsoft的原始CPUID项目包含两个重要文件:cpuid.h和cpuid.c。
我在Visual Studio 2008中打开了我的旧SFMT解决方案(您可以在这里下载它)。然后,我点击它,并使用Add --> New project --> Other Languages --> Visual C++ --> Class Library向我的SFMT解决方案添加了一个新的C++项目。我将新C++项目重命名为CPUID。之后,我将cpuid.h和cpuid.c文件添加到这个CPUID项目中。您可以在下面的屏幕截图中看到。
我还创建了一个名为SSE2.cpp的新文件,并向其中添加了三个重要方法。让我们看一下SSE2.cpp的内容。
#include <windows.h>
#include "cpuid.h"
_p_info info;
#ifdef __cplusplus
extern "C" {
#endif
DllExport void getCPUProperties();
DllExport int checkSSE2Feature();
DllExport int checkSSE2Os();
//This one gets CPU properties of your machine
void getCPUProperties()
{
_cpuid(&info);
}
//You must call getCPUProperties before calling this function
//This function checks SSE2 support of your CPU
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;
}
}
//You must call getCPUProperties before calling this function
//This function checks SSE2 support of Operating System
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
第一个函数getCPUProperties
获取机器的CPU属性。第二个函数checkSSE2Feature
检查CPU的SSE2支持,必须在调用getCPUProperties
之后调用。最后一个函数checkSSE2Os
检查操作系统的SSE2支持,必须在调用getCPUProperties
之后调用。由于使用了DllExport
结构,所有这些方法都可以从CPUID.dll外部调用。我在CpuCapabilities
类中使用了这些方法。让我们看看这个类。
CpuCapabilities
CpuCapabilities
是一个C#类,它可以使用UnmanagedLibrary
访问CPUID.dll及其方法。我在编写和调用任何SFMT类结构之前就编写了这个类。我组织了CpuCapabilities
类的结构,以便我可以从其他项目类中使用它。下面,您可以看到CpuCapabilities
类的全部内容。
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;
}
}
可以通过委托方法从CpuCapabilities
类使用CPUID.dll的方法。当类的实例被初始化时,它会获取CPUID.dll的所有方法。
CheckSse2Support
方法是该类中最重要的。它使用该类的其他方法,如_checkSse2Feature
和_checkSse2Os
。如果两者都支持SSE2,则该方法返回true
;否则返回false
。返回false
意味着您不能在使用随机数时利用SSE2支持。此外,在类中,您必须在调用_checkSse2Feature
或_checkSse2Os
方法之前调用_getCpuProperties
方法。
正如我之前所说,CpuCapabilities
类使用UnmanagedLibrary
类来调用CPUID.dll方法。在OOP术语中,CpuCapabilities
类和UnmanagedLibrary
类之间的关系称为组合。正如您在下面看到的,表示包含关系的组合图标显示为带有实心菱形的关联,表示聚合。UnmanagedLibrary
是CpuCapabilities
类的一部分,UnmanagedLibrary
实例的生命周期由CpuCapabilities
类的生命周期管理。换句话说,当创建一个新的CpuCapabilities
实例时,会在CpuCapabilities
类的构造函数中创建一个新的UnmanagedLibrary
实例,并将其传递给CpuCapabilities
类的_unmanagedLibrary
字段。同样,当CpuCapabilities
实例被销毁时,UnmanagedLibrary
实例(CpuCapabilities
类的字段)也会自动被销毁。有关组合的更多信息,请访问这个链接。
PrngLib项目
PrngLib是我的主要项目。为了完成它,我首先在Visual Studio 2008中打开了我的旧SFMT解决方案(您可以在这里下载)。然后,我点击它,并使用Add --> New project --> Visual C# --> Class Library命令向我的SFMT解决方案添加了一个新的C#项目。我将我的新C#项目重命名为PrngLib。我的PrngLib项目将生成一个名为PrngLib.dll的DLL,并且这个DLL可以被C#项目轻松使用。
再次,我点击PrngLib,并使用Add-->Existing item-->UnmanagedLibrary.cs将UnmanagedLibrary
类添加到PrngLib项目中,同样也添加了CpuCapabilities
类。现在,是时候为项目中的SFMT类编写代码了。
SFMT的策略设计模式
设计模式是开发高质量软件的必备条件。它基于OO设计,如今在软件行业中得到广泛应用。
在我的PrngLib项目中,我将使用策略模式。它属于行为模式组,它基本上包括将算法与其宿主解耦,并将算法封装到单独的类中。更简单地说,一个对象及其行为被分离,并放入两个不同的类中。这允许您随时切换正在使用的算法。一般的UML图如图所示。
在我这边,这可以赋予我轻松地在SFMTc和SFMTsse2算法(DLL)之间切换的能力。此外,如果引入了新的算法(未来可能出现更快的新SFMT算法),我可以添加并使用它们。
SfmtStrategy类
起初,我添加了一个名为SfmtStrategy
的新类到我的PrngLib项目中。这个类是一个抽象类,是SFMT算法的一种原型。您可以在下面看到该类的全部代码。正如您所见,所有随机数生成方法都+1重载。
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
字段:protected
,将在派生类中填充。_unmanagedLibrary
字段:protected
,将在派生类中初始化。DllName
属性:用于获取当前SfmtStrategy
类实例使用的DLL的名称。MakeSeed()
方法:用于制作新的SFMT周期的引用方法。将在派生类中实现。在调用任何生成方法之前,必须调用此方法。Generate32BitInt()
方法:生成32位整数并返回。该方法将在派生类中实现。Generate32BitInt(int[] intArray)
方法:生成32位整数,并用这些整数填充intArray
参数。该方法将在派生类中实现。Generate32BitUInt()
方法:生成32位无符号整数并返回。它将在派生类中实现。Generate32BitUInt(uint[] intArray)
方法:生成32位无符号整数,并用这些整数填充intArray
参数。该方法将在派生类中实现。Generate64BitInt()
方法:生成64位整数并返回。它将在派生类中实现。Generate64BitInt(long[] longArray)
方法:生成64位整数,并用这些整数填充longArray
参数。该方法将在派生类中实现。Generate64BitUInt()
方法:生成64位无符号整数并返回。它将在派生类中实现。Generate64BitUInt(ulong[] longArray)
方法:生成64位无符号整数,并用这些整数填充longArray
参数。该方法将在派生类中实现。
现在,是时候添加派生类并在这些类中实现每个抽象方法了。为了实现这一点,我在PrngLib项目中添加了两个新类:SfmtC
和SfmtSse2
。这些类继承自SfmtStrategy
类,并且它们都允许通过我们的C/C++ DLL(当然是通过调用SfmtC.dll和Sfmtsse2.dll)实现自己的算法。
SfmtC类
此类实现了SfmtStrategy
抽象类,并使用SfmtC.dll来生成整数。UnmanagedLibrary
是SfmtC
类的一部分,它们之间的关系是组合。此类使用SfmtC.dll代码,并且不利用硬件加速。整个代码如下。
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类
与SfmtC
类一样,此类实现了SfmtStrategy
抽象类,并使用SfmtSse2.dll来生成整数。UnmanagedLibrary
是SfmtSse2
类的一部分,它们之间的关系是组合。此类使用SfmtSse2.dll代码,并且可以利用硬件加速。整个代码如下。
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
}
}
下面,您可以看到类图以及类之间的关系。在这里,UnmanagedLibrary
是SfmtC
类的一部分,UnmanagedLibrary
实例的生命周期由SfmtC
类的生命周期管理。同样,对于SfmtSse2
-UnmanagedLibrary
类也是如此。SfmtC
和SfmtSse2
类继承自SfmtStrategy
,在OOP术语中,这种关系称为泛化(也称为is a关系)。泛化图标显示为带有封闭箭头的关联。箭头指向超类,关联的另一端表示子类。SfmtStrategy
类是超类,其子类是SfmtC
和SfmtSse2
。有关泛化的更多信息,请访问这个链接。
Sfmt类及其成员
Sfmt
类将是一个前端类,C#应用程序将使用该类及其方法来生成随机整数。为了实现此结构,我首先向我的PrngLib项目添加了一个名为Prng
的新C#抽象类。您可以在下面看到代码。
namespace PrngLib
{
public abstract class Prng
{
protected Boolean _isNumberGenerated;
/// <summary>
/// It realize that the PRN is generated successfully
/// </summary>
public abstract Boolean IsNumberGenerated
{
get;
}
/// <summary>
/// Generates seed for PRNGs.
/// </summary>
public abstract void MakeSeed();
}
}
IsNumberGenerated
:它指示PRN是否已成功生成。MakeSeed
:为PRNG生成种子。
之后,我添加了一个新的C#类并将其重命名为Sfmt
。我使用Visual Studio IDE类图在Prng
类和Sfmt
类之间建立了一个is a关系,所以Sfmt
类是从Prng
抽象类派生的。正如我之前所说,Sfmt
类是我项目中一个重要的类,我向其中添加了一些方法。现在,我将尝试向您解释这个结构。
namespace PrngLib
{
public class Sfmt : Prng
{
#region Fields
private const int _mexp = 19937;
private SfmtStrategy _sfmtStrategy;
private CpuCapabilities _cpuCapabilities;
#endregion
#region Constructors
/// <summary>
/// Constructor.
/// <para>After using this constructor,
/// you should call SetSfmtStrategy method</para>
/// </summary>
public Sfmt()
{
this._cpuCapabilities = new CpuCapabilities();
_isNumberGenerated = false;
}
/// <summary>
/// Constructor.
/// <summary>
/// <param name="sfmtStrategy">
/// Object of a SfmtStrategy class.
/// </param>
public Sfmt(SfmtStrategy sfmtStrategy)
: this()
{
SetSfmtStrategy(sfmtStrategy);
}
#endregion
#region Properties
/// <summary>
/// Mersenne Twister constant
/// </summary>
public int Mexp
{
get
{
return _mexp;
}
}
/// <summary>
/// Realizes that the PRN is generated successfully
/// </summary>
public override bool IsNumberGenerated
{
get { return _isNumberGenerated; }
}
#endregion
#region Methods
/// <summary>
/// Sets Sfmt number generation strategy.
///<para>Allows switching between Sfmt generation strategies.</para>
///<para>Before using any Sfmt method that generate numbers
/// you must call it.</para> <para>Otherwise methods return 0.</para>
/// </summary>
/// <param name="sfmtStrategy">
/// Object of a SfmtStrategy class.
/// </param>
public void SetSfmtStrategy(SfmtStrategy sfmtStrategy)
{
this._sfmtStrategy = sfmtStrategy;
}
/// <summary>
/// Detects most efficient number generation strategy
/// according to OS/CPU and sets it automatically.
/// </summary>
public void AutoSfmtStrategy()
{
if (_cpuCapabilities.CheckSse2Suppport())
SetSfmtStrategy(new SfmtSse2());
else
SetSfmtStrategy(new SfmtC());
}
/// <summary>
/// Generates seed for Sfmt.
/// <para>You must call this method when trying
/// to call any new number generation method</para>
/// </summary>
public override void MakeSeed()
{
if (_sfmtStrategy != null)
{
_sfmtStrategy.MakeSeed();
}
_isNumberGenerated = false;
}
/// <summary>
/// Generates and returns 32 bit signed integer.
///<para>If it fails then returns 0</para>
/// </summary>
public int Generate32BitInt()
{
_isNumberGenerated = false;
int number = 0;
try
{
if (_sfmtStrategy != null)
{
number = _sfmtStrategy.Generate32BitInt();
_isNumberGenerated = true;
}
}
catch (Exception)
{
throw;
}
return number;
}
/// <summary>
/// Generates and fills an array with 32 bit integers.
///<para>If it fails then returns false, otherwise returns true</para>
/// </summary>
/// <param name="intArray">
/// Array that will be filled with 32 bit integers.
/// </param>
public bool Generate32BitInt(int[] intArray)
{
_isNumberGenerated = false;
try
{
if (_sfmtStrategy != null)
{
if (_sfmtStrategy.Generate32BitInt(intArray))
{
_isNumberGenerated = true;
}
}
}
catch (Exception)
{
throw;
}
return _isNumberGenerated;
}
/// <summary>
/// Generates and fills an array with 32 bit unsigned integers.
///<para>If it fails then returns false, otherwise returns true</para>
/// </summary>
/// <param name="intArray">
/// Array that will be filled with 32 bit unsigned integers.
/// </param>
public bool Generate32BitUInt(uint[] intArray)
{
_isNumberGenerated = false;
try
{
if (_sfmtStrategy != null)
{
if (_sfmtStrategy.Generate32BitUInt(intArray))
{
_isNumberGenerated = true;
}
}
}
catch (Exception)
{
throw;
}
return _isNumberGenerated;
}
/// <summary>
/// Generates and returns 32 bit unsigned integer.
///<para>If it fails then returns 0</para>
/// </summary>
public uint Generate32BitUInt()
{
_isNumberGenerated = false;
uint number = 0;
try
{
if (_sfmtStrategy != null)
{
number = _sfmtStrategy.Generate32BitUInt();
_isNumberGenerated = true;
}
}
catch (Exception)
{
throw;
}
return number;
}
/// <summary>
/// Generates and fills an array with 64 bit integers.
///<para>If it fails then returns false, otherwise returns true</para>
/// </summary>
/// <param name="longArray">
/// Array that will be filled with 64 bit integers.
/// </param>
public bool Generate64BitInt(long[] longArray)
{
_isNumberGenerated = false;
try
{
if (_sfmtStrategy != null)
{
if (_sfmtStrategy.Generate64BitInt(longArray))
{
_isNumberGenerated = true;
}
}
}
catch (Exception)
{
throw;
}
return _isNumberGenerated;
}
/// <summary>
/// Generates and fills an array with 64 bit unsigned integers.
///<para>If it fails then returns false, otherwise returns true</para>
/// </summary>
/// <param name="longArray">
/// Array that will be filled with 64 bit unsigned integers.
/// </param>
public bool Generate64BitUInt(ulong[] longArray)
{
_isNumberGenerated = false;
try
{
if (_sfmtStrategy != null)
{
if (_sfmtStrategy.Generate64BitUInt(longArray))
{
_isNumberGenerated = true;
}
}
}
catch (Exception)
{
throw;
}
return _isNumberGenerated;
}
/// <summary>
/// Generates and returns 64 bit signed integer.
///<para>If it fails then returns 0</para>
/// </summary>
public long Generate64BitInt()
{
_isNumberGenerated = false;
long number = 0;
try
{
if (_sfmtStrategy != null)
{
number = _sfmtStrategy.Generate64BitInt();
_isNumberGenerated = true;
}
}
catch (Exception)
{
throw;
}
return number;
}
/// <summary>
/// Generates and returns 64 bit unsigned integer.
///<para>If it fails then returns 0</para>
/// </summary>
public ulong Generate64BitUInt()
{
_isNumberGenerated = false;
ulong number = 0;
try
{
if (_sfmtStrategy != null)
{
number = _sfmtStrategy.Generate64BitUInt();
_isNumberGenerated = true;
}
}
catch (Exception)
{
throw;
}
return number;
}
#endregion
}
}
Sfmt
类及其行为的成员列表如下。我将在A Windows Forms Application: SfmtClient 部分讨论它们。
_mexp
字段:它是一个常量,名为梅森旋转指数。它相当于19937。_sfmtStrategy
字段:用于操作SfmtStrategy
实例。_cpuCapabilities
字段:用于操作CpuCapabilities
实例。Mexp
属性:用于获取我们的Sfmt
类使用的MEXP常量。Sfmt()
构造函数:默认构造函数。使用此构造函数后,应调用SetSfmtStrategy
方法。Sfmt(SfmtStrategy sfmtStrategy)
构造函数:重载构造函数。SetSfmtStrategy(SfmtStrategy sfmtStrategy)
方法:用于设置和更改SFMT数字生成策略。AutoSfmtStrategy()
方法:根据操作系统/CPU检测最高效的数字生成策略,并自动设置它。IsNumberGenerated()
方法:用于检测上次生成数字的尝试是否成功。MakeSeed()
方法:派生自Prng
类,并实现此方法。在尝试调用任何新的数字生成方法时,必须调用此方法。Generate32BitInt()
方法:生成32位整数并返回。它使用SFMT的顺序调用概念。Generate32BitInt(int[] intArray)
方法:生成32位整数,并用这些整数填充intArray
参数。它使用SFMT的块调用概念。Generate32BitUInt()
方法:生成32位无符号整数并返回。它使用SFMT的顺序调用概念。Generate32BitUInt(uint[] intArray)
方法:生成32位无符号整数,并用这些整数填充intArray
参数。它使用SFMT的块调用概念。Generate64BitInt()
方法:生成64位整数并返回。它使用SFMT的顺序调用概念。Generate64BitInt(long[] longArray)
方法:生成64位整数,并用这些整数填充longArray
参数。它使用SFMT的块调用概念。Generate64BitUInt()
方法:生成64位无符号整数并返回。它使用SFMT的顺序调用概念。Generate64BitUInt(ulong[] longArray)
方法:生成64位无符号整数,并用这些整数填充longArray
参数。它使用SFMT的块调用概念。
在下面的屏幕截图中,您可以看到Sfmt
类以及它与其他类的关系。Sfmt
派生自Prng
类(is a关系=泛化)。CpuCapabilities
是Sfmt
的一部分(part of关系=组合)。Sfmt
有一个SfmtStrategy
,在面向对象分析和设计中,SfmtStrategy
和Sfmt
之间的关系称为聚合(也称为has a关系)。聚合图标显示为带有空心菱形的关联,表示聚合。在聚合关系中,类实例的生命周期是独立的。SfmtStrategy
是Sfmt
类的一部分,但SfmtStrategy
实例不受Sfmt
类生命周期的管理。因为SfmtStrategy
实例必须在Sfmt
类外部创建,然后在创建将使用SfmtStrategy
实例的Sfmt
实例之前创建。换句话说,在聚合中,对象可能只包含对另一个对象的引用或指针。有关更多信息,请访问这个链接。
面向对象方法论中的另一个术语是多重性。多重性指定可以在关联(如聚合或组合)之间连接的类的数量。在确定关联一端的数量时,问自己:“另一端类的单个实例可能与此端类的多少个实例相关”?数量符号及其含义如下。
0..1 | 零个实例或一个实例(可选) |
1 | 正好一个实例 |
0..* 或 * | 零个或多个实例 |
1..* | 一个或多个实例(至少一个) |
在我这边,一个SfmtStrategy
实例可能与零个或多个(*符号)Sfmt
实例相关。此外,一个Sfmt
类实例需要仅一个(如下面的1符号所示)SfmtStrategy
类实例。一个CpuCapabilities
实例必须与仅一个(1)Sfmt
实例相关(因为CpuCapabilities
实例的生命周期由Sfmt
实例管理)。
PrngLib的整体类关系(UML)
PrngLib项目的整体Visual Studio类图
现在,在所有这些之后,我构建了我的PrngLib项目并得到了一个PrngLib.dll。这个DLL可以被C#应用程序轻松使用。我不得不编写一个Windows Forms应用程序来测试和使用我的新DLL。
一个Windows Forms应用程序:SfmtClient
要获取一个Windows Forms应用程序,我首先在我的SFMT解决方案中添加了一个新项目(Add --> New Project --> Visual C# --> Windows Forms Application)。之后,在我的窗体上,我添加了一些来自工具箱的组件,并编写了一些代码来测试我的SFMT库。在运行我的应用程序之前,我将所有必要的DLL(SfmtC.dll、SfmtSse2.dll、CPUID.dll、PrngLib.dll)放在SfmtClient的同一目录下。下面是屏幕截图以及我的C#代码。
using PrngLib;
namespace SFMTClient
{
public partial class SfmtClientForm : Form
{
public PrngLib.Sfmt Sfmt;
public SfmtClientForm()
{
InitializeComponent();
Sfmt = new Sfmt();
//also you can use --> new Sfmt(new PrngLib.SfmtC());
}
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();//it's important to call 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;
}
}
}
正如您所见,SfmtClient
可以使用Sfmt
类的所有方法。您可以轻松生成32位-64位整数,以及32位-64位无符号整数。此外,您可以选择生成方法(单独整数或填充数组)。只需将您的数组传递给Sfmt
方法,您就可以轻松地用整数填充您的数组。不要忘记,填充数组方法总是比单独整数方法快得多。填充数组方法使用并需要RAM。因此,如果您有大量可用内存,您可以使用填充数组方法生成更多的数字。另一方面,单独整数方法速度较慢,并且这种方法使用CPU功率来生成数字。此外,您可以确定一个策略。可用的策略是纯C(在后台,它使用SfmtC.dll和C/C++代码)、SSE2支持(在后台,它使用SfmtSse2.dll和CPU指令集的强大功能)和自动(根据操作系统/CPU检测最高效的数字生成策略并自动通过Sfmt
的AutoSfmtStrategy
方法设置)。不要忘记,如果可能在机器上使用SSE2策略,使用此策略总是比使用纯C策略更快。下面,您可以看到如何在Sfmt
类的实例上轻松更改SFMT策略。
if (rbPureC.Checked)
Sfmt.SetSfmtStrategy(new PrngLib.SfmtC());
else if (rbSSE2.Checked)
Sfmt.SetSfmtStrategy(new PrngLib.SfmtSse2());
else if (rbAuto.Checked)
Sfmt.AutoSfmtStrategy();
SFMT库的好处和重要说明
这个新的Sfmt库有一些好处,我想在此提及。
- 生成数字是统计分析和预测的必需品。许多蒙特卡洛模拟和类似的问题需要更快的随机数生成算法。C# SFMT库使您能够将SFMT(SIMD优化快速梅森旋转)的强大功能添加到您的.NET应用程序中。
- 只需用C#编码,您就可以利用CPU的SSE2支持。
- 随着时间的推移,64位应用程序支持不断增长。SFMT库可以轻松为您生成64位数字。此外,除了生成整数外,SFMT库还可以生成无符号整数。
- SFMT库的生成限制仅取决于您的机器限制,如RAM或CPU的性能。某些SFMT实现无法生成某些整数。例如,像“数组大小必须是4的倍数或2的倍数”和“数组大小必须大于或等于(MEXP / 128 + 1)* 4或(MEXP / 128 + 1)* 2”这样的规则被避免,以便生成1313个整数使用填充数组方法。但是,生成总共1313个整数对于SFMT库来说是小菜一碟。
- 在使用SFMT库时,在使用任何生成方法之前或在更改生成方法之间,始终使用
MakeSeed()
。这会重置并清除其他必要的内容。 - 填充数组方法始终比单独整数方法快。填充方法比单独整数方法快20-35倍。特别地,使用SSE2的强大功能配合填充数组方法,与使用纯C配合填充数组方法相比,可以带来大约35-40%的性能提升。您可以在这里看到我在我的机器上实现的性能比较测试结果。
机器配置和其他参数
- Windows Vista Home Premium 32位
- AMD Turion 64X2 移动技术 TL-60 2.00 GHz
- 2046 MB RAM
- 在此测试中生成的整数数量是20,000,000
结果
32位整数 纯C |
32位整数 SSE2支持 |
64位整数 纯C |
64位整数 SSE2支持 |
|
单独整数 | 00:00:08.780 秒 | 00:00:08.700 秒 | 00:00:09.050 秒 | 00:00:08.790 秒 |
填充数组 | 00:00:00.343 秒 | 00:00:00.250 秒 | 00:00:00.650 秒 | 00:00:00.450 秒 |
下一步
可能是使用C#的蒙特卡洛模拟。
如果您对本文和SFMT库有任何疑问,请随时与我联系。
再见。
参考文献
历史
- 2009年10月31日:首次发布。