我正在尝试使用“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]];
}
}
}
}
这就是我测试这种方法的方法:
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万左右。
还有其他方法可以提高这种手牌排名系统的性能吗?
答案 0 :(得分:4)
首先 - 基准测试总是很棘手。在你的机器上执行单向操作的东西并不总是在其他机器上以相同的方式执行,并且有很多“封闭式”可能使数据无效(例如操作系统甚至硬件完成的缓存)。 / p>
话虽如此 - 我只看了你的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位笔记本电脑 - 这似乎是你期待的速度。像其他人所说的那样 - 在发布模式下运行(启用优化)会对性能产生很大影响。
答案 1 :(得分:4)
如果你想要通用速度,我建议使用Brecware的评估员:http://www.brecware.com/Software/software.html。 Steve Brecher的评估器比RayW评估器更快,用于以随机顺序进行的评估,并且更加紧凑。
如评论中所述,RayW评估员依赖于其速度的参考位置。如果您没有以与查找表完全相同的顺序遍历评估,那么它将会很慢。如果这是你的问题,有三种方法: