Java静的呼び出しは、非静的呼び出しよりも多かれ少なかれ高価ですか?


回答:


74

まず、パフォーマンスに基づいて静的か非静的かを選択するべきではありません。

第二に、実際には、それは何の違いもありません。Hotspotは、静的呼び出しを1つのメソッドで高速にし、非静的呼び出しを別のメソッドで高速にする方法で最適化することを選択できます。

3番目:静的と非静的を取り巻く神話の多くは、非常に古いJVM(これはHotspotが行う最適化の近くでは機能しませんでした)、またはC ++に関する覚えられた雑学(動的呼び出しがもう1つのメモリアクセスを使用する)に基づいています静的呼び出しよりも)。


1
これだけに基づく静的メソッドを好むべきではありません。ただし、静的メソッドが設計にうまく適合している場合は、インスタンスメソッドよりも高速ではなくても、少なくとも同じくらい高速であり、パフォーマンスに基づいて除外すべきではないことを知っておくと役立ちます。
ウィル

2
@AaronDigulla -.-私が今最適化を行っているので、時期尚早ではなく、本当に必要なときにここに来たと言ったらどうしますか?OPは時期尚早に最適化することを望んでいると想定していましたが、このサイトは少しグローバルであると知っています...そうですか?私は失礼になりたくありませんが、次回はこのようなことを想定しないでください。
Dalibor Filus 2017年

1
@DaliborFilusバランスを見つける必要があります。静的メソッドを使用すると、あらゆる種類の問題が発生するため、特に何をしているのかわからない場合は、回避する必要があります。次に、ほとんどの「遅い」コードは(悪い)設計が原因であり、選択言語が遅いことが原因ではありません。コードが遅い場合、静的メソッドは、何もしない呼び出しメソッドでない限り、おそらくそれを保存しません。ほとんどの場合、メソッドのコードは呼び出しオーバーヘッドを小さくします。
アーロンディグラ2017年

6
反対投票。これは質問の答えにはなりません。パフォーマンスのメリットについて質問されました。設計原則に関する意見は求められなかった。
Colm Bhandal

4
「早すぎる最適化がすべての悪の根源である」と言うようにオウムを訓練した場合、私はオウムと同じくらいパフォーマンスについて知っている人々から1000票を獲得します。
rghome

62

4年後...

さて、この質問をいつまでも解決することを期待して、さまざまな種類の呼び出し(仮想、非仮想、静的)を互いに比較する方法を示すベンチマークを作成しました。

私はそれをideoneで実行しました、そしてこれは私が得たものです:

(反復回数が多いほど良いです。)

    Success time: 3.12 memory: 320576 signal:0
  Name          |  Iterations
    VirtualTest |  128009996
 NonVirtualTest |  301765679
     StaticTest |  352298601
Done.

予想どおり、仮想メソッド呼び出しは最も遅く、非仮想メソッド呼び出しはより高速で、静的メソッド呼び出しはさらに高速です。

私が予期していなかったことは、その違いがそれほど顕著であるということでした。仮想メソッド呼び出しは、非仮​​想メソッド呼び出しの速度の半分未満で実行されるように測定され、静的呼び出しよりも全体で15%遅く実行されるように測定されました。これらの測定値はそれを示しています。実際の違いは、実際にはやや顕著である必要があります。仮想、非仮想、および静的メソッド呼び出しごとに、1つの整数変数をインクリメントし、ブール変数をチェックし、trueでない場合はループするという追加の一定のオーバーヘッドがあるためです。

結果は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を追加する必要が生じ、その結果生じるネットオーバーヘッドが制限されます。少なくないために、もっと。おそらく、はるかに、はるかに、信じられないほどはるかに。


7
「仮想」はC ++用語です。Javaには仮想メソッドはありません。ランタイムポリモーフィックである通常のメソッドと、そうでない静的または最終メソッドがあります。
Zhenya

16
@levgenはい、その視点が言語の公式の高レベルの概要と同じくらい狭い人にとっては、あなたが言うとおりです。しかしもちろん、高レベルの概念は、Javaが登場するずっと前に発明された確立された低レベルのメカニズムを使用して実装されており、仮想メソッドはその1つです。あなたがフードの下ほんの見てみる場合は、すぐにこれがそうであることがわかります。docs.oracle.com/javase/specs/jvms/se7/html/...
マイクNakis

13
時期尚早の最適化について推測せずに質問に回答していただきありがとうございます。すばらしい答えです。
vegemite4me 2016年

