Чи є якісь переваги продуктивності так чи інакше? Це специфічно для компілятора / віртуальної машини? Я використовую Hotspot.
Чи є якісь переваги продуктивності так чи інакше? Це специфічно для компілятора / віртуальної машини? Я використовую 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% менше пропускної здатності. Це може бути специфічним для АРТ явищем, адже я отримую очікуваний результат на планшеті Android 4.4:VirtualTest | 138183740 -- NonVirtualTest | 142268636 -- StaticTest | 161388933
Добре, що статичні дзвінки не можна перекрити (тому вони завжди є кандидатами на вбудовану лінію) і не потребують перевірки недійсності. HotSpot робить купу крутих оптимізацій, наприклад, методів, які цілком можуть заперечувати ці переваги, але вони можливі, чому статичний дзвінок може бути швидшим.
Однак це не повинно впливати на ваш дизайн - код найбільш читабельним, природним чином - і турбуйтеся про такий різновид мікрооптимізації лише якщо у вас є лише причина (чого у вас майже ніколи не буде).
Це спеціально для компілятора / віртуальної машини.
Тому, мабуть, не варто турбуватися, якщо ви не визначили це як справді критичну проблему продуктивності у своєму додатку. Передчасна оптимізація - корінь усього зла тощо ...
Однак я вже бачив це оптимізація дає істотне збільшення продуктивності в такій ситуації:
Якщо вищезазначене стосується вас, можливо, варто перевірити.
Існує також ще одна хороша (і потенційно навіть важливіша!) Причина використання статичного методу - якщо метод насправді має статичну семантику (тобто логічно не пов'язаний із заданим екземпляром класу), то має сенс зробити його статичним щоб відобразити цей факт. Потім досвідчені програмісти Java помітять статичний модифікатор і відразу подумають "ага! Цей метод є статичним, тому йому не потрібен екземпляр і, імовірно, не маніпулює конкретним станом екземпляра". Отже, ви ефективно передасте статичну природу методу ....
Як говорили попередні плакати: Це здається передчасною оптимізацією.
Однак є одна відмінність (частина від того, що нестатичні виклики вимагають додаткового натискання викличеного об'єкта на стек операндів):
Оскільки статичні методи неможливо замінити, віртуальних пошуків під час виконання статичного виклику методу не буде. Це може призвести до помітної різниці за певних обставин.
Різниця на рівні байтового коду полягає в тому, що виклик нестатичного методу здійснюється наскрізь INVOKEVIRTUAL
, INVOKEINTERFACE
або в INVOKESPECIAL
той час як статичний виклик методу здійснюється наскрізь INVOKESTATIC
.
invokespecial
оскільки він не є віртуальним.
Неймовірно, що будь-яка різниця у виконанні статичних та нестатичних викликів може змінити вашу програму. Пам'ятайте, що "передчасна оптимізація - корінь усього зла".
7 років потому ...
Я не маю величезної впевненості в результатах, які знайшов Майк Накіс, оскільки вони не вирішують деяких загальних питань, пов'язаних з оптимізацією точки доступу. Я інструментував тести, використовуючи 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 має менше рішень для пошуку фактичного коду для виконання. Це спірний момент у епоху точки доступу, оскільки точка доступу оптимізує виклики методів екземпляра, які мають лише один сайт реалізації, тому вони будуть виконувати те саме.
2.) Ще одна тонка відмінність полягає в тому, що статичні методи, очевидно, не мають посилання "це". Це призводить до того, що кадр стека на один слот менше, ніж у методу екземпляра з однаковим підписом і тілом ("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);
};
};