4

在一个规则不可知的扑克模拟器上工作以获得乐趣。测试枚举中的瓶颈,以及总是从“唯一”数组中拉出的手,我发现了一个有趣的瓶颈。我测量了运行每个变体低于 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 循环要慢得多吗?

4

2 回答 2

3

我想你会发现最大的区别在于分支。您的 for 循环场景需要在 for 循环的每次迭代中进行检查和条件分支。您的 CPU 将尝试预测将采用哪个分支,并相应地流水线指令,但是当它预测错误时(每个函数调用至少一次,因为循环终止),流水线停止,这是非常昂贵的。

要尝试的一件事是具有固定上限的常规 for 循环(而不是基于数组长度的循环);Java JRE 可能会展开这样的循环,这将导致与您的更高效版本相同的操作序列。

于 2012-05-02T04:37:24.527 回答
0

这种增强for的循环需要设置一个迭代器,当您只有少量项目时,这相对昂贵。

for如果你写了一个传统的循环,看看你的时间安排会很有趣:

for (int i = 0; i < cards.length; ++i)
{
    q |= (cards[i] >> 16);
    product *= (cards[i] & 0xFF);
}

但即使这样也可能比第一个示例稍慢,因为存在一些循环开销(增加索引,将其与长度进行比较,然后分支到循环的开头)。

在任何情况下,循环开销都会为每次迭代增加一个增量、一个比较和一个分支。而且这种比较很可能需要指针取消引用才能到达cards.length. 很可能循环开销比您在循环中所做的工作要昂贵得多。

于 2012-05-02T03:12:25.503 回答