比较长的原因比比较双倍慢

时间:2011-06-17 11:35:24

标签: java optimization runtime double long-integer

我写了一个小程序,用(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会给我几毫秒的时间。我敢肯定,我必须显着减少循环迭代次数。但是如何?

3 个答案:

答案 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

如果您想在所限范围内找到所有三元组,可以采取其他改进措施:

  • 以2而不是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大于zZ_MAX将超过Y_MAX
  • 为每个x重新计算Math.pow(powers[Z_MAX] - powers[x] + 1, 1./3)/2for (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; } }
顺便说一句,订购三元组的一种更常见的方法是使用z作为主要排序键,这可能会导致前18个不同,而不是先按x排序。要改变这种情况,你需要让你的外部循环遍历z,这无论如何都会更简单:

{{1}}