3
はい、それはまさに私が意図したことです。とにかく、私は自分のマシンでテストを実行しました。このようなベンチマークで期待できるジッタを除いて、速度にはまったく違いはありません。VirtualTest | 488846733 -- NonVirtualTest | 480530022 -- StaticTest | 484353198私のOpenJDKインストールでは。FTR:finalモディファイアを削除した場合も同様です。ところで 私はterminateフィールド を作らvolatileなければなりませんでした。さもなければ、テストは終了しませんでした。
Marten

4
ちなみに、Android 6を実行しているNexus 5ではかなり驚くべき結果が得られましたVirtualTest | 12451872 -- NonVirtualTest | 12089542 -- StaticTest | 8181170。私のノートブックのOpenJDKが40倍以上の反復を実行できるだけでなく、静的テストでは常にスループットが約30%低くなります。私は、Android 4.4タブレットに期待される結果を得るため、これは、ARTの特定の現象かもしれません:VirtualTest | 138183740 -- NonVirtualTest | 142268636 -- StaticTest | 161388933
マーテン

46

まあ、静的呼び出しオーバーライドできないため(常にインライン化の候補です)、無効性チェックは必要ありません。HotSpotは、これらの利点を打ち消す可能性のあるインスタンスメソッドの一連の優れた最適化を行いますが、静的呼び出しの方が高速である理由として考えられます。

しかし、それがデザインに影響を与えることはありません。最も読みやすく自然な方法でコードを作成してください。この種のマイクロ最適化について心配するのは、原因が(ほとんどない)場合だけです。


静的呼び出しの方が速い理由として考えられるのは、これらの理由を教えてください。
JavaTechnical 2013

6
@JavaTechnical:答えはそれらの理由を説明していない-何も(あなたがするたびに使用するために実装うちの仕事に必要のないことを意味するオーバーライドし、あなたがインラインすることができます)と、あなたがAのメソッドを呼び出しているかどうかをチェックする必要はありません。 null参照。
Jon Skeet、

6
@JavaTechnical:わかりません。静的メソッドについて計算/チェックする必要のないものと、インライン化の機会を提供しました。仕事をしないことパフォーマンス上の利点です。理解しておくべきことは何ですか?
Jon Skeet、

静的変数は非静的変数よりも速く取得されますか?
JavaTechnical 2013

1
@JavaTechnical:実行するnullityチェックはありませんが、JITコンパイラーがそのチェック(コンテキスト固有のもの)を削除できる場合、それほど大きな違いは期待できません。メモリがキャッシュにあるかどうかなどは、はるかに重要です。
Jon Skeet

18

これはコンパイラ/ VM固有です。

  • 理論的には、静的呼び出しは仮想関数のルックアップを行う必要がないため、わずかに効率的です。また、非表示の「this」パラメーターのオーバーヘッドも回避できます。
  • 実際には、多くのコンパイラはこれをとにかく最適化します。

したがって、これがアプリケーションの本当に重要なパフォーマンスの問題であると特定しない限り、気にする必要はないでしょう。時期尚早の最適化は、すべての悪などの根源です...

しかし、私はしているこの最適化は、次のような状況の大幅な性能向上を与える見て:

  • メモリアクセスなしで非常に単純な数学的計算を実行する方法
  • 密な内部ループで毎秒数百万回呼び出されるメソッド
  • あらゆるパフォーマンスが重要なCPUバインドアプリケーション

上記が当てはまる場合は、テストする価値があります。

静的メソッドを使用するもう1つの良い(そして潜在的にはさらに重要な!)理由もあります。メソッドが実際に静的セマンティクスを持っている(つまり、クラスの特定のインスタンスに論理的に接続されていない)場合は、静的にするのが理にかなっています。この事実を反映するために。その後、経験豊富なJavaプログラマーは静的修飾子に気づき、「ああ!このメソッドは静的であるため、インスタンスを必要とせず、おそらくインスタンス固有の状態を操作しません」とすぐに思います。したがって、メソッドの静的な性質を効果的に伝えることができます。


14

以前のポスターが言ったように:これは時期尚早の最適化のようです。

ただし、1つの違いがあります(静的でない呼び出しでは、呼び出し先オブジェクトをオペランドスタックに追加でプッシュする必要があるという事実からの一部)。

静的メソッドはオーバーライドできないため、実行時に静的メソッド呼び出しの仮想ルックアップはありません。これにより、状況によっては観察可能な差異が生じる可能性があります。

バイトコードレベルでの違いは、非静的メソッド呼び出しを介して行われることであるINVOKEVIRTUALINVOKEINTERFACEまたはINVOKESPECIAL静的メソッドの呼び出しを介して行われている間INVOKESTATIC


