Oracle Hotspot JVM:一般来说,哪些操作会特别消耗CPU?

4
我希望了解哪些操作会对CPU负载产生不成比例的影响,以及开发常见操作的相对成本的直觉。为了最小化概括,请假定使用Oracle 7 HotSpot JVM。
例如:
- 构造大量对象是否会消耗CPU(我知道它会消耗内存 :-))? - 竞争监视器是否会消耗CPU?即如果我们有多个线程尝试进入相同的同步块,那么被阻塞的线程是否也会消耗CPU周期? - 上述操作的相对成本是多少?例如,“new”一个单独的对象与迭代X元素数组的成本相同
有什么提示可以让我更好地了解典型操作的相对CPU成本?
您能推荐一些相关的好读物吗?
谢谢,
澄清:
感谢早期的回复,但请注意:
- 我没有问“为什么我的应用程序很慢” - 了解使用分析器将有助于识别特定应用程序中的问题,例如GC可能会占用CPU,或者GC tenured generation比Eden space更昂贵 - 大多数操作只有在大量执行时才变得昂贵(即如果很少使用,则几乎没有操作是昂贵的) - 相反,我正在寻求关于相对CPU成本的指导,特别是关于上述操作(假设“Web-scale”应用程序使用所有已提及的操作相等的数量-很多)。
例如,我已经知道:
- 长方法调用链不会对CPU负载产生显着影响(因此通常可以自由使用方法委托) - 抛出异常比使用条件语句更昂贵(因此在高性能敏感代码中通常首选后者作为流程控制)
...但是实例化新对象或竞争监视器呢?这两个操作中的任何一个都会对CPU负载产生重要(甚至支配性)的贡献吗(假设我不关心延迟或堆大小)?

XML处理(解析,转换,编组),IO操作是我能想到的一些。 - adarshr
过度的垃圾回收可能会导致高CPU负载,请使用“-verbose:gc”进行检查。 - axtavt
我们对我们的应用程序进行了剖析 - 垃圾回收占总 CPU 时间的 <2%。 - Nikita
1
你没有从对代码进行性能分析中了解到哪些消耗CPU吗? - Marko Topolnik
1
争夺锁并不会消耗CPU,这是肯定的。一旦锁被释放,操作系统将简单地决定哪个线程可以获得锁;其它线程将继续等待。 - Marko Topolnik
1
内存分配在 CPU 方面几乎是免费的,但 new 可能意味着更多。例如,实例化 Calendar 是很重的。 - Marko Topolnik
3个回答

2
一次操作总是很快的。当一个操作被执行几千次,甚至是一百万或十亿次时,就会产生可测量的CPU负载。因此,您需要注意各种循环和重递归调用。
通常情况下,并不明显某些东西被执行了一百万次,因为明显的循环只执行了一百次。但它调用了一个函数,该函数执行了一百次某些操作,其中包含另一个函数执行一百次某些操作。这样,您最终得到了某些东西被运行了一百万次。在Web应用程序中,它还会乘以并发请求的数量。
由于很难找出真正的热点,您可能想使用Java的特殊性能分析工具来调查您的应用程序。这样,您就可以了解哪些模式是CPU密集型的,哪些模式不是。
在Java中,如果您分配了大量内存(无论是少量大块还是许多小块),并且不能快速释放,则垃圾收集可能会成为CPU瓶颈。使用大量字符串(例如在处理XML时)可能是这样的原因。
但最好的方法是使用分析工具。

谢谢,但那不是我的问题,Codo(也许表述不太清楚)。我们已经对我们的应用程序进行了分析;我知道如果只执行一次,几乎没有操作是昂贵的。我的问题是关于相对成本的:如果每个操作都执行1m次,哪个更耗费CPU资源(比如:a)new Object() b)throw Exception c)其他操作)。 - Nikita
1
@Nikita 我认为你会遇到的问题是这种微基准测试在运行时受许多变量的影响。不同的操作系统和硬件组合可能会给出不同的相对结果。此外,不同的程序状态可以影响某些事情,比如在捕获异常之前,抛出要展开堆栈的距离,或者在分配新对象时VM是否必须分配更多内存,构造函数有多复杂。很难概括,这就是优化困难并且应该通过针对特定应用程序的适当分析进行优化。 - Dev
很好,@Dev,我明白没有100%准确的答案。但是我们仍然可以进行一些概括 - 有一些例外;-在某些假设下,例如“所有操作都在相同的硬件上运行;分配的堆大小是固定的”等。 - Nikita

1
抛出异常的成本可能非常低。通常只有创建异常对象才会昂贵,因为这会调用 fillInStackTrace(),而 fillInStackTrace() 是昂贵的。如果省略此步骤,则其余部分很快(可以像 C 中的 goto 一样快)。
来源:https://blogs.oracle.com/jrose/entry/longjumps_considered_inexpensive 以下是关于 OpenJDK JVM 的内部信息页面,列出了有关性能的信息,供进一步参考:page with internals about the OpenJDK JVM that lists information about performance

1

我认为你们中有人写的相对CPU消耗如下:

1)数组索引; 它很快; 只是寻址

2)监视器 - 较慢; 暂停和等待线程不会消耗CPU,切换消耗的CPU非常少,但比索引多

3)如果对象复杂并导致子对象创建,则创建对象可能很慢; 创建单个new Object()比线程切换略慢; 但也许我错了,它们是一样的; 无论如何是可比较的

4)抛出/捕获异常非常慢; 它比创建对象慢10-100倍


如果抛出和捕获异常非常缓慢,从性能的角度来看,返回错误类型或类似的东西而不是抛出异常是否更好?也许可以返回一个包含实际返回值和/或错误类型的对象数组? - ldam
@LoganDam 根据定义,异常应该很少发生;如果在您的情况下异常经常发生,那么当然 - 您应该使用返回的错误标记来处理它们。 - Suzan Cioc
请注意,除非虚拟机能够根据某些启发式方法确定不需要检查,否则对数组进行索引会包括 VM 进行边界检查。如果您感觉有点不可移植和冒险,可以完全通过使用 sun.misc.Unsafe 来绕过这一点(我相信未来 JDK 中有一项标准化 sun.misc.Unsafe 的倡议)。除非您正在进行性能关键的操作,并已经进行了分析等确认,否则不建议这样做。 - Philip Guin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接