我正在分析我的应用程序 - 它是一个基于Java构建的宏系统。我正在使用hprof来分析一些基本示例,以下是花费时间的前20个函数:
rank self accum count trace method
1 14.73% 14.73% 453 303755 java.lang.Object.<init>
2 9.75% 24.48% 300 303754 java.lang.Object.<init>
3 7.18% 31.66% 221 303641 java.lang.Object.<init>
4 6.83% 38.49% 210 303621 java.util.ArrayList.<init>
5 6.76% 45.25% 208 303620 java.util.ArrayList.<init>
6 5.95% 51.20% 183 303833 java.lang.Character.toChars
7 4.55% 55.75% 140 303809 java.lang.Object.<init>
8 4.42% 60.18% 136 303791 java.lang.Object.<init>
9 3.77% 63.95% 116 303756 java.lang.Object.<init>
10 3.64% 67.59% 112 300509 java.lang.Object.<init>
11 2.67% 70.25% 82 303789 java.lang.Object.<init>
12 2.54% 72.79% 78 303790 java.lang.Object.<init>
13 1.69% 74.48% 52 303688 java.lang.Object.<init>
14 1.66% 76.14% 51 303644 java.lang.Object.<init>
15 1.46% 77.60% 45 305935 java.lang.Object.<init>
16 1.40% 79.00% 43 303758 java.lang.Object.<init>
17 1.20% 80.20% 37 305324 java.lang.Object.<init>
18 0.98% 81.18% 30 302559 java.lang.Object.<init>
19 0.62% 81.79% 19 300006 java.util.Arrays.copyOf
20 0.52% 82.31% 16 305214 java.lang.Object.<init>
如您所见,大部分时间花费在Object.<init>
。这对我来说有些模糊。
我的直觉是时间是由内存分配完成的。在C中工作让我强烈感觉到动态内存分配(即malloc()
)是低效的。然而,在Java中,福音似乎是JVM有效地处理短期对象;因此,从对象池等模式中无法获得任何东西。
我应该补充说,应用程序中性能最高的部分是解析器,它确实创建了大量短期对象作为其操作的一部分。
您认为Object.<init>
所花费的时间是多少?它确实与内存分配有关吗?我可以使用对象池或其他技巧来减少内存分配吗?
编辑:
在回答Mike Dunlavey的回答时,这里是JPerfAnal对hprof输出的解释,给出了包容性时间。
Method Times by Caller (times inclusive): 3076 ticks
1: java.lang.Object.<init>: 71,26% (2192 inclusive / 2191 exclusive)
2: com.sun.demo.jvmti.hprof.Tracker.ObjectInit: 0,03% (1 inclusive / 0 exclusive)
3: java.lang.Thread.currentThread: 0,03% (1 inclusive / 1 exclusive)
1: parser.ParseData.<init>: 34,33% (1056 inclusive / 0 exclusive)
2: parser.ParseErrors.<init>: 13,98% (430 inclusive / 1 exclusive)
3: java.lang.Object.<init>: 7,18% (221 inclusive / 221 exclusive)
3: java.util.ArrayList.<init>: 6,76% (208 inclusive / 208 exclusive)
2: java.lang.Object.<init>: 13,52% (416 inclusive / 416 exclusive)
2: java.util.ArrayList.<init>: 6,83% (210 inclusive / 0 exclusive)
3: java.util.ArrayList.<init>: 6,83% (210 inclusive / 210 exclusive)
1: parser.Matcher.parse: 34,33% (1056 inclusive / 0 exclusive)
2: parser.ParseData.<init>: 34,33% (1056 inclusive / 0 exclusive)
3: parser.ParseErrors.<init>: 13,98% (430 inclusive / 1 exclusive)
4: java.lang.Object.<init>: 7,18% (221 inclusive / 221 exclusive)
4: java.util.ArrayList.<init>: 6,76% (208 inclusive / 208 exclusive)
3: java.lang.Object.<init>: 13,52% (416 inclusive / 416 exclusive)
3: java.util.ArrayList.<init>: 6,83% (210 inclusive / 0 exclusive)
4: java.util.ArrayList.<init>: 6,83% (210 inclusive / 210 exclusive)
1: java.util.ArrayList.<init>: 28,38% (873 inclusive / 419 exclusive)
2: java.util.AbstractList.<init>: 14,76% (454 inclusive / 0 exclusive)
3: java.util.AbstractCollection.<init>: 14,76% (454 inclusive / 0 exclusive)
4: java.lang.Object.<init>: 14,76% (454 inclusive / 454 exclusive)
(JPerfAnal还会生成一个倒置的树,其中孩子是父母的来电者。为简洁起见,我不会重现它,但足以说大约有40%的Object.<init>
次呼叫来自初始化ArrayList
,ParseData
和ParseErrors
。)
现在,这并没有真正改变我对这个问题或我的问题的看法。我可以改变算法,以便实例化更少的对象;但目前,我正在寻找正交解决方案。那么:对象池可以帮助我吗?
答案 0 :(得分:1)
1)“自我”时间几乎总是无用的,因为真正的问题在于您的代码中调用那些低级方法 - 获得包容性时间(自我+被调用者)
2)“累积”栏目更加无用,因为它所做的就是加上“自我”时间。
其他专栏也告诉你没什么用处。
hprof
获取堆栈跟踪。
你需要的是那些尽可能深的人
你不需要大量的(与流行的统计错误信息相反) - 十个可能绰绰有余
但是,您需要查看它们并理解它们。
Here's some general info about profiling.
编辑以响应显示JPerfAnal输出的编辑:
a)看起来堆栈深度仅为4.您需要尽可能深地查看堆栈深度。 我不关心他们呈现输出的方式。 我主要只是看看堆栈样本本身。 时间不是真正重要的事情。重要的是发生了什么以及为什么。
b)堆栈样本实际上包含行级信息。因此,他们会指出您的日常工作中的精确行,例如parser.ParseData.<init>
,parser.Matcher.parse
和parrser.ParseErrors.<init>
,其中发生有问题的调用。
这将告诉你究竟哪些分配花费了所有时间,然后你可以决定是否有更好的方法来做它们。
我得到了解析器运行数据结构表的印象。 我知道你不想重新设计它,但如果你重新设计它,你将获得更好的性能。 基本上,如果你可以把它表达为一个递归下降的解析器,并且如果语言不经常改变,你可以直接在代码中编写解析器 - 没有表 - 它会快10到100倍。
答案 1 :(得分:1)
简短回答:
在Object.<init>
中花费的时间可能是由于使用堆配置文件在中生成的CPU配置文件;后者用于检测所有内存分配,使分配变慢并使它们浮现在CPU配置文件的顶部。
答案很长:
我怀疑问题中显示的配置文件是通过同时询问CPU配置文件和堆配置文件生成的。换句话说,它是通过执行以下操作生成的:
java -agentlib:hprof=heap=sites,cpu=samples,depth=20 MyProg
不幸的是,至少在JVM的内置hprof分析器中,CPU采样在堆分析的情况下是无用的。堆分析仪器记录所有内存分配,使程序运行顺序更慢。在用于跟踪内存分配的程序中,分配确实是瓶颈,这就是Object.<init>
出现在CPU配置文件顶部的原因。但在实际程序中,分配/垃圾收集可能是,也可能不是瓶颈。
您通常希望一次只生成一个配置文件。首先通过运行类似java -agentlib:hprof=cpu=samples,depth=20
的内容生成CPU配置文件,然后分析结果。如果CPU配置文件指向内存分配/垃圾收集作为瓶颈,那么,只有这样,才能生成并分析堆配置文件,以查看大部分分配来自哪里。
另一个答案提出了很多关于分析CPU配置文件的重点。特别是,通过查看原始hperf CPU配置文件,可以更容易地查看使用JPerfAnal的内容。此外,为了使包容性百分比合理准确,您可能需要增加堆栈采样深度以匹配您的应用。