Java 정적 호출은 비 정적 호출보다 비용이 많이 듭니까?
어떤 방식 으로든 성능상의 이점이 있습니까? 컴파일러 / VM 특정입니까? 핫스팟을 사용하고 있습니다.
첫째, 성능을 기준으로 정적과 비정 적을 선택해서는 안됩니다.
둘째, 실제로는 아무런 차이가 없습니다. 핫스팟은 한 메서드에 대해 정적 호출을 더 빠르게 만들고 다른 메서드에 대해서는 비 정적 호출을 더 빠르게 만드는 방식으로 최적화하도록 선택할 수 있습니다.
셋째 : 정적 대 비정 적을 둘러싼 신화의 대부분은 매우 오래된 JVM (핫스팟이 수행하는 최적화 근처에서는 수행하지 않음) 또는 C ++에 대한 기억 된 퀴즈 (동적 호출이 하나 이상의 메모리 액세스를 사용하는 경우)를 기반으로 합니다. 정적 호출보다).
4 년 후 ...
좋아,이 질문을 영원히 해결하기 위해 여러 종류의 호출 (가상, 비가 상, 정적)이 서로 어떻게 비교되는지 보여주는 벤치 마크를 작성했습니다.
나는 그것을 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 명령 세트.) 비가 상 또는 정적 호출로 대체하여 제거하려는 경우 기능을 에뮬레이션하기 위해 추가 코드를 추가해야하는 경우 결과적으로 발생하는 순 오버 헤드가 제한됩니다. 적지 않고 더 많을 것입니다. 아마도, 훨씬, 훨씬, 헤아릴 수 없을 정도로 훨씬 더 많이.
음, 정적 호출 은 재정의 할 수 없으며 (따라서 항상 인라인 후보가 됨) nullity 검사가 필요하지 않습니다. 핫스팟은 물론 이러한 장점을 부정 할 수 인스턴스 메서드에 대한 멋진 최적화의 무리를 수행하지만,있는 거 가능한 이유는 왜 정적 호출을 빠르게 할 수있다.
그러나, 당신의 디자인에 영향을 미치지 않습니다 - 가장 읽을 수있는, 자연적인 방법 코드 - 마이크로 최적화 이런 종류의 약 만 걱정하면 (이 거의 정당한 사유가있는 경우 결코 것입니다).
컴파일러 / VM에 따라 다릅니다.
- 이론적 으로 정적 호출은 가상 함수 조회를 수행 할 필요가 없기 때문에 약간 더 효율적으로 만들 수 있으며 숨겨진 "this"매개 변수의 오버 헤드도 피할 수 있습니다.
- 실제로 많은 컴파일러가이를 최적화합니다.
따라서이를 애플리케이션에서 진정으로 중요한 성능 문제로 식별하지 않는 한 귀찮게 할 가치가 없을 것입니다. 조기 최적화는 모든 악의 근원입니다.
그러나 나는 한 이 최적화는 다음과 같은 상황에서 상당한 성능 향상을 제공 볼 수 :
- 메모리 액세스없이 매우 간단한 수학적 계산을 수행하는 방법
- 엄격한 내부 루프에서 초당 수백만 번 호출되는 메서드
- 모든 성능이 중요한 CPU 바운드 애플리케이션
위의 내용이 적용되는 경우 테스트 해 볼 가치가 있습니다.
정적 메서드를 사용하는 또 다른 좋은 이유 (잠재적으로 더 중요합니다!)가 있습니다. 메서드가 실제로 정적 의미를 가지고 있다면 (즉, 논리적으로 클래스의 주어진 인스턴스에 연결되지 않은 경우) 정적 메서드를 만드는 것이 합리적입니다. 이 사실을 반영합니다. 숙련 된 Java 프로그래머는 static modifier를 발견하고 즉시 "아하!이 메서드는 정적이므로 인스턴스가 필요하지 않으며 아마도 인스턴스 특정 상태를 조작하지 않습니다"라고 생각할 것입니다. 따라서 방법의 정적 특성을 효과적으로 전달하게 될 것입니다 ....
이전 포스터에서 말했듯이 : 이것은 조기 최적화처럼 보입니다.
그러나 한 가지 차이점이 있습니다 (비 정적 호출에는 피연산자 스택에 피 호출자 객체를 추가로 푸시해야한다는 사실과 일부) :
정적 메서드는 재정의 할 수 없으므로 런타임에 정적 메서드 호출에 대한 가상 조회가 없습니다 . 이로 인해 일부 상황에서 눈에 띄는 차이가 발생할 수 있습니다.
바이트 코드 레벨에서의 차이는 비 정적 메소드 호출을 통해 수행된다는 점이다 INVOKEVIRTUAL
, INVOKEINTERFACE
또는 INVOKESPECIAL
정적 메소드 호출을 통해 수행되는 동안 INVOKESTATIC
.
정적 호출과 비 정적 호출의 성능 차이가 애플리케이션에 영향을 미칠 가능성은 거의 없습니다. "조기 최적화는 모든 악의 근원"임을 기억하십시오.
For the decision if a method should be static, the performance aspect should be irrelevant. If you have a performance problem, making lots of methods static isn't going to save the day. That said, static methods are almost certainly not slower than any instance method, in most cases marginally faster:
1.) Static methods are not polymorphic, so the JVM has less decisions to make to find the actual code to execute. This is a moot point in the Age of Hotspot, since Hotspot will optimize instance method calls that have only one implementation site, so they will perform the same.
2.) Another subtle difference is that static methods obviously have no "this" reference. This results in a stack frame one slot smaller than that of an instance method with the same signature and body ("this" is put in slot 0 of the local variables on the bytecode level, whereas for static methods slot 0 is used for the first parameter of the method).
7 years later...
I don't have a huge degree of confidence in the results that Mike Nakis found because they don't address some common issues relating to Hotspot optimisations. I've instrumented benchmarks using JMH and found the overhead of an instance method to be about 0.75% on my machine vs a static call. Given that low overhead I think except in the most latency sensitive operations it's arguably not the biggest concern in an applications design. The summary results from my JMH benchmark are as follows;
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
You can look at the code here on Github;
https://github.com/nfisher/svsi
The benchmark itself is pretty simple but aims to minimise dead code elimination and constant folding. There are possibly other optimisations that I've missed/overlooked and these results are likely to vary per JVM release and 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);
}
}
There might be a difference, and it might go either way for any particular piece of code, and it might change with even a minor release of the JVM.
This is most definitely part of the 97% of small efficiencies that you should forget about.
In theory, less expensive.
Static initialization is going to be done even if you create an instance of the object, whereas static methods will not do any initialization normally done in a constructor.
However, I haven't tested this.
As Jon notes, static methods can't be overridden, so simply invoking a static method may be -- on a sufficiently naive Java runtime -- faster than invoking an instance method.
But then, even assuming you're at the point where you care about messing up your design to save a few nanoseconds, that just brings up another question: will you need method overriding yourself? If you change your code around to make an instance method into a static method to save a nanosecond here and there, and then turn around and implement your own dispatcher on top of that, yours is almost certainly going to be less efficient than the one built into your Java runtime already.
I would like to add to the other great answers here that it also depends on your flow, for example:
Public class MyDao {
private String sql = "select * from MY_ITEM";
public List<MyItem> getAllItems() {
springJdbcTemplate.query(sql, new MyRowMapper());
};
};
Pay attention that you create a new MyRowMapper object per each call.
Instead, I suggest to use here a static field.
Public class MyDao {
private static RowMapper myRowMapper = new MyRowMapper();
private String sql = "select * from MY_ITEM";
public List<MyItem> getAllItems() {
springJdbcTemplate.query(sql, myRowMapper);
};
};
'program story' 카테고리의 다른 글
선택된 상태의 Android ImageButton? (0) | 2020.10.11 |
---|---|
현재 PowerShell 프로세스가 32 비트인지 64 비트인지 확인합니까? (0) | 2020.10.11 |
Visual Studio에서 Windows 서비스를 어떻게 디버깅합니까? (0) | 2020.10.11 |
코드를 통해 TextView의 wrap_content에 너비 설정 (0) | 2020.10.11 |
Windows 7에서 .net Framework 4.0 용 GACUTIL은 어디에 있습니까? (0) | 2020.10.10 |