Есть ли какой-то выигрыш в производительности так или иначе? Это конкретный компилятор / виртуальная машина? Я использую точку доступа.
Есть ли какой-то выигрыш в производительности так или иначе? Это конкретный компилятор / виртуальная машина? Я использую точку доступа.
Ответы:
Во-первых: вам не следует выбирать статический или нестатический на основе производительности.
Второе: на практике никакой разницы не будет. Hotspot может выбрать оптимизацию таким образом, чтобы статические вызовы выполнялись быстрее для одного метода, а нестатические вызовы - для другого.
В-третьих: большая часть мифов, окружающих статическое и нестатическое, основаны либо на очень старых JVM (которые не имели ничего общего с оптимизацией, как у Hotspot), либо на некоторых помнятых мелочах о C ++ (в котором динамический вызов использует еще один доступ к памяти чем статический вызов).
Четыре года спустя...
Хорошо, в надежде решить этот вопрос раз и навсегда, я написал тест, который показывает, как разные типы вызовов (виртуальные, не виртуальные, статические) сравниваются друг с другом.
Я запустил его на 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
указатель не должен быть передан статическим метод. Таким образом, потребовалось бы лишь довольно небольшое количество кода, выполняющего тривиальные вещи между вызовами, чтобы разница между различными типами вызовов уменьшилась до такой степени, что не оказало никакого общего влияния.
Кроме того, вызовы виртуальных методов существуют не просто так; у них действительно есть цель, и они реализуются с использованием наиболее эффективных средств, предоставляемых базовым оборудованием. (Набор инструкций ЦП.) Если, желая устранить их, заменив их невиртуальными или статическими вызовами, вам в конечном итоге придется добавить не меньше йоты дополнительного кода для имитации их функциональности, тогда ваши итоговые чистые накладные расходы будут ограничены быть не меньше, а больше. Вполне возможно, намного, намного, непостижимо много, больше.
VirtualTest | 488846733 -- NonVirtualTest | 480530022 -- StaticTest | 484353198
на моей установке OpenJDK. FTR: Это даже правда, если я уберу final
модификатор. Кстати. Пришлось пройти terminate
поле volatile
, иначе тест не закончился.
VirtualTest | 12451872 -- NonVirtualTest | 12089542 -- StaticTest | 8181170
. Мало того, что OpenJDK на моем ноутбуке удается выполнить в 40 раз больше итераций, статический тест всегда дает примерно на 30% меньше пропускной способности. Это может быть специфическое явление ART, потому что я получаю ожидаемый результат на планшете Android 4.4:VirtualTest | 138183740 -- NonVirtualTest | 142268636 -- StaticTest | 161388933
Что ж, статические вызовы не могут быть переопределены (поэтому всегда являются кандидатами на встраивание) и не требуют никаких проверок на нулевое значение. HotSpot выполняет кучу классных оптимизаций, например, методов, которые могут свести на нет эти преимущества, но это возможные причины, по которым статический вызов может быть быстрее.
Однако это не должно влиять на ваш дизайн - код наиболее читаемым и естественным образом - и беспокоиться о такого рода микрооптимизации только в том случае, если у вас есть веская причина (чего вы почти никогда не будете).
Это зависит от компилятора / виртуальной машины.
Следовательно, вероятно, не стоит беспокоиться, если вы не определили это как действительно критическую проблему производительности в вашем приложении. Преждевременная оптимизация - это корень всех зол и т. Д.
Однако я уже видел это оптимизация дает существенное увеличение производительности в следующей ситуации:
Если вышесказанное относится к вам, возможно, стоит попробовать.
Есть также еще одна хорошая (и потенциально даже более важная!) Причина использовать статический метод - если метод действительно имеет статическую семантику (т.е. логически не связан с данным экземпляром класса), то имеет смысл сделать его статическим. чтобы отразить этот факт. Тогда опытные Java-программисты заметят модификатор static и сразу же подумают: «Ага! Этот метод статический, поэтому ему не нужен экземпляр и, по-видимому, он не управляет состоянием конкретного экземпляра». Таким образом, вы эффективно передадите статический характер метода ...
Как говорилось на предыдущих плакатах: это похоже на преждевременную оптимизацию.
Однако есть одно отличие (отчасти из-за того, что нестатические вызовы требуют дополнительного проталкивания вызываемого объекта в стек операндов):
Поскольку статические методы нельзя переопределить, во время выполнения не будет никаких виртуальных поисков для вызова статического метода. При некоторых обстоятельствах это может привести к заметной разнице.
Разница на уровне байт-кода является то , что не-статический метод вызова выполняется через INVOKEVIRTUAL
, INVOKEINTERFACE
или в INVOKESPECIAL
то время как статический метод вызова выполняется через INVOKESTATIC
.
invokespecial
поскольку он не является виртуальным.
Маловероятно, что какое-либо различие в производительности статических и нестатических вызовов имеет значение для вашего приложения. Помните, что «преждевременная оптимизация - корень всех зол».
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);
}
}
ops/s
микрооптимизация может иметь для показателей, отличных от основных в среде ART (например, использование памяти, уменьшенный размер файла .oat и т. Д.). Знаете ли вы какие-либо относительно простые инструменты / способы, которыми можно было бы попытаться сравнить эти другие показатели?
Для решения, должен ли метод быть статическим, аспект производительности не должен иметь значения. Если у вас проблемы с производительностью, создание статических методов не спасет положение. Тем не менее, статические методы почти наверняка не медленнее, чем любой метод экземпляра, в большинстве случаев немного быстрее :
1.) Статические методы не являются полиморфными, поэтому JVM должна принимать меньше решений, чтобы найти фактический код для выполнения. Это спорный вопрос в Age of Hotspot, поскольку Hotspot оптимизирует вызовы методов экземпляра, которые имеют только один сайт реализации, поэтому они будут работать одинаково.
2.) Еще одно тонкое отличие состоит в том, что у статических методов явно нет ссылки this. В результате кадр стека на один слот меньше, чем у метода экземпляра с той же сигнатурой и телом ("this" помещается в слот 0 локальных переменных на уровне байт-кода, тогда как для статических методов слот 0 используется для первого параметр метода).
Может быть разница, и она может повлиять на любой конкретный фрагмент кода, и она может измениться даже с незначительным выпуском JVM.
Это определенно часть из 97% небольших показателей эффективности, о которых вам следует забыть .
TableView
миллионах записей.
Теоретически дешевле.
Статическая инициализация будет выполняться, даже если вы создаете экземпляр объекта, тогда как статические методы не будут выполнять никакой инициализации, обычно выполняемой в конструкторе.
Однако я этого не проверял.
Как отмечает Джон, статические методы нельзя переопределить, поэтому простой вызов статического метода может быть - в достаточно наивной среде выполнения Java - быстрее, чем вызов метода экземпляра.
Но тогда, даже если предположить, что вы находитесь в той точке, где вы беспокоитесь о том, чтобы испортить свой дизайн, чтобы сэкономить несколько наносекунд, это просто поднимает другой вопрос: понадобится ли вам метод, переопределяющий себя? Если вы измените свой код, чтобы превратить метод экземпляра в статический метод, чтобы сэкономить наносекунду здесь и там, а затем развернитесь и внедрите свой собственный диспетчер поверх этого, ваш почти наверняка будет менее эффективным, чем созданный уже в вашу среду выполнения Java.
Я хотел бы добавить к другим отличным ответам здесь, что это также зависит от вашего потока, например:
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);
};
};