2
ただし、プライベートインスタンスメソッドinvokespecialは仮想ではないため、(少なくとも通常は)を使用して呼び出されます。
Mark Peters、

ああ、面白いです。コンストラクタしか考えられなかったので、省略しました。ありがとう!(回答の更新)
aioobe

2
インスタンス化されるタイプが1つある場合、JVMは最適化します。BがAを拡張し、Bのインスタンスがインスタンス化されていない場合、Aのメソッド呼び出しは仮想テーブルルックアップを必要としません。
Steve Kuo

13

静的呼び出しと非静的呼び出しのパフォーマンスの違いがアプリケーションに影響を与えることは、信じられないほど考えられません。「時期尚早の最適化はすべての悪の根源」であることを忘れないでください。



「時期尚早の最適化がすべての悪の根源である」とはどういうことかをさらに説明していただけませんか?
user2121 2016年

問題は「何らかの方法でパフォーマンス上の利点があるか」ということでした。
DJクレイワース

13

7年後...

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

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);
    }
}

1
ここでは純粋に学術的な関心があります。この種のマイクロ最適化がops/s、ART環境以外のメトリック(たとえば、メモリ使用量、.oatファイルサイズの縮小など)にもたらす潜在的な利点について知りたいです。これらの他のメトリックのベンチマークを試みることができる比較的単純なツール/方法を知っていますか?
Ryan Thomas

ホットスポットは、クラスパスにInstanceSumへの拡張がないことを示しています。InstanceSumを拡張し、メソッドをオーバーライドする別のクラスを追加してみてください。
ミラノ

12

メソッドを静的にするかどうかを決定する場合、パフォーマンスの側面は無関係である必要があります。パフォーマンスの問題がある場合、多くのメソッドを静的にしても、その日を節約することはできません。そうは言っても、静的メソッドはほとんどの場合、インスタンスメソッドより遅くはなく、ほとんどの場合わずかに高速です。

1.)静的メソッドはポリモーフィックではないため、JVMは実行する実際のコードを見つけるための決定が少なくなります。これは、Age of Hotspotの問題点です。Hotspotは、実装サイトが1つしかないインスタンスメソッド呼び出しを最適化し、同じように実行するためです。

2.)もう1つの微妙な違いは、静的メソッドには明らかに「this」参照がないことです。これにより、スタックフレームは、同じシグネチャと本文を持つインスタンスメソッドのスロットよりも1スロット小さくなります(「this」は、バイトコードレベルのローカル変数のスロット0に配置されますが、静的メソッドの場合、最初のスロット0が使用されます。メソッドのパラメーター)。


5

違いがあるかもしれませんし、それは特定のコードに対してどちらの方向に行くかもしれませんし、JVMのマイナーリリースでさえ変わるかもしれません。

これは間違いなく、忘れてはならない小さな効率の97%の一部です。


2
違う。何も想定できません。これは、フロントエンドUIに必要なタイトなループであり、UIの「スナップ」に大きな違いをもたらす可能性があります。たとえばTableView、何百万ものレコードを検索します。
三部作

0

理論的には、より安価です。

静的な初期化は、オブジェクトのインスタンスを作成しても行われますが、静的なメソッドは、コンストラクターで通常行われる初期化を行いません。

しかし、私はこれをテストしていません。


1
@R。Bemrose、静的な初期化はこの質問とどう関係しているのですか?
カークウォル10/9/27

@Kirk Woll:静的初期化はクラスが初めて参照されるときに行われるため、最初の静的メソッド呼び出しの前を含みます。
Powerlord 2010

@R。Bemroseはもちろん、クラスをVMに読み込んでいます。非分離型のIMOのようです。
Kirk Woll

0

Jonが指摘しているように、静的メソッドはオーバーライドできないため、静的メソッドの呼び出しは、単純なJavaランタイムでは、インスタンスメソッドの呼び出しよりも高速です。

しかし、デザインをめちゃくちゃにして数ナノ秒を節約することに気がついていると仮定しても、別の問題が生じます。自分をオーバーライドするメソッドが必要ですか?インスタンスメソッドを静的メソッドにして、あちこちにナノ秒を節約するようにコードを変更してから、その上に独自のディスパッチャーを実装して実装すると、ほとんどの場合、ビルドしたものよりも効率が低下します。あなたのJavaランタイムにすでに。


-2

それはあなたの流れにも依存していることをここで他の素晴らしい答えに加えたいと思います、例えば:

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);
   };
};
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.