更快的扑克手评估

Ale*_*lex 19 c# poker ranking winforms

我正在尝试使用"RayW手评估器"方法来获得卡组合分数(7个中最好的5个卡).但是我在这个方法上遇到了一些性能问题.根据消息来源 - 使用这种方法,必须能够每秒评估超过300密耳的手!我的结果是1.5秒内10毫秒,这慢了很多倍.

"RayW手评估者"背后的想法如下:

Two Plus Two评估器由一个包含大约三千二百万个条目的大型查找表组成(准确地说是32,487,834).为了查找给定的7张牌扑克牌,您可以在此表中跟踪路径,每张卡执行一次查找.当你到达最后一张牌时,这样获得的值是手牌的官方等值

这是代码的样子:

namespace eval
{
public struct TPTEvaluator
{
    public static int[] _lut;

    public static unsafe void Init() // to load a table
    {
        _lut = new int[32487834];
        FileInfo lutFileInfo = new FileInfo("HandRanks.dat");
        if (!lutFileInfo.Exists)
        {throw new Exception("Handranks.dat not found");}

        FileStream lutFile = new FileStream("HandRanks.dat", FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 4096);

        byte[] tempBuffer = new byte[32487834 * 4];
        lutFile.Read(tempBuffer, 0, 32487834 * 4);

        fixed (int* pLut = _lut)
        { Marshal.Copy(tempBuffer, 0, (IntPtr)pLut, 32487834 * 4);}
        tempBuffer = null;
    }

    public unsafe static int LookupHand(int[] cards) // to get a hand strength
    {
        fixed (int* pLut = _lut)
        {
            int p = pLut[53 + cards[0]];
            p = pLut[p + cards[1]];
            p = pLut[p + cards[2]];
            p = pLut[p + cards[3]];
            p = pLut[p + cards[4]];
            p = pLut[p + cards[5]];
            return pLut[p + cards[6]];
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

}

这就是我测试这种方法的方法:

    private void button4_Click(object sender, EventArgs e)
    {
        int[] str = new int[] { 52, 34, 25, 18, 1, 37, 22 };

        int r1 = 0;

        DateTime now = DateTime.Now;
        for (int i = 0; i < 10000000; i++) // 10 mil iterations 1.5 - 2 sec
        { r1 = TPTEvaluator.LookupHand(str);} // here
        TimeSpan s1 = DateTime.Now - now;
        textBox14.Text = "" + s1.TotalMilliseconds;
    }
Run Code Online (Sandbox Code Playgroud)

我相信这种方法最初是用C++实现的,但是C#端口应该更快.有什么方法可以让我在一秒内接近至少1亿手?

到目前为止我尝试了什么:

  • 尝试使用静态和非静态方法 - 没有区别.
  • 尝试使用字典查找而不是数组

    public void ArrToDict(int[] arr, Dictionary<int, int> dic)
    {
        for (int i = 0; i < arr.Length; i++)
        {
            dic.Add(i, arr[i]);
        }
    }
    
    public unsafe static int LookupHandDict(int[] cards)
    {
        int p = dict[53 + cards[0]];
        p = dict[p + cards[1]];
        p = dict[p + cards[2]];
        p = dict[p + cards[3]];
        p = dict[p + cards[4]];
        p = dict[p + cards[5]];
        return dict[p + cards[6]];
    }
    
    Run Code Online (Sandbox Code Playgroud)

10手手的经过时间差了近6倍..

  • 据一位人士透露,他通过删除"不安全"代码将性能提高了200多万.我尝试做同样的事情,但结果几乎相同.

    public static int LookupHand(int[] cards)
    {
            int p = _lut[53 + cards[0]];
            p = _lut[p + cards[1]];
            p = _lut[p + cards[2]];
            p = _lut[p + cards[3]];
            p = _lut[p + cards[4]];
            p = _lut[p + cards[5]];
            return _lut[p + cards[6]];
    }
    
    Run Code Online (Sandbox Code Playgroud)

这是引用:

删除"不安全"的代码部分和c#版本中的一些小调整后,它现在也是大约310 mio.

有没有其他方法来提高这个手牌排名系统的表现?

Rob*_* P. 4

首先 - 基准测试总是很棘手。在您的计算机上以一种方式执行的操作在其他计算机上并不总是以相同的方式执行,并且“幕后”发生的许多事情可能会使数据无效(例如操作系统甚至硬件完成的缓存)。

话虽如此 - 我只看了你的 Init() 方法,它让我摸不着头脑。我发现很难跟上。我使用“不安全”的经验法则是不要使用它,除非绝对必要。我假设这个 Init() 方法被调用一次,对吧?我决定对其进行基准测试:

static void BenchmarkIt(string input, Action myFunc)
{
    myWatch.Restart();
    myFunc();
    myWatch.Stop();

    Console.WriteLine(input, myWatch.ElapsedMilliseconds);
}

BenchmarkIt("Updated Init() Method:  {0}", Init2);
BenchmarkIt("Original Init() Method: {0}", Init1);  
Run Code Online (Sandbox Code Playgroud)

其中 Init1() 是您的原始代码,Init2() 是我重写的代码(为了公平起见,我还多次翻转了顺序)。这是我得到的(在我的机器上)......

更新了 Init() 方法:110

原始 Init() 方法:159

这是我使用的代码。不需要不安全的关键字。

public static void Init2()
{
    if (!File.Exists(fileName)) { throw new Exception("Handranks.dat not found"); }            

    BinaryReader reader = new BinaryReader(File.Open(fileName, FileMode.Open));            

    try
    {
        _lut = new int[maxSize];
        var tempBuffer = reader.ReadBytes(maxSize * 4); 
        Buffer.BlockCopy(tempBuffer, 0, _lut, 0, maxSize * 4);
    }
    finally
    {
        reader.Close();
    }
}
Run Code Online (Sandbox Code Playgroud)

在我看来,这段代码更容易阅读,而且似乎运行得更快。

我知道您可能更关心 LookupHand() 的性能,但我无法做出任何重大改进。我尝试了几种不同的方法,但没有任何帮助。

我能够在 500 毫秒内运行您的代码 100,000,000 次。我正在一台相当强大的 64 位笔记本电脑上运行 - 这似乎是您所期望的速度。正如其他人所说 - 在发布模式下运行(启用优化)会对性能产生很大影响。