Являются ли статические вызовы Java более или менее дорогостоящими, чем нестатические вызовы?

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

Ответ 1

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

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

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

Ответ 2

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

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

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

(Лучшее количество итераций лучше.)

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

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

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

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

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

Ответ 3

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

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

Ответ 4

Он специфичен для компилятора /VM.

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

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

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

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

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

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

Ответ 5

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

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

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

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

Ответ 6

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

Ответ 7

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

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

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

Ответ 9

В теории, менее дорого.

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

Однако я не тестировал это.

Ответ 10

Как отмечает Джон, статические методы не могут быть переопределены, поэтому простое обращение к статическому методу может быть - на достаточно наивной Java-среде выполнения - быстрее, чем вызвать метод экземпляра.

Но тогда, даже если предположить, что вы находитесь в той точке, где вам небезразличен ваш дизайн, чтобы сэкономить несколько наносекунд, это просто вызывает другой вопрос: вам нужен метод, переопределяющий себя? Если вы измените свой код вокруг, чтобы сделать метод экземпляра статическим методом, чтобы сохранить наносекунду здесь и там, а затем развернуться и реализовать свой собственный диспетчер поверх этого, ваш почти наверняка будет менее эффективным, чем тот, который был построен в вашу среду выполнения Java.

Ответ 11

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

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