剖析Java代码改变执行时间

问题描述 投票:0回答:1

我正试图优化我的代码,但它给我带来了问题.我有这个对象列表。

List<DataDescriptor> descriptors;

public class DataDescriptor {
    public int id;
    public String name;
}

有1700个对象 有唯一的id(0 -1699)和一些名字, 它用来解码我以后得到的数据类型.

我尝试优化的方法是这样的。

    public void processData(ArrayList<DataDescriptor> descriptors, ArrayList<IncomingData> incomingDataList) {
        for (IncomingData data : incomingDataList) {
            DataDescriptor desc = descriptors.get(data.getDataDescriptorId());

            if (desc.getName().equals("datatype_1")) {
                 doOperationOne(data);
            } else if (desc.getName().equals("datatype_2")) {
                 doOperationTwo(data);
            } else if ....
                .
                .
            } else if (desc.getName().equals("datatype_16")) {
                 doOperationSixteen(data);
            }
        }
    }

这个方法在处理数据文件时被调用了大约一百万次 每次incomingDataList包含大约60个元素 所以这组ifelses被执行了大约60次

这在我的桌面上(i7-8700)大约需要15秒。

将代码改为测试整数id而不是字符串,显然可以减少几秒钟的时间,这很好,但我希望能有更多的时间 :) 我试着用VisualVM进行分析,但是对于这个方法(有字符串测试),它说66%的时间花在 "Self time"(我相信所有这些字符串测试都是这样的吗?),而33%的时间花在了 descriptors.get - 这是简单的从ArrayList获取,我不认为我可以进一步优化它,除了尝试改变数据在内存中的结构(仍然,这是Java,所以我不知道这是否会有很大的帮助)。

我写了一个 "简单基准 "的应用来隔离这个String与int的比较。正如我所预料的那样,当我简单地运行应用程序时,比较整数比String.equals快10倍左右,但当我在VisualVM中对它进行剖析时(我想检查在基准测试中ArrayList.get是否也会如此缓慢),奇怪的是这两种方法花费的时间完全相同。当使用VisualVM的Sample,而不是Profile时,应用程序完成了预期的结果(ints快了10倍),但VisualVM显示在他的样本中,两种类型的比较花费的时间是一样的。

剖析和不剖析时得到的结果完全不同,是什么原因呢?我知道有很多因素,有JIT和profiling也许会干扰它等等。- 但归根结底,当剖析工具改变了代码的运行方式时,你该如何剖析和优化Java代码呢?(如果是这样的话)

java performance profiling
1个回答
2
投票

剖析器可以分为两类:仪器和采样。VisualVM包含了这两类,但它们都有缺点。

仪器化剖析器 使用字节码工具来修改类。他们基本上在每个方法的进入和退出中插入特殊的跟踪代码。这样就可以记录所有执行的方法和它们的运行时间。然而,这种方法有一个很大的开销:首先,因为跟踪代码本身可能会花费很多时间(有时甚至比原始代码更多);其次,因为工具化的代码变得更加复杂,并且阻止了某些可以应用于原始代码的JIT优化。

采样分析器 是不同的。它们不会修改你的应用程序;相反,它们会定期获取应用程序正在做的事情的快照,即当前运行线程的堆栈痕迹。在这些堆栈痕迹中,某个方法出现的频率越高--这个方法的总执行时间就越长(统计学上)。

采样剖析器的开销通常要小得多;此外,这种开销是可控的,因为它直接取决于剖析间隔,即剖析器获取线程快照的频率。

采样剖析器的问题是,JDK获取堆栈痕迹的公共API存在缺陷。JVM并不是在任何任意时刻获取堆栈跟踪。而是在它知道如何可靠地走栈的预定义地方之一停止线程。这些地方被称为 安全点. 安全点位于方法退出处(不包括内联方法),以及循环内部(不包括短计数循环)。这就是为什么,如果你有很长的线性和平代码或短的计数循环,你永远不会在依赖JVM标准的采样剖析器中看到它。getStackTrace API。

这个问题被称为 安全点偏差. 它在下列文件中得到了很好的描述 伟业 由Nitsan Wakart。VisualVM不是唯一的受害者。许多其他的剖析器,包括商业工具,也遭受同样的问题,因为原来的问题在JVM而不是在某个剖析工具中。

Java飞行记录器 要好得多,只要它不依赖于安全点。然而,它也有自己的缺陷:例如,当一个线程在执行某些JVM固有方法时,它无法获得堆栈跟踪,如 System.arraycopy. 这一点尤其令人失望,因为 arraycopy 是Java应用中经常出现的瓶颈。

试试 async-profiler. 这个项目的目标正是为了解决上述问题。async-profiler可以在Linux和macOS上运行。如果你在Windows上,JFR仍然是你最好的选择。

© www.soinside.com 2019 - 2024. All rights reserved.