有没有性能上的好处呢?是编译器/虚拟机特定的吗?我正在使用Hotspot。
有没有性能上的好处呢?是编译器/虚拟机特定的吗?我正在使用Hotspot。
四年后...
好的,为了彻底解决这个问题,我编写了一个基准测试,展示了不同类型的调用(虚拟、非虚拟、静态)之间的比较。
我在ideone上运行了它,这是我的结果:
(迭代次数越多越好。)
Success time: 3.12 memory: 320576 signal:0
Name | Iterations
VirtualTest | 128009996
NonVirtualTest | 301765679
StaticTest | 352298601
Done.
预料之中的是,虚函数调用最慢,非虚函数调用更快,而静态方法调用甚至更快。
我没有预料到的是差异如此明显:测量结果表明,虚函数调用的速度不到非虚函数调用的一半,而非虚函数调用的速度整体上比静态调用慢15%。这就是这些测量结果显示的内容;实际差异实际上必须稍微更加明显,因为对于每个虚拟、非虚拟和静态方法调用,我的基准测试代码都有额外的常量开销:增加一个整数变量的值、检查布尔变量并循环(如果未满足条件)。
我想结果会因CPU和JVM的不同而异,所以尝试一下,看看您会得到什么:
import java.io.*;
class StaticVsInstanceBenchmark
{
public static void main( String[] args ) throws Exception
{
StaticVsInstanceBenchmark program = new StaticVsInstanceBenchmark();
program.run();
}
static final int DURATION = 1000;
public void run() throws Exception
{
doBenchmark( new VirtualTest( new ClassWithVirtualMethod() ),
new NonVirtualTest( new ClassWithNonVirtualMethod() ),
new StaticTest() );
}
void doBenchmark( Test... tests ) throws Exception
{
System.out.println( " Name | Iterations" );
doBenchmark2( devNull, 1, tests ); //warmup
doBenchmark2( System.out, DURATION, tests );
System.out.println( "Done." );
}
void doBenchmark2( PrintStream printStream, int duration, Test[] tests ) throws Exception
{
for( Test test : tests )
{
long iterations = runTest( duration, test );
printStream.printf( "%15s | %10d\n", test.getClass().getSimpleName(), iterations );
}
}
long runTest( int duration, Test test ) throws Exception
{
test.terminate = false;
test.count = 0;
Thread thread = new Thread( test );
thread.start();
Thread.sleep( duration );
test.terminate = true;
thread.join();
return test.count;
}
static abstract class Test implements Runnable
{
boolean terminate = false;
long count = 0;
}
static class ClassWithStaticStuff
{
static int staticDummy;
static void staticMethod() { staticDummy++; }
}
static class StaticTest extends Test
{
@Override
public void run()
{
for( count = 0; !terminate; count++ )
{
ClassWithStaticStuff.staticMethod();
}
}
}
static class ClassWithVirtualMethod implements Runnable
{
int instanceDummy;
@Override public void run() { instanceDummy++; }
}
static class VirtualTest extends Test
{
final Runnable runnable;
VirtualTest( Runnable runnable )
{
this.runnable = runnable;
}
@Override
public void run()
{
for( count = 0; !terminate; count++ )
{
runnable.run();
}
}
}
static class ClassWithNonVirtualMethod
{
int instanceDummy;
final void nonVirtualMethod() { instanceDummy++; }
}
static class NonVirtualTest extends Test
{
final ClassWithNonVirtualMethod objectWithNonVirtualMethod;
NonVirtualTest( ClassWithNonVirtualMethod objectWithNonVirtualMethod )
{
this.objectWithNonVirtualMethod = objectWithNonVirtualMethod;
}
@Override
public void run()
{
for( count = 0; !terminate; count++ )
{
objectWithNonVirtualMethod.nonVirtualMethod();
}
}
}
static final PrintStream devNull = new PrintStream( new OutputStream()
{
public void write(int b) {}
} );
}
值得注意的是,这种性能差异仅适用于除调用无参方法之外的代码。无论在调用之间有什么其他代码,包括参数传递,在其中的差异都会被稀释。实际上,静态和非虚拟调用之间的15%差异可能完全由于不必将“this”指针传递给静态方法而解释。因此,只要有一些简单的代码在调用之间执行,不同类型的调用之间的差异就会被稀释到几乎没有任何影响。VirtualTest | 488846733 -- NonVirtualTest | 480530022 -- StaticTest | 484353198
在我的 OpenJDK 安装中。顺便说一句,即使我删除了 final
修饰符,这个结果仍然成立。另外,我必须将 terminate
字段设置为 volatile
,否则测试就无法完成。 - Marten首先,您不应该基于性能来选择静态或非静态。
其次,在实践中,这并不会有任何区别。Hotspot可能会选择以使一个方法的静态调用更快,使另一个方法的非静态调用更快的方式进行优化。
第三点:关于静态与非静态的大部分神话都基于非常旧的JVM(它们没有做到Hotspot所做的优化),或者是一些关于C ++的记忆小事(其中动态调用使用比静态调用多一个内存访问)。
静态调用无法被覆盖(因此始终是内联候选项),并且不需要任何空值检查。HotSpot针对实例方法进行了许多很酷的优化,这可能会抵消这些优势,但这些是可能的原因,为什么静态调用可能更快。
然而,这不应影响您的设计 - 以最可读、最自然的方式编写代码 - 只有在有正当理由时才考虑这种微观优化(而这几乎是从不出现的情况)。
七年之后......
对于Mike Nakis发现的结果,我没有太大的信心,因为它们没有解决与热点优化有关的一些常见问题。我使用JMH仪器测试了基准测试,并发现在我的机器上,实例方法的开销约为静态调用的0.75%。考虑到这种低延迟,除非在最敏感的操作中,它在应用程序设计中可能不是最大的关注点。我的JMH基准测试的概要结果如下:
java -jar target/benchmark.jar
# -- snip --
Benchmark Mode Cnt Score Error Units
MyBenchmark.testInstanceMethod thrpt 200 414036562.933 ± 2198178.163 ops/s
MyBenchmark.testStaticMethod thrpt 200 417194553.496 ± 1055872.594 ops/s
https://github.com/nfisher/svsi
这个基准测试本身很简单,但旨在最小化死代码消除和常量折叠。可能有其他优化措施被我忽略了,这些结果可能会因JVM版本和操作系统而有所不同。
package ca.junctionbox.svsi;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.infra.Blackhole;
class InstanceSum {
public int sum(final int a, final int b) {
return a + b;
}
}
class StaticSum {
public static int sum(final int a, final int b) {
return a + b;
}
}
public class MyBenchmark {
private static final InstanceSum impl = new InstanceSum();
@State(Scope.Thread)
public static class Input {
public int a = 1;
public int b = 2;
}
@Benchmark
public void testStaticMethod(Input i, Blackhole blackhole) {
int sum = StaticSum.sum(i.a, i.b);
blackhole.consume(sum);
}
@Benchmark
public void testInstanceMethod(Input i, Blackhole blackhole) {
int sum = impl.sum(i.a, i.b);
blackhole.consume(sum);
}
}
ops/s
以外的指标(例如内存使用、减少 .oat 文件大小等)有什么潜在好处感到好奇。您知道有哪些相对简单的工具/方法可以尝试对这些其他指标进行基准测试吗? - Ryan Thomas它取决于编译器/虚拟机。
因此,除非您已经确定将其视为应用程序中真正关键的性能问题,否则可能没有必要担心它。过早地优化是万恶之源等等......
然而,我确实看到过在以下情况下,这种优化可以显著提高性能:
如果上述情况适用于您,则值得测试。
还有另一个很好(甚至可能更重要!)使用静态方法的原因-如果该方法实际上具有静态语义(即在逻辑上与类的给定实例无关),那么将其设为静态是有意义的,以反映这一事实。经验丰富的Java程序员会注意到静态修饰符并立即想到“啊哈!这个方法是静态的,所以它不需要实例,可能不会操纵特定实例的状态”。因此,您已经有效地传达了该方法的静态性质....
正如之前的帖子所说:这似乎是一种过早的优化。
然而,有一个区别(除了额外将被调用对象推送到操作数栈上的非静态调用):
由于静态方法无法被覆盖,静态方法调用在运行时不会进行任何虚拟查找。在某些情况下,这可能会导致可观察到的差异。
字节码级别上的差异是,非静态方法调用通过INVOKEVIRTUAL
、INVOKEINTERFACE
或INVOKESPECIAL
完成,而静态方法调用则通过INVOKESTATIC
完成。
invokespecial
调用,因为它不是虚拟的。 - Mark Peters在您的应用程序中,静态调用与非静态调用的表现有所不同是相当不可能的。请记住,“过早优化是万恶之源”。
在决定一个方法是否应该是静态的时候,性能方面应该是无关紧要的。如果你有性能问题,那么将许多方法改成静态并不会挽救局面。话虽如此,在大多数情况下,静态方法几乎肯定不比任何实例方法慢,通常稍微更快:
1.) 静态方法不是多态的,因此JVM需要做出更少的决策来找到要执行的实际代码。在热点(HotSpot)时代,这是一个无关紧要的点,因为热点会优化只有一个实现位置的实例方法调用,所以它们的表现相同。
2.) 另一个微妙的区别是静态方法显然没有"this"引用。这导致栈帧比具有相同签名和主体的实例方法小一个插槽("this"放在字节码级别的本地变量中的插槽0中,而对于静态方法,插槽0用于方法的第一个参数)。
对于任何一个特定的代码片段,可能存在差异并且可能朝着任意一种方式发展,甚至在JVM的次要版本中也可能发生变化。
这绝对是应该忘记的97%小效率的一部分。
TableView
中搜索数百万条记录。 - trilogy理论上来说,更便宜。
即使您创建对象的实例,静态初始化也将完成,而静态方法通常不会执行构造函数中通常执行的任何初始化。
但是,我还没有测试过这个。