Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/324.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 如何绕过固定时间操作的循环瓶颈?_Java_Performance_Algorithm_Time Complexity - Fatal编程技术网

Java 如何绕过固定时间操作的循环瓶颈?

Java 如何绕过固定时间操作的循环瓶颈?,java,performance,algorithm,time-complexity,Java,Performance,Algorithm,Time Complexity,工作在一个规则不可知的扑克模拟器的乐趣。在枚举中测试瓶颈,对于总是从“惟一”数组中抽出的手,我发现了一个有趣的瓶颈。我测量了在100000000次以下运行每个变体的平均计算时间,然后从100次重复中选择最好的一次,以使JIT和Hotspot发挥其魔力。我发现两种方法在计算时间上存在差异(6ns对27ns) 及 问题肯定是for循环,而不是函数顶部处理乘法的加法。我对此有点困惑,因为我在每个场景中运行相同数量的操作。。。我意识到在这个函数中我总是有6张或更多的卡,所以我把它改为 public in

工作在一个规则不可知的扑克模拟器的乐趣。在枚举中测试瓶颈,对于总是从“惟一”数组中抽出的手,我发现了一个有趣的瓶颈。我测量了在100000000次以下运行每个变体的平均计算时间,然后从100次重复中选择最好的一次,以使JIT和Hotspot发挥其魔力。我发现两种方法在计算时间上存在差异(6ns对27ns)

问题肯定是for循环,而不是函数顶部处理乘法的加法。我对此有点困惑,因为我在每个场景中运行相同数量的操作。。。我意识到在这个函数中我总是有6张或更多的卡,所以我把它改为

public int getRank(int c0, int c1, int c2, int c3, int c4, int c5, int ... cards)

但随着卡片数量的增加,我会遇到同样的瓶颈。有什么方法可以绕过这个事实吗?如果没有,有人能向我解释为什么相同操作数的for循环要慢得多吗?

增强的
for
循环需要设置迭代器,当您只有少量项时,迭代器的成本相对较高

如果您为循环编写了一个传统的
,那么查看您的计时将非常有趣:

for (int i = 0; i < cards.length; ++i)
{
    q |= (cards[i] >> 16);
    product *= (cards[i] & 0xFF);
}
for(int i=0;i>16);
产品*=(卡[i]&0xFF);
}
但即使这样,也可能比第一个示例稍微慢一些,因为存在一些循环开销(增加索引,将其与长度进行比较,并分支到循环的开始)


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

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


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

在我发布之前尝试过这一点-在时间上没有可测量的变化。如果在for循环之前执行“int N=cards.length”,然后使用N而不是cards.length作为循环保护,会怎么样?否则,每次通过循环时,你都必须取消对cards对象的引用,才能找到长度。希思,你刚刚试过改变。也将x的init移到了顶部。“intq=0,i=cards.length,x=0;”-似乎没有任何效果。据我所知,JVM足够聪明,可以自己进行这样的微优化。Jim,你能详细解释一下为什么for循环会增加如此巨大的开销吗?即使运行数十亿次,这些步骤都不应该只需要一个ns。当然,这些步骤需要时间。递增
i
需要获取、递增、存储(除非它保存在寄存器中)。将
i
卡进行比较。长度
需要时间,分支到循环开始也需要时间。即使在2GHz的情况下,每次迭代的循环开销也至少要花费一纳秒。实际上,在每个场景中运行的操作数并不相同。在第一个示例中,如果
刷新[q]>0
唯一[q]>0
,则跳过乘法。在第二个例子中,您总是进行乘法运算。你确定这对时间安排没有影响吗?肯定的。我已经用完全移除的乘法对它进行了测试,在运行时没有任何变化。检查后将其移动到unique只会为每对/两对/一组/船/四元组类型的手增加额外for循环的开销。为了证明它是循环逻辑的一个隐藏方面,而不是循环本身定义的一个操作,我将其中使用的每个变量(x&cards.length)都移动到了没有变化的方法参数,还尝试了从cards.length切换到静态变量,但没有改变。我肯定会更多地研究分支。至于您关于切换到常规for循环的建议,我所能做的最好是将其更改为一个实例变量,或一个方法参数,这两个参数似乎都没有任何影响-还有什么我应该尝试的吗?感谢您对分支的解释,我编写此程序的主要原因是为了了解更多关于时间复杂性和瓶颈的信息。有没有什么地方可以告诉我关于机器实际操作步骤的可靠信息,以及为什么会影响性能?对于7个if语句,我会得到同样的效果吗?@sgrif我的建议是尝试一个实际常数——在本例中是“7”。如果它加速,你知道这是因为JIT展开了你的循环,而减速是由于分支。如果没有,它可能仍然是分支-但是JIT可能不够聪明,无法展开循环。有关分支(mis)预测的更多信息,请参阅:是的,如果语句同样糟糕,那么代价就是CPU错误地猜测代码将采用哪个分支。如果您真的喜欢优化,最好的方法可能是为每种手类型编写硬编码求值器,或者编写一些代码生成字节码并在运行时对其进行JIT。不能使用硬编码常量。尝试它只是为了看看是否有性能差异。跑了23秒,所以它稍微改进了一点。至于硬编码每种类型的手,这绝对是最好的方式来确保
public int getRank(int c0, int c1, int c2, int c3, int c4, int c5, int ... cards)
for (int i = 0; i < cards.length; ++i)
{
    q |= (cards[i] >> 16);
    product *= (cards[i] & 0xFF);
}