在规则无关的扑克模拟器上工作以获得乐趣。测试枚举中的瓶颈,以及总是从“独特”阵列中获取的手,我发现了一个有趣的瓶颈。我测量了运行每个变量低于1,000,000,000次的平均计算时间,然后在100次重复中进行了最佳操作,以允许JIT和Hotspot发挥他们的魔力。我发现
之间的计算时间(6ns vs 27ns)存在差异public int getRank7(int ... cards) {
int q = (cards[0] >> 16) | (cards[1] >> 16) | (cards[2] >> 16) | (cards[3] >> 16) | (cards[4] >> 16) | (cards[5] >> 16) | (cards[6] >> 16);
int product = ((cards[0] & 0xFF) * (cards[1] & 0xFF) * (cards[2] & 0xFF) * (cards[3] & 0xFF) * (cards[4] & 0xFF) * (cards[5] & 0xFF) * (cards[6] & 0xFF));
if(flushes[q] > 0) return flushes[q];
if(unique[q] > 0) return unique[q];
int x = Arrays.binarySearch(products, product);
return rankings[x];
}
和
public int getRank(int ... cards) {
int q = 0;
long product = 1;
for(int c : cards) {
q |= (c >> 16);
product *= (c & 0xFF);
}
if(flushes[q] > 0) return flushes[q];
if(unique[q] > 0) return unique[q];
int x = Arrays.binarySearch(products, product);
return rankings[x];
}
问题肯定是for循环,而不是在函数顶部添加处理乘法。我对此感到困惑,因为我在每个场景中都运行了相同数量的操作......我意识到我在此功能中总是有6张或更多卡片所以我通过将它更改为
public int getRank(int c0, int c1, int c2, int c3, int c4, int c5, int ... cards)
但是随着卡数的增加,我将面临同样的瓶颈。有没有办法解决这个问题,如果没有,有人可以向我解释为什么相同数量的操作的for循环要慢得多?
答案 0 :(得分:3)
我想你会发现最大的不同就是分支。 for循环场景需要在for循环的每次迭代中进行检查和条件分支。你的CPU将尝试预测将采用哪个分支,并相应地预测管道指令,但是当它错误预测时(每个函数调用至少一次,因为循环终止),管道停滞,这是非常昂贵的。
要尝试的一件事是具有固定上限的常规for循环(而不是基于数组长度的循环); Java JRE可以展开这样的循环,这将导致与更高效的版本相同的操作序列。
答案 1 :(得分:0)
增强的for
循环需要设置一个迭代器,当你只有一些项目时这是相对昂贵的。
如果你写了一个传统的for
循环,看看你的时间是多么有趣:
for (int i = 0; i < cards.length; ++i)
{
q |= (cards[i] >> 16);
product *= (cards[i] & 0xFF);
}
但即使这可能比第一个例子略慢,因为有一些循环开销(递增索引,将其与长度进行比较,并分支到循环的开头)。
在任何情况下,循环开销都会为每次迭代添加增量,比较和分支。而这种比较很可能需要指针取消引用才能到达cards.length
。循环开销比你在循环中所做的工作要昂贵得多,这是非常合理的。