Answers:
首先:您不应基于性能来选择静态还是非静态。
第二:在实践中,没有任何区别。热点可能会选择进行优化,以使一种方法的静态调用更快,而另一种方法的非静态调用更快。
第三:关于静态和非静态的许多神话都是基于非常老的JVM(在Hotspot所做的优化附近没有做任何事情),或者是一些关于C ++的记住的琐事(动态调用使用了更多的内存访问权)比静态调用)。
四年后...
好的,希望一劳永逸地解决这个问题,我编写了一个基准测试,该基准测试显示了不同类型的调用(虚拟,非虚拟,静态)之间如何进行比较。
我在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
不必指针传递给静态方法。因此,只需花费很少的代码在两次调用之间做一些琐碎的事情,即可将不同类型调用之间的差异稀释到没有任何净影响的程度。
同样,存在虚方法调用是有原因的。它们确实有服务的目的,并且使用底层硬件提供的最有效的方法来实现它们。(CPU指令集。)如果要通过用非虚拟或静态调用替换它们来消除它们,而最终不得不添加多达Iota的额外代码来模拟它们的功能,那么必然会产生净费用不少于,但更多。可能很多很多,很多很多。
VirtualTest | 488846733 -- NonVirtualTest | 480530022 -- StaticTest | 484353198
在我的OpenJDK安装上。FTR:如果删除final
修饰符,这甚至是正确的。顺便说一句。我必须参加terminate
比赛 volatile
,否则测试未完成。
VirtualTest | 12451872 -- NonVirtualTest | 12089542 -- StaticTest | 8181170
。不仅笔记本电脑上的OpenJDK可以执行40倍以上的迭代,而且静态测试的吞吐量始终降低了约30%。这可能是一种ART特有的现象,因为我在Android 4.4平板电脑上获得了预期的结果:VirtualTest | 138183740 -- NonVirtualTest | 142268636 -- StaticTest | 161388933
好吧,静态调用不能被覆盖(因此总是内联的候选对象),并且不需要任何无效检查。HotSpot对实例方法进行了很多很酷的优化,这些优化可能会抵消这些优势,但是这可能是静态调用可能更快的原因。
但是,这不应该影响您的设计-以最易读,最自然的方式编写代码-仅在有正当理由的情况下才担心这种微优化( 永远不会)。
它特定于编译器/ VM。
因此,除非您已将其确定为应用程序中真正关键的性能问题,否则可能不值得费心。过早的优化是万恶之源...
不过,我已经看到了这个优化得到以下情况下大幅提高性能:
如果以上适用于您,则可能值得测试。
还有一个使用静态方法的其他好理由(甚至可能更重要!)-如果该方法实际上具有静态语义(即,逻辑上未连接到该类的给定实例),则使其成为静态是有意义的反映这一事实。然后,经验丰富的Java程序员会注意到static修饰符,并立即认为“啊哈!此方法是静态的,因此它不需要实例,并且大概不会操纵实例的特定状态”。因此,您将有效地传达了该方法的静态性质。
正如以前的海报所说:这似乎是过早的优化。
但是,存在一个差异(部分原因是非静态调用需要将被调用者对象额外推送到操作数堆栈上):
由于不能覆盖静态方法,因此不会进行任何虚拟查找在运行时对静态方法调用。在某些情况下,这可能会导致明显的差异。
在字节代码级的区别在于非静态方法调用是通过完成INVOKEVIRTUAL
,INVOKEINTERFACE
或INVOKESPECIAL
同时静态方法调用是通过完成INVOKESTATIC
。
invokespecial
因为它不是虚拟的。
令人难以置信的是,静态调用和非静态调用的性能差异不会对您的应用程序产生影响。请记住,“过早的优化是万恶之源”。
7年后...
我对Mike Nakis的结果没有太大的信心,因为他们没有解决与Hotspot优化相关的一些常见问题。我已经使用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
您可以在Github上查看代码;
https://github.com/nfisher/svsi
基准测试本身非常简单,但是旨在最大程度地减少死代码消除和持续折叠。我可能遗漏了一些其他优化/忽略了这些优化,并且这些结果可能因JVM版本和OS而异。
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
主要在ART环境以外的其他指标可能产生的潜在好处(例如,内存使用量,减少的.oat文件大小等)。您是否知道有任何相对简单的工具/方法可以尝试对这些其他指标进行基准测试?
为了确定方法是否应该是静态的,性能方面应该无关紧要。如果您遇到性能问题,那么将许多方法设为静态将无济于事。也就是说,静态方法几乎肯定不会比任何实例方法都要慢,在大多数情况下要快一些:
1.)静态方法不是多态的,因此JVM很少做出决定来查找要执行的实际代码。这是Hotspot时代的一个争论点,因为Hotspot将优化仅具有一个实现站点的实例方法调用,因此它们将执行相同的操作。
2.)另一个细微的区别是静态方法显然没有“ this”引用。这导致堆栈帧比具有相同签名和正文的实例方法的插槽小一个插槽(“ this”放在字节码级别的局部变量的插槽0中,而对于静态方法,插槽0用于第一个插槽)。方法的参数)。
可能有所不同,对于任何特定的代码段来说,它可能都是相反的,即使是较小的JVM版本,它也可能会发生变化。
这绝对是您应该忽略的97%小效率中的一部分。
TableView
成千上万的记录。
从理论上讲,价格便宜。
即使您创建对象的实例,也要进行静态初始化,而静态方法将不会执行通常在构造函数中完成的任何初始化。
但是,我尚未对此进行测试。
我想在这里补充其他很好的答案,它也取决于您的流程,例如:
Public class MyDao {
private String sql = "select * from MY_ITEM";
public List<MyItem> getAllItems() {
springJdbcTemplate.query(sql, new MyRowMapper());
};
};
请注意,每个调用都会创建一个新的MyRowMapper对象。
相反,我建议在这里使用一个静态字段。
Public class MyDao {
private static RowMapper myRowMapper = new MyRowMapper();
private String sql = "select * from MY_ITEM";
public List<MyItem> getAllItems() {
springJdbcTemplate.query(sql, myRowMapper);
};
};