Являются ли статические вызовы Java более или менее дорогими, чем нестатические вызовы? - PullRequest
76 голосов
/ 27 сентября 2010

Есть ли какой-либо выигрыш в производительности, так или иначе? Это специфично для компилятора / виртуальной машины? Я использую Hotspot.

Ответы [ 12 ]

70 голосов
/ 27 сентября 2010

Во-первых: вы не должны делать выбор статического или нестатического на основе производительности.

Во-вторых: на практике это не будет иметь никакого значения.Hotspot может выбрать оптимизацию таким образом, чтобы сделать статические вызовы более быстрыми для одного метода, нестатические вызовы быстрее для другого.

В-третьих: многие мифы, окружающие статические и нестатические, основаны либо на очень старых JVM (который не имел ничего общего с оптимизацией, которую выполняет Hotspot), или некоторые запоминающиеся мелочи о C ++ (в котором динамический вызов использует на один больше доступа к памяти, чем статический вызов).

50 голосов
/ 14 февраля 2015

Четыре года спустя ...

Хорошо, в надежде решить этот вопрос раз и навсегда, я написал тест, который показывает, как различные виды вызовов (виртуальные, не виртуальные, статические) сравниваются друг с другом.

Я запустил его на ideone , и вот что я получил:

(Чем больше число итераций, тем лучше.)

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

Как и ожидалось, вызовы виртуальных методов - самые медленные, вызовы невиртуальных методов - быстрее, а вызовы статических методов - еще быстрее.

Чего я не ожидал, так это того, что различия будут настолько явными: измеренные вызовы виртуальных методов имели скорость меньше половины скорости вызовов невиртуальных методов, которые, в свою очередь, измерялись для выполнения в целом 15% медленнее , чем статические вызовы. Это то, что показывают эти измерения; фактические различия на самом деле должны быть немного более выраженными, поскольку для каждого вызова виртуального, не виртуального и статического методов мой код для сравнительного анализа имеет дополнительные постоянные накладные расходы, заключающиеся в увеличении одной целочисленной переменной, проверке логической переменной и выполнении цикла, если оно не истинно.

Я полагаю, что результаты будут варьироваться от процессора к процессору и от 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) {}
    } );
}

Стоит отметить, что эта разница в производительности применима только к коду, который не делает ничего, кроме вызова методов без параметров. Какой бы другой код вы ни использовали между вызовами, это уменьшит различия, и это включает передачу параметров. На самом деле, разница в 15% между статическими и не виртуальными вызовами, вероятно, объясняется в полном объеме тем, что указатель this не нужно передавать статическому методу. Таким образом, потребовалось бы довольно небольшое количество кода, выполняющего тривиальные вещи между вызовами, чтобы различие между различными типами вызовов было сведено до такой степени, чтобы не иметь никакого чистого влияния вообще.

Кроме того, вызовы виртуальных методов существуют по причине; у них есть цель, и они реализуются с использованием наиболее эффективных средств, предоставляемых базовым оборудованием. (Набор инструкций ЦП.) Если, если вы хотите устранить их, заменив их не виртуальными или статическими вызовами, вам придется добавить столько йоты дополнительного кода, чтобы эмулировать их функциональность, тогда ваши чистые накладные расходы будут связаны быть не меньше, а больше. Вполне возможно, много, много, непостижимо много, больше.

41 голосов
/ 27 сентября 2010

Ну, статические вызовы не могут быть переопределены (поэтому всегда являются кандидатами на встраивание), и не требуют каких-либо проверок недействительности. HotSpot выполняет кучу классных оптимизаций для методов экземпляров, которые могут свести на нет эти преимущества, но они возможные причины, по которым статический вызов может быть быстрее.

Однако это не должно влиять на ваш дизайн - код наиболее читабельным, естественным образом - и беспокоиться о такого рода микрооптимизации только в том случае, если у вас есть только одна причина (которой вы почти никогда не будете) .

18 голосов
/ 27 сентября 2010

Это зависит от компилятора / ВМ.

  • Теоретически можно сделать статический вызов немного эффективнее, потому что это не нужно делать виртуальную функцию поиск, и он также может избежать накладные расходы скрытого "это" параметр.
  • На практике , многие компиляторы все равно оптимизировать это.

Следовательно, вероятно, не стоит беспокоиться, если вы не определили это как действительно критическую проблему производительности в вашем приложении. Преждевременная оптимизация - корень всего зла и т. Д ...

Однако я видел , что эта оптимизация дает существенное увеличение производительности в следующей ситуации:

  • Метод выполнения очень простого математического вычисления без обращений к памяти
  • Метод, вызываемый миллионов раз в секунду в тесной внутренней петле
  • Приложение с привязкой к процессору, где важен каждый бит производительности

Если вышеизложенное относится к вам, возможно, стоит попробовать.

Существует также еще одна веская (и, возможно, даже более важная!) Причина использовать статический метод - если метод на самом деле имеет статическую семантику (т. Е. Логически не связан с данным экземпляром класса), то имеет смысл сделать это статичным, чтобы отразить этот факт. Затем опытные Java-программисты заметят статический модификатор и сразу подумают: «Ага! Этот метод статичен, поэтому ему не нужен экземпляр и, по-видимому, он не манипулирует конкретным состоянием экземпляра». Таким образом, вы будете эффективно сообщать статическую природу метода ...

14 голосов
/ 27 сентября 2010

Как говорилось в предыдущих постерах: это кажется преждевременной оптимизацией.

Однако есть одно отличие (часть от того факта, что нестатические вызовы требуют дополнительного нажатия объекта вызываемого настек операндов):

Поскольку статические методы не могут быть переопределены, не будет никаких виртуальных поисков во время выполнения для вызова статического метода.Это может привести к заметной разнице в некоторых обстоятельствах.

Разница на уровне байт-кода заключается в том, что вызов нестатического метода выполняется через INVOKEVIRTUAL, INVOKEINTERFACE или INVOKESPECIAL, в то время как статический вызов метода выполняется через INVOKESTATIC.

11 голосов
/ 27 сентября 2010

Для решения о том, должен ли метод быть статичным, аспект производительности не должен иметь значения.Если у вас проблемы с производительностью, статические методы не спасут день.При этом статические методы почти наверняка не медленнее , чем любой метод экземпляра, в большинстве случаев незначительно быстрее :

1.) Статические методы не являются полиморфными, поэтомуJVM имеет меньше решений, чтобы найти фактический код для выполнения.Это спорный вопрос в Age of Hotspot, поскольку Hotspot оптимизирует вызовы методов экземпляра, которые имеют только один сайт реализации, поэтому они будут выполнять то же самое.

2.) Другое тонкое отличие состоит в том, что статические методы, очевидно, не имеют ссылки "this".Это приводит к тому, что кадр стека на один слот меньше, чем у экземпляра метода с той же сигнатурой и телом («this» помещается в слот 0 локальных переменных на уровне байт-кода, тогда как для статических методов слот 0 используется для первогопараметр метода).

11 голосов
/ 27 сентября 2010

Невероятно маловероятно, что какое-либо различие в производительности статических и нестатических вызовов имеет значение в вашем приложении. Помните, что «преждевременная оптимизация - корень всего зла».

9 голосов
/ 27 декабря 2017

7 лет спустя ...

Я не очень уверен в результатах, которые обнаружил Майк Накис, потому что они не решают некоторые общие проблемы, связанные с оптимизацией 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 и ОС.

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);
    }
}
4 голосов
/ 27 сентября 2010

Может быть разница, и она может пойти в любую сторону для любого конкретного фрагмента кода, и она может измениться даже при незначительном выпуске JVM.

Это, безусловно, часть 97% малых показателей эффективности, о которых вы должны забыть .

0 голосов
/ 10 июля 2015

Я хотел бы добавить к другим отличным ответам, что это также зависит от вашего потока, например:

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