65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.71/5 (5投票s)

2009年10月31日

CPOL

18分钟阅读

viewsIcon

48983

downloadIcon

646

新的SIMD优化快速梅森旋转(SFMT)库是使用面向对象技术开发的,例如UML、组合、聚合、泛化、多重性和设计模式。

目录

引言

最终,经过很长时间,我终于完成了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
}

代码逻辑

  1. P/Invoke API LoadLibrary以动态加载本地DLL。
  2. P/Invoke API GetProcAddress以获取DLL导出表中文档化函数的函数指针。
  3. 调用Marshal.GetDelegateForFunctionPointer将函数指针转换为委托对象。
  4. 调用委托。
  5. 对非托管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.hcpuid.c

我在Visual Studio 2008中打开了我的旧SFMT解决方案(您可以在这里下载它)。然后,我点击它,并使用Add --> New project --> Other Languages --> Visual C++ --> Class Library向我的SFMT解决方案添加了一个新的C++项目。我将新C++项目重命名为CPUID。之后,我将cpuid.hcpuid.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类之间的关系称为组合。正如您在下面看到的,表示包含关系的组合图标显示为带有实心菱形的关联,表示聚合。UnmanagedLibraryCpuCapabilities类的一部分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.csUnmanagedLibrary类添加到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项目中添加了两个新类:SfmtCSfmtSse2。这些类继承自SfmtStrategy类,并且它们都允许通过我们的C/C++ DLL(当然是通过调用SfmtC.dllSfmtsse2.dll)实现自己的算法。

SfmtC类

此类实现了SfmtStrategy抽象类,并使用SfmtC.dll来生成整数。UnmanagedLibrarySfmtC类的一部分,它们之间的关系是组合。此类使用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来生成整数。UnmanagedLibrarySfmtSse2类的一部分,它们之间的关系是组合。此类使用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

}
}

下面,您可以看到类图以及类之间的关系。在这里,UnmanagedLibrarySfmtC类的一部分,UnmanagedLibrary实例的生命周期由SfmtC类的生命周期管理。同样,对于SfmtSse2-UnmanagedLibrary类也是如此。SfmtCSfmtSse2类继承自SfmtStrategy,在OOP术语中,这种关系称为泛化(也称为is a关系)。泛化图标显示为带有封闭箭头的关联。箭头指向超类,关联的另一端表示子类。SfmtStrategy类是超类,其子类SfmtCSfmtSse2。有关泛化的更多信息,请访问这个链接。

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关系=泛化)。CpuCapabilitiesSfmt的一部分(part of关系=组合)。Sfmt有一个SfmtStrategy,在面向对象分析和设计中,SfmtStrategySfmt之间的关系称为聚合(也称为has a关系)。聚合图标显示为带有空心菱形的关联,表示聚合。在聚合关系中,类实例的生命周期是独立的。SfmtStrategySfmt类的一部分,但SfmtStrategy实例不受Sfmt类生命周期的管理。因为SfmtStrategy实例必须在Sfmt类外部创建,然后在创建将使用SfmtStrategy实例的Sfmt实例之前创建。换句话说,在聚合中,对象可能只包含对另一个对象的引用或指针。有关更多信息,请访问这个链接。

面向对象方法论中的另一个术语是多重性。多重性指定可以在关联(如聚合或组合)之间连接的类的数量。在确定关联一端的数量时,问自己:“另一端类的单个实例可能与此端类的多少个实例相关”?数量符号及其含义如下。

0..1 零个实例或一个实例(可选)
1 正好一个实例
0..** 零个或多个实例
1..* 一个或多个实例(至少一个)

在我这边,一个SfmtStrategy实例可能与零个或多个*符号)Sfmt实例相关。此外,一个Sfmt类实例需要仅一个(如下面的1符号所示)SfmtStrategy类实例。一个CpuCapabilities实例必须与仅一个1Sfmt实例相关(因为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.dllSfmtSse2.dllCPUID.dllPrngLib.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检测最高效的数字生成策略并自动通过SfmtAutoSfmtStrategy方法设置)。不要忘记,如果可能在机器上使用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库有一些好处,我想在此提及。

  1. 生成数字是统计分析和预测的必需品。许多蒙特卡洛模拟和类似的问题需要更快的随机数生成算法。C# SFMT库使您能够将SFMT(SIMD优化快速梅森旋转)的强大功能添加到您的.NET应用程序中。
  2. 只需用C#编码,您就可以利用CPU的SSE2支持。
  3. 随着时间的推移,64位应用程序支持不断增长。SFMT库可以轻松为您生成64位数字。此外,除了生成整数外,SFMT库还可以生成无符号整数
  4. SFMT库的生成限制仅取决于您的机器限制,如RAM或CPU的性能。某些SFMT实现无法生成某些整数。例如,像“数组大小必须是4的倍数或2的倍数”和“数组大小必须大于或等于(MEXP / 128 + 1)* 4或(MEXP / 128 + 1)* 2”这样的规则被避免,以便生成1313个整数使用填充数组方法。但是,生成总共1313个整数对于SFMT库来说是小菜一碟。
  5. 在使用SFMT库时,在使用任何生成方法之前或在更改生成方法之间,始终使用MakeSeed()。这会重置并清除其他必要的内容。
  6. 填充数组方法始终比单独整数方法快。填充方法比单独整数方法快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日:首次发布。
© . All rights reserved.