当前位置: 首页 > 知识库问答 >
问题:

执行提取方法重构后,代码变慢 6 倍

壤驷文华
2023-03-14

我知道微观基准很难。我并不想建立一个糟糕的微观基准。相反,我在进行(我认为是的)无害重构时遇到了这个问题。下面是问题的简化演示。

该程序构建一个包含一万个随机整数的ArrayList,然后查找元素的和。在该示例中,求和被重复一百万次,以提高所用时间测量中的信噪比。在实际的程序中,有一百万个略有不同的列表,但不管怎样,问题仍然存在。

    < Li > < code > App # arraySumInlined 是重构前的方法版本,在循环体中保持内联求和。 < Li > < code > App # arraySumSubFunctionCall 是将循环体提取到单独方法中的方法版本。

现在,(对我来说)令人惊讶的是arraySumIn⃣需要约7秒,但arraySumSubFunctionCall需要约42秒。在我看来,这似乎是一个令人印象深刻的差异。

如果我取消注释数组摘要数组摘要子功能调用,则它们分别在大约7秒内完成。即数组子子函数调用不再那么慢。

这是怎么回事?有什么更广泛的含义吗?例如,我以前从未想过提取方法重构可以将7秒的方法调用变成42秒的方法调用。

在研究这个问题时,我发现了几个涉及JIT的问题(例如Java方法调用性能和为什么使用流的代码在Java9比Java8运行得快得多?),但它们似乎处理相反的情况:内联代码的性能比单独方法中的代码差。

环境详细信息:Windows 10 x64,Intel Core i3-6100。

λ java -version
openjdk version "11.0.4" 2019-07-16
OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.4+11)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 11.0.4+11, mixed mode)

λ javac -version
javac 11.0.4
import java.util.ArrayList;
import java.util.Random;
import java.util.concurrent.TimeUnit;

public class App {

  public static void main(String[] args) {
    final int size = 10_000;
    final int iterations = 1_000_000;
    final var data = integerListWithRandomValues(size);

    //arraySumInlined(iterations, data);
    arraySumSubFunctionCall(iterations, data);
  }

  private static void arraySumSubFunctionCall(int iterations,
      final ArrayList<Integer> data) {
    final long start = System.nanoTime();
    long result = 0;
    for (int i = 0; i < iterations; ++i) {
      result = getSum(data);
    }
    final long end = System.nanoTime();
    System.out.println(String.format("%f sec (%d)",
        TimeUnit.NANOSECONDS.toMillis(end - start) / 1000.0, result));
  }

  private static void arraySumInlined(int iterations,
      final ArrayList<Integer> data) {
    final long start = System.nanoTime();
    long result = 0;
    for (int i = 0; i < iterations; ++i) {
      result = data.stream().mapToInt(e -> e).sum();
    }
    final long end = System.nanoTime();
    System.out.println(String.format("%f sec (%d)",
        TimeUnit.NANOSECONDS.toMillis(end - start) / 1000.0, result));
  }

  private static int getSum(final ArrayList<Integer> data) {
    return data.stream().mapToInt(e -> e).sum();
  }

  private static ArrayList<Integer> integerListWithRandomValues(final int size) {
    final var result = new ArrayList<Integer>();
    final var r = new Random();

    for (int i = 0; i < size; ++i) {
      result.add(r.nextInt());
    }

    return result;
  }
}

共有2个答案

鲁永福
2023-03-14

值得一提的是,我也做了一些实验,发现当在静态方法中执行时,IntStream上的sum()方法特别适用。我如下调整了您的代码,以便获得每次迭代的平均持续时间:

import java.util.ArrayList;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;

public class App2 {

    public static void main(String[] args) {
        final int size = 10_000;
        final int iterations = 1_000_000;
        final var data = integerListWithRandomValues(size);
        boolean inline = args.length > 0 && "inline".equalsIgnoreCase(args[0]);

        if (inline) {
            System.out.println("Running inline");
        } else {
            System.out.println("Running sub-function call");
        }

        arraySum(inline, iterations, data);
    }

    private static void arraySum(boolean inline, int iterations, final ArrayList<Integer> data) {
        long start;
        long result = 0;
        long totalElapsedTime = 0;

        for (int i = 0; i < iterations; ++i) {
            start = System.nanoTime();
            if (inline) {
                result = data.stream().mapToInt(e -> e).sum();
            } else {
                result = getIntStream(data).sum();
            }
            totalElapsedTime += getElapsedTime(start);
        }
        printElapsedTime(totalElapsedTime/iterations, result);
    }

    private static long getElapsedTime(long start) {
        return TimeUnit.NANOSECONDS.toNanos(System.nanoTime() - start);
    }

    private static void printElapsedTime(long elapsedTime, long result) {
        System.out.println(String.format("%d per iteration (%d)", elapsedTime, result));
    }

    private static IntStream getIntStream(final ArrayList<Integer> data) {
        return data.stream().mapToInt(e -> e);
    }

    private static int getSum(final ArrayList<Integer> data) {
        return data.stream().mapToInt(e -> e).sum();
    }

    private static ArrayList<Integer> integerListWithRandomValues(final int size) {
        final var result = new ArrayList<Integer>();
        final var r = new Random();

        for (int i = 0; i < size; ++i) {
            result.add(r.nextInt());
        }

        return result;
    }
}

一旦我切换到 getIntStream() 静态方法(在尝试其他排列之后),速度就与内联执行时间相匹配。

包丁雨
2023-03-14

我用你的代码做了一些实验,以下是我的结论:

1-如果在main()中先放arraySumSubFunctionCall(),然后放arraySumInlined(),则执行时间会有所不同:

public static void main(String[] args) {
    ...
    arraySumSubFunctionCall(iterations, data);
    arraySumInlined(iterations, data); 
}

这意味着即时编译器优化发生在arraySumIn()中,然后可以应用于arraySumSubFunctionCall()。

2-如果你替换你的常量data.stream(). mapToInt(e-

private static void arraySumInlined(int iterations,
      final ArrayList<Integer> data) {
    ...
    for (int i = 0; i < iterations; ++i) {
      result = new Random().nextInt();
    }
    ...
}


private static int getSum(final ArrayList<Integer> data) {
    return new Random().nextInt();
}

这意味着常量data.stream()。地图输入(电子-

在现实生活中,我认为在本地for循环中重新计算N次相同的值并不经常发生,因此如果需要进行代码准备,您不应该害怕提取方法重构。

 类似资料:
  • 问题内容: 我希望解析Java源代码文件,并提取方法源代码。 我需要这样的方法: 有没有简单的方法可以做到这一点,有一个库可以帮助我构建方法,等等? 问题答案: 从https://javaparser.org/下载Java解析器 您必须编写一些代码。此代码将调用解析器…它将返回一个CompilationUnit: 注意:SEDInputStream是输入流的子类。您可以根据需要使用FileInpu

  • 在开发servlet程序时,我遇到了一个问题,我使用了netbeans ide,并使用了glassfish服务器。这是我的索引代码。html文件:- servlet的代码是:- 我认为问题是在解析变量时发生的。。。拜托,谁能帮我??而且索引文件执行得很好,但当我直接点击按钮而不在文本框中键入任何内容时,它会显示正确的输出,但当我输入程序(abc)中指定的用户名和密码时 HTTP状态500-内部服务

  • 问题内容: 我正在尝试在Glassfish上运行Java Web服务。有一些初始化代码可以设置一些变量并从Glassfish环境本身中检索一些信息。我在@WebService类内的静态初始化程序中具有该代码,但是此代码似乎被调用为时过早,它在WebService端点部署后立即运行,而我需要在成功部署整个Web服务后才能运行。 我尝试将代码移到WebService类的构造函数中,但是只有当我进入Te

  • 我需要重写执行程序的执行方法,我需要改变线程超过核心池大小的行为,只有当队列已满时才会创建。 然而,在实时应用程序中,这种行为是不可取的,因为它会导致队列中存在的任务无休止地等待。 我已将execute方法更改如下: 尝试实现:核心线程-

  • 本文向大家介绍PHP实现动态执行代码的方法,包括了PHP实现动态执行代码的方法的使用技巧和注意事项,需要的朋友参考一下 本文实例讲述了PHP实现动态执行代码的方法。分享给大家供大家参考,具体如下: 这里介绍的PHP动态执行,即在页面上直接输入代码,点击执行,返回执行结果 方法很简单,主要使用了: 函数来实现。 代码如下: 更多关于PHP相关内容感兴趣的读者可查看本站专题:《php操作office文

  • 问题内容: 我正在编写一个swing应用,当执行某些方法时,我希望有“ wait”光标。我们可以这样: 我想要实现的是一个Java批注,该批注将在方法执行之前设置等待游标,并在执行后将其设置回正常状态。所以前面的例子看起来像这样 我怎样才能做到这一点?也欢迎提出有关解决此问题的其他方法的建议。谢谢! PS-我们在项目中使用Google Guice,但我不知道如何使用它来解决问题。如果有人为我提供类