gpt4 book ai didi

java - 当 x = 0 时,Java 的 Math.pow(x, 2) 性能不佳

转载 作者:塔克拉玛干 更新时间:2023-11-03 04:08:19 25 4
gpt4 key购买 nike

背景

注意到我正在处理的 Java 程序的执行速度比预期的慢,我决定修改我认为可能导致问题的代码区域 - 调用 Math.pow(x, 2)从 for 循环中。与another questions on this site相反,我创建的一个简单基准测试(最后的代码)发现用 x*x 替换 Math.pow(x, 2) 实际上使循环加速了近 70 倍:

x*x: 5.139383ms
Math.pow(x, 2): 334.541166ms

请注意,我知道该基准并不完美,并且肯定应该对这些值持保留态度 - 基准的目的是获得一个大概的数字。

问题

虽然基准测试给出了有趣的结果,但它并没有准确地对我的数据建模,因为我的数据主要由 0 组成。因此,更准确的测试是在没有标记为可选的 for 循环的情况下运行基准测试。根据 Math.pow() 的 javadoc

If the first argument is positive zero and the second argument is greater than zero, or the first argument is positive infinity and the second argument is less than zero, then the result is positive zero.

所以预计该基准测试会运行得更快,对吧!?然而实际上,这又要慢得多:

x*x: 4.3490535ms
Math.pow(x, 2): 3082.1720006ms

当然,人们可能认为 math.pow() 代码比简单的 x*x 代码运行得慢一点,因为它需要在一般情况下工作,但慢了 700 倍?到底是怎么回事!?为什么 0 的情况比 Math.random() 的情况慢得多?

更新:根据@Stephen C 的建议更新了代码和时间。然而,这没什么区别。

用于基准测试的代码

请注意,重新排序两个测试的差异可以忽略不计。

public class Test {
public Test(){
int iterations = 100;
double[] exampleData = new double[5000000];
double[] test1Results = new double[iterations];
double[] test2Results = new double[iterations];

//Optional
for (int i = 0; i < exampleData.length; i++) {
exampleData[i] = Math.random();
}

for (int i = 0; i < iterations; i++) {
test1Results[i] = test1(exampleData);
test2Results[i] = test2(exampleData);
}
System.out.println("x*x: " + calculateAverage(test1Results) / 1000000 + "ms");
System.out.println("Math.pow(x, 2): " + calculateAverage(test2Results) / 1000000 + "ms");
}

private long test1(double[] exampleData){
double total = 0;
long startTime;
long endTime;
startTime = System.nanoTime();
for (int j = 0; j < exampleData.length; j++) {
total += exampleData[j] * exampleData[j];
}
endTime = System.nanoTime();
System.out.println(total);
return endTime - startTime;
}

private long test2(double[] exampleData){
double total = 0;
long startTime;
long endTime;
startTime = System.nanoTime();
for (int j = 0; j < exampleData.length; j++) {
total += Math.pow(exampleData[j], 2);
}
endTime = System.nanoTime();
System.out.println(total);
return endTime - startTime;
}

private double calculateAverage(double[] array){
double total = 0;
for (int i = 0; i < array.length; i++) {
total += array[i];
}
return total/array.length;
}

public static void main(String[] args){
new Test();
}
}

最佳答案

虽然这是一个糟糕的基准,但幸运的是它揭示了一个有趣的效果。

这些数字表明您显然是在“客户端”VM 下运行基准测试。它没有非常强大的 JIT 编译器(称为 C1 编译器),缺乏很多优化。难怪它的效果不如预期。

  • 客户端 VM 不够智能,无法消除 Math.pow 调用,即使它没有副作用也是如此。
  • 此外,对于 Y=2X=0,它都没有专门的快速路径。至少,在 Java 9 之前它没有。这最近已在 JDK-8063086 中修复。然后在JDK-8132207进一步优化.

但有趣的是 Math.pow 对于 X=0 和 C1 编译器确实更慢!

但是为什么?由于实现细节。

x86 架构不提供计算 X^Y 的硬件指令。但是还有其他有用的说明:

  • FYL2X 计算 Y * log₂X
  • F2XM1 计算 2^X - 1

因此,X^Y = 2^(Y * log₂X)。由于 log2X 仅针对 X > 0 定义,FYL2XX=0 的异常结束并返回 -Inf。因此,X=0 是在一条缓慢的异常路径中处理的,而不是在专门的快速路径中处理的。

那怎么办?

首先,停止使用 Client VM,尤其是在您关心性能的情况下。切换到最新的 64 位 JDK 8,您将获得最佳的 C2 优化 JIT 编译器。当然,它可以很好地处理 Math.pow(x, 2) 等。然后写一个 correct benchmark使用适当的工具,如 JMH .

关于java - 当 x = 0 时,Java 的 Math.pow(x, 2) 性能不佳,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34189749/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com