c# - 更快的扑克手评估

标签 c# winforms ranking poker

我正在尝试使用“RayW 手牌评估器”方法来获得纸牌组合分数(7 张牌中最好的 5 张牌)。但是,我在使用此方法时遇到了一些性能问题。根据消息来源 - 使用这种方法,每秒必须可以评估超过 3 亿手牌!我的结果是 1.5 秒内完成 10 次研磨,这要慢很多倍。

“RayW 手部评估器”背后的想法如下:

The Two Plus Two evaluator consists of a large lookup table containing some thirty-two million entries (32,487,834 to be precise). In order to lookup a given 7-card poker hand, you trace a path through this table, performing one lookup per card. When you get to the last card, the value so obtained is the official equivalence value of the hand

代码如下:

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]];
        }
    }
}

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

    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;
    }

我相信此方法最初是在 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]];
    }
    

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]];
    }
    

引用如下:

After removing the "unsafe" code parts and some small adjustments in the c# version it is now also around 310 mio.

还有其他方法可以提高手牌排名系统的性能吗?

最佳答案

首先 - 基准测试总是很棘手。在你的机器上以一种方式执行的事情在其他机器上并不总是以相同的方式执行,并且有很多“隐藏”的事情可以使数据无效(比如操作系统甚至硬件完成的缓存)。

话虽如此 - 我只看了一下您的 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);  

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

Updated Init() Method: 110

Original Init() Method: 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();
    }
}

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

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

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

关于c# - 更快的扑克手评估,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10483993/

相关文章:

mysql - 在 GROUP BY 中使用 LIMIT 来获得每组 N 个结果?

c# - 代码契约(Contract)的好处

.net - 如何立即退出 Windows Forms .NET 应用程序?

c# - C# 中的自动 HTML 标签补全

c# - 将 Combobox.SelectedItem 转换回 DateTime 会导致 "Specified cast is not valid."

arrays - 使用矢量运算在 MATLAB 中按幅度对矢量条目进行排名

python - 如何根据另一列中的日期值范围创建排名列?

c# - 重新分配类索引的数组(引用类型)

c# - 两个 WCF 服务之间的共享类型

c# - PlatformNotSupportedException 使用 .NET 语音识别