19

我正在尝试使用“RayW 手牌评估器”方法来获得卡片组合分数(7 张中的 5 张最佳卡片)。但是,我在使用这种方法时遇到了一些性能问题。据消息人士称——使用这种方法,每秒必须能够评估超过 3 亿只手!我的结果是 1.5 秒内 10 次磨机,这要慢很多倍。

“RayW 手部评估器”背后的理念如下:

二加二评估器由一个包含大约 3200 万个条目(准确地说是 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]];
        }
    }
}

}

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

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

这是报价:

在删除了“不安全”的代码部分并在 c# 版本中进行了一些小调整后,它现在也在 310 mio 左右。

有没有其他方法可以提高这个手牌排名系统的性能?

4

2 回答 2

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

其中 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();
    }
}

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

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

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

于 2012-05-07T20:26:02.913 回答
4

如果您想要通用速度,我建议您使用 Brecware 的评估器:https ://web.archive.org/web/20160502170946/http://brecware.com/Software/software.html 。对于以随机顺序进行的评估,Steve Brecher 的评估器比 RayW 评估器更快,并且更紧凑。

如评论中所述,RayW 评估器的速度取决于参考的位置。如果您没有以与查找表完全相同的顺序遍历评估,那么它会很慢。如果这是您的问题,则有三种方法:

  1. 使您的评估顺序与表格更加匹配。
  2. 制作符合您的评估顺序的表格
  3. 使评估器针对您的用例进行优化。
于 2012-05-09T00:38:23.117 回答