And*_*nko 81 java performance premature-optimization
这种或那种方式有任何性能上的好处吗?它是编译器/ VM特定的吗?我正在使用Hotspot.
Ano*_*non 71
第一:你不应该在性能的基础上选择静态与非静态.
第二:在实践中,它不会有任何区别.热点可以选择以一种方式优化以使一种方法更快地进行静态调用,而对另一种方法更快地进行非静态调用.
第三:围绕静态与非静态的大多数神话都基于非常古老的JVM(在Hotspot所做的优化附近没有做任何事情),或者是一些关于C++的记忆琐事(其中动态调用使用了一个以上的内存访问)而不是静态电话).
Mik*_*kis 53
四年后......
好的,为了一次又一次地解决这个问题,我编写了一个基准测试,显示了不同类型的调用(虚拟,非虚拟,静态)如何相互比较.
我在ideone上运行它,这就是我得到的:
(更大的迭代次数更好.)
Success time: 3.12 memory: 320576 signal:0
Name | Iterations
VirtualTest | 128009996
NonVirtualTest | 301765679
StaticTest | 352298601
Done.
Run Code Online (Sandbox Code Playgroud)
正如所料,虚方法调用是最慢的,非虚方法调用更快,静态方法调用甚至更快.
我没想到的是差异是如此明显:虚拟方法调用的运行速度不到非虚拟方法调用的一半,而非虚拟方法调用的速度比静态调用慢15%.这就是这些测量显示的结果; 事实上,实际差异必须稍微明显一些,因为对于每个虚拟,非虚拟和静态方法调用,我的基准测试代码都有一个额外的常量开销,即递增一个整数变量,检查一个布尔变量,如果不是,则循环.
我想结果会因CPU和CPU以及JVM到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) {}
} );
}
Run Code Online (Sandbox Code Playgroud)
值得注意的是,这种性能差异仅适用于除了调用无参数方法之外什么都不做的代码.无论调用之间的其他代码是什么,都会稀释差异,这包括参数传递.实际上,静态和非虚拟调用之间15%的差异可能完全由this指针不必传递给静态方法的事实来解释.因此,只需要相当少量的代码就可以在不同类型的调用之间的差异调用之间做一些微不足道的事情,将其稀释到没有任何净影响的程度.
此外,存在虚拟方法调用的原因; 它们确实有服务的目的,它们是使用底层硬件提供的最有效的方法实现的.(CPU指令集.)如果您希望通过用非虚拟或静态调用替换它们来消除它们,那么您最终必须添加尽可能多的额外代码来模拟它们的功能,然后您的结果净开销受到限制不少,但更多.很可能,非常多,不可思议地多,更多.
Jon*_*eet 44
好吧,静态调用不能被覆盖(所以总是候选内联),并且不需要任何无效检查.HotSpot为实例方法做了很多很好的优化,这可能会抵消这些优势,但它们可能是静态调用可能更快的原因.
但是,这不应该以最可读,最自然的方式影响您的设计 - 代码 - 如果您有正当理由(您几乎从不会这样做),只会担心这种微优化.
mik*_*era 18
它是特定于编译器/ VM的.
因此,除非您在应用程序中将此视为真正关键的性能问题,否则可能不值得烦恼.过早的优化是万恶之源......
不过,我已经看到了这个优化得到以下情况下大幅提高性能:
如果上述内容适用于您,则可能值得测试.
使用静态方法还有一个好的(可能更重要的!)理由 - 如果方法实际上具有静态语义(即逻辑上没有连接到类的给定实例)那么将它设置为静态是有意义的反映这一事实.经验丰富的Java程序员会注意到静态修饰符并立即想到"啊哈!这个方法是静态的,因此它不需要实例,并且可能不会操纵特定于实例的状态".所以你将有效地传达方法的静态性质....
aio*_*obe 14
正如之前的海报所说:这似乎是一个不成熟的优化.
但是,存在一个区别(非静态调用需要额外将被调用对象推送到操作数堆栈的事实):
由于无法覆盖静态方法,因此静态方法调用在运行时不会有任何虚拟查找.在某些情况下,这可能会导致可观察到的差异.
字节码级别的差异在于非静态方法调用是通过完成INVOKEVIRTUAL,INVOKEINTERFACE或者INVOKESPECIAL在静态方法调用完成时完成的INVOKESTATIC.
Dur*_*dal 12
对于决定方法是否应该是静态的,性能方面应该是无关紧要的.如果您遇到性能问题,那么将大量方法设置为静态就不会节省时间.也就是说,静态方法几乎肯定不比任何实例方法慢,在大多数情况下略快一些:
1.)静态方法不是多态的,因此JVM做出较少的决定来查找要执行的实际代码.这是Hotspot时代的一个有争议的问题,因为Hotspot将优化只有一个实现站点的实例方法调用,因此它们将执行相同的操作.
2.)另一个细微差别是静态方法显然没有"这个"参考.这导致堆栈帧的一个槽小于具有相同签名和主体的实例方法的槽("this"放在字节码级别的局部变量的槽0中,而对于静态方法,槽0用于第一个方法的参数).
Nat*_*han 11
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
Run Code Online (Sandbox Code Playgroud)
您可以在Github上查看代码;
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);
}
}
Run Code Online (Sandbox Code Playgroud)
可能有所不同,对于任何特定的代码段来说,它可能会以任何一种方式出现,并且即使在较小的JVM版本中也可能会有所不同。
这绝对是您应该忽略的97%的小效率中的一部分。
| 归档时间: |
|
| 查看次数: |
40013 次 |
| 最近记录: |