我写了一个小程序,用(x,y,z)
计算前18个三元组x<y<z
,满足x^3+y^3=z^3+1
。
在尝试优化总运行时间时,我发现,使用double
表示立方值,方程的两边比使用long
更快。在我的机器上,差异大约是3秒。
现在我想知道为什么会这样。我想这是long
内部处理的某个地方,而两个long
- 变量的比较,因为这是唯一的,它在计算循环中发生变化。
这是我的代码:
class Threes {
public static void main(String[] args) {
System.out.println("Threes --- Java");
int Z_MAX = 60000, Y_MAX = Z_MAX-1, X_MAX = Y_MAX-1;
double[] powers = new double[Z_MAX+1];
for (int i = 0; i <= Z_MAX; i++) {
powers[i] = Math.pow(i, 3);
}
System.out.println("Powers calculated");
int x, y, z;
double right, left;
int[][] sets = new int[18][3];
int foundCount = 0;
long loopCount = 0;
long start, end;
start = System.currentTimeMillis();
for (x = 1 ; x < X_MAX; x++) {
for (y = x + 1; y < Y_MAX; y++) {
right = powers[x] + powers[y];
for (z = y + 1; z < Z_MAX; z++) {
left = powers[z] + 1;
if (right < left) {
z = Z_MAX;
} else if (right == left) {
sets[foundCount][0] = x;
sets[foundCount][1] = y;
sets[foundCount][2] = z;
foundCount++;
end = System.currentTimeMillis();
System.out.println("found " + foundCount + ". set:\t" + x + "\t" + y + "\t" + z + "\t" + ((end - start) / 1000.0));
if (foundCount == 18) {
x = X_MAX;
y = Y_MAX;
z = Z_MAX;
}
}
loopCount++;
}
}
}
System.out.println("finished: " + loopCount);
}
}
我更改的行是:
double[] powers = new double[Z_MAX+1];
变为
long[] powers = new long[Z_MAX+1];
和
powers[i] = Math.pow(i, 3);
变为
powers[i] = (long)Math.pow(i, 3);
和
double right, left;
变为
long right, left;
“奖金问题”:我有什么其他可能性来优化整个代码的总运行时间?我知道,遗漏loopCount
会给我几毫秒的时间。我敢肯定,我必须显着减少循环迭代次数。但是如何?
答案 0 :(得分:8)
如果您使用的是32位操作系统,则长变量的性能可能会更差,因为long是64位类型。例如,对于64位操作系统,Java只能与一个机器指令进行比较,但在32位环境中,它必须使用多个机器指令,因为它当时只能处理32位。
但是对于double来说,这不是必要的,因为32位系统具有64位浮点数的机器指令,即使它们不具有64位整数。
另外,使用代码:
powers[i] = (long)Math.pow(i, 3);
有两个不必要的转换,首先将i(整数)转换为double(这是Math.pow所采用的),然后将返回值转换回64位整数(long)。
答案 1 :(得分:3)
可以公平地说,您的代码大部分时间都花在本节中:
for (z = y + 1; z < Z_MAX; z++) {
left = powers[z] + 1;
if (right < left) {
z = Z_MAX;
}
而且大多数时候,它总会从条件中取出相同的分支。因此,一旦您的代码达到稳定状态(即一旦设置了CPU的分支预测器),运行时将由计算本身控制:依赖性被最小化,因此指令流水线的延迟无关紧要。
在32位机器上,对64位整数类型进行添加和比较比在double
上执行等效操作需要更多指令。 double
计算需要更多周期才能完成,但这并不重要。我们主要是指令吞吐量,而不是延迟。因此整体运行时间会更长。
在进一步优化方面,您可以通过计算right = powers[x] + powers[y] - 1
将+1移动到内循环之外。但优化器已经发现了这一点。
答案 2 :(得分:1)
您最大的“奖励”优化是用以下计算替换z
循环:
z = Math.round(Math.pow(left - 1, 1./3));
并检查是否z > y && left == powers[(int)z] + 1
。
如果您想在所限范围内找到所有三元组,可以采取其他改进措施:
x
z = Z_MAX;
替换为break;
以提前退出循环X_MAX
计算为Math.pow((powers[Z_MAX] + 1)/2, 1./3)
〜= Z_MAX * Math.pow(0.5, 1./3)
,因为如果x
大于z
,Z_MAX
将超过Y_MAX
x
重新计算Math.pow(powers[Z_MAX] - powers[x] + 1, 1./3)/2
为for (z = 1; z < Z_MAX; z++) {
for (y = 1; y < z - 1; y++) {
zy = powers[z] - 1 - powers[y];
x = Math.round(Math.pow(zy, 1./3));
if (x < y && zy == powers[(int)x])
...report triple found;
}
}
{{1}}