更快的扑克牌评估
本文关键字:评估 扑克牌 | 更新日期: 2023-09-27 18:33:28
我正在尝试使用"RayW手牌评估器"方法来获得牌组合分数(7张牌中的5张最佳牌(。但是,我在使用此方法时遇到了一些性能问题。据消息人士称 - 使用这种方法必须能够评估每秒超过 300 密耳的手!我的结果是在 1.5 秒内完成 10 次铣削,这要慢很多倍。
"RayW手部评估器"背后的想法如下:
二加二赋值器由一个大型查找表组成,其中包含 大约三千二百万个条目(确切地说是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左右。
有没有其他方法可以提高这个手牌排名系统的性能?
首先 - 基准测试总是很棘手。 在您的机器上以一种方式执行的事情在其他机器上并不总是以相同的方式执行,并且有很多"幕后"可能会使数据无效(例如操作系统甚至硬件完成的缓存(。
话虽如此 - 我只看了一下你的 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 位笔记本电脑 - 这似乎是您期望的速度。 正如其他人所说 - 在发布模式下运行(启用优化(会对性能产生重大影响。
如果你想要通用的速度,我建议使用Brecware的评估器:https://web.archive.org/web/20160502170946/http://brecware.com/Software/software.html。 Steve Brecher的评估器比RayW评估器更快,用于随机顺序进行的评估,并且更加紧凑。
如评论中所述,RayW 评估器的速度取决于参考位置。 如果不按照与查找表完全相同的顺序遍历评估,则会很慢。 如果这是您的问题,有三种方法:
- 使评估顺序与表更匹配。
- 制作符合评估顺序的表格
- 根据您的用例优化评估器。