어떤 방식 으로든 성능상의 이점이 있습니까? 컴파일러 / VM 특정입니까? 핫스팟을 사용하고 있습니다.
어떤 방식 으로든 성능상의 이점이 있습니까? 컴파일러 / 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 명령 세트.) 비가 상 또는 정적 호출로 대체하여 제거하려는 경우 기능을 에뮬레이션하기 위해 추가 코드를 추가해야하는 경우 결과적으로 발생하는 순 오버 헤드가 제한됩니다. 적지 않고 더 많을 것입니다. 아마도, 훨씬, 훨씬, 헤아릴 수 없을 정도로 훨씬 더 많이.
VirtualTest | 488846733 -- NonVirtualTest | 480530022 -- StaticTest | 484353198
OpenJDK 설치 에서 속도 차이가 전혀 없습니다 . FTR : final
수정자를 제거해도 마찬가지 입니다. Btw. 나는 terminate
현장 을 만들어야했다 volatile
. 그렇지 않으면 시험이 끝나지 않았다.
VirtualTest | 12451872 -- NonVirtualTest | 12089542 -- StaticTest | 8181170
.. 내 노트북의 OpenJDK가 40 배 더 많은 반복을 수행 할뿐만 아니라 정적 테스트의 처리량은 항상 약 30 % 적습니다. Android 4.4 태블릿에서 예상되는 결과가 나오기 때문에 이것은 ART 특정 현상 일 수 있습니다.VirtualTest | 138183740 -- NonVirtualTest | 142268636 -- StaticTest | 161388933
음, 정적 호출 은 재정의 할 수 없으며 (따라서 항상 인라인 후보가 됨) nullity 검사가 필요하지 않습니다. 핫스팟은 물론 이러한 장점을 부정 할 수 인스턴스 메서드에 대한 멋진 최적화의 무리를 수행하지만,있는 거 가능한 이유는 왜 정적 호출을 빠르게 할 수있다.
그러나 그것은 당신의 디자인에 영향을주지 않아야합니다-가장 읽기 쉽고 자연스러운 방법으로 코드-당신이 정당한 이유가있는 경우에만 이런 종류의 마이크로 최적화에 대해 걱정하십시오 (거의 절대 그렇게 하지 않을 것입니다).
컴파일러 / VM에 따라 다릅니다.
따라서이를 애플리케이션에서 진정으로 중요한 성능 문제로 식별하지 않는 한 신경 쓸 가치가 없을 것입니다. 조기 최적화는 모든 악의 근원입니다.
그러나 나는 한 이 최적화는 다음과 같은 상황에서 상당한 성능 향상을 제공 볼 수 :
위의 내용이 적용되는 경우 테스트 해 볼 가치가 있습니다.
정적 메서드를 사용하는 또 다른 좋은 이유가 있습니다 (잠재적으로 더 중요합니다!). 메서드에 실제로 정적 의미가있는 경우 (즉, 논리적으로 클래스의 지정된 인스턴스에 연결되지 않은 경우) 정적 메서드를 만드는 것이 합리적입니다. 이 사실을 반영합니다. 숙련 된 Java 프로그래머는 static modifier를 발견하고 즉시 "아하!이 메서드는 정적이므로 인스턴스가 필요하지 않으며 아마도 인스턴스 특정 상태를 조작하지 않습니다"라고 생각할 것입니다. 따라서 방법의 정적 특성을 효과적으로 전달하게 될 것입니다 ....
이전 포스터에서 말했듯이 : 이것은 조기 최적화처럼 보입니다.
그러나 한 가지 차이점이 있습니다 (비 정적 호출에는 피연산자 스택에 피 호출자 객체를 추가로 푸시해야한다는 사실과 일부) :
정적 메서드는 재정의 할 수 없으므로 런타임에 정적 메서드 호출에 대한 가상 조회가 없습니다 . 이로 인해 일부 상황에서 눈에 띄는 차이가 발생할 수 있습니다.
바이트 코드 레벨에서의 차이는 비 정적 메소드 호출을 통해 수행된다는 점이다 INVOKEVIRTUAL
, INVOKEINTERFACE
또는 INVOKESPECIAL
정적 메소드 호출을 통해 수행되는 동안 INVOKESTATIC
.
invokespecial
는 가상이 아니기 때문에 (적어도 일반적으로) 사용하여 호출됩니다 .
정적 호출과 비 정적 호출의 성능 차이가 애플리케이션에 영향을 미칠 가능성은 거의 없습니다. "조기 최적화는 모든 악의 근원"임을 기억하십시오.
7 년 후 ...
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
여기 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은 실행할 실제 코드를 찾기 위해 결정해야 할 결정이 적습니다. 핫스팟은 구현 사이트가 하나만있는 인스턴스 메서드 호출을 최적화하여 동일한 작업을 수행하기 때문에 이것은 핫스팟 시대의 논쟁 점입니다.
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);
};
};