Как стать автором
Обновить

Комментарии 166

Старайтесь как можно реже использовать оператор instanceOf. Это один из самых медленных java операторов

А можно поподробнее? Что значит «самый медленный оператор»?
«instanceof» на интерфейсах использует достаточно сложную систему проверок из-за возможности реализовывать множество интерфейсов (а также из-за их собственного дерева наследования). Собственно, этого вполне достаточно, чтобы этот оператор можно было считать медленным. Хотя, на классах эта проверка делается за константное время.
Все верно, в то же время, если противопоставлять instanceOf и полиморфизм, то разницы в скорости практически не будет, так как используются одни и те же механизмы.
Для виртуального вызова используется таблица виртуальных функций и, зачастую, спекулятивная оптимизация. Это пара обращений к памяти.

Определение принадлежности к интерфейсу — практически поиск в глубину по дререву предков класса.
Блин, xappymah, не от тебя я хотел ответ услышать :)
В общем случае (при применении к интерфейсам) — медленный, т.к. против оптимизации поиска интерфейсов в дереве наследования в HS несложно создать контр-пример, который сведет ее на нет.
Впрочем, в реальной жизни такие контр-примеры случаются реже.
НЛО прилетело и опубликовало эту надпись здесь
Оберните код тегом «source».

Все внятно и очевдно. Читается в Effective Java. Вот на счет интерфейса для констант не соглашусь. Часто встречаю такую практику и считаю ее примлемой. Хотя, сам стараюсь писать финальный класс.

Пруфф по поводу интерфейсов для констант.
нет ничего плохого определить константы в интерфейсе, если он не реализуется ни одним классом, но, поскольку гарантировать этого нельзя, то лучше не надо.
Всё-таки думаю, что это не «пруфф». К сожалению, Java API не везде спроектировано идеально (особенно, ранние версии). Есть куча примеров плохой реализации и проектирования API в Java (например, тут нелпохое обсуждение: stackoverflow.com/questions/186964/java-core-api-anti-patterns-what-is-wrong). Тот же Блох, на книгу которого вы ссылаетесь, приводит много примеров плохого JDK API.
Спасибо за тег. Такая практика действительно частая, собственно это и подвигло меня к этому опусу.
НЛО прилетело и опубликовало эту надпись здесь
Для получения shortcut'ов на константы можно использовать static import. Но и его нужно использовать очень осмотрительно.
Неужели всё это не оптимизируется современными компиляторами?
Реквестирую пруфы с замерами производительности!
строковую конкатенацию вроде оптимизируют в новых JDK. А с new vs valueOf оптимизация затруднительна — new должен гарантированно давать новый объект (мало ли кто что потом будет с ним делать, например использовать в качестве монитора синхронизации или ключа в IdentityHashMap)
Да, оптимизация происходит, но не в случае что я описал. Компилятор оптимизирует код, но в своем понимании:
s = s + fileds[i]
транслируется в
s = new StringBuilder(s).append(fields[i]).toString();
Но ведь логика-то какая-то должна в этом быть. Почему именно так, а, например, не как вручную?
Ну, я имею в виду в контексте приведенного примера. Почему бы не использовать один StringBuilder для массового сложения строк? Неужели подобный анализ невозможно провести?
String[] fields = new String[] {"a","b","c","d","e","f","g"};
String s = "";
for (int i = 0; i < fields.length; i++) {
    s = s + fields[i];
    doSomeThing(s);
}


на момент вызова doSomeThing(s) s должно быть вычислено
Если я правильно понял, тут отличие лишь в том, что при каждой итерации будет создаваться новый StringBuilder?
это я пытаюсь показать почему нельзя прооптимизировать как вы хотите, путем избежания создания StringBuilder.
там есть хитрая опция XX:+OptimizeStringConcat, которая, по идее, должна убирать StringBuilder.toString() в ситуациях когда можно обойтись без него. Но документация по ней туманна, когда она реально помогает мне не понятно
Еще часто приходится видеть такое «экспресс»-преобразование чисел в строки
int a = 12;
String s = a + "";
вместо
String s = String.valueOf(12);
На пхп любят писать еще эпичнее:
$a = "2";
$a = $a + 0; // преобразовуем в число
Это дичайший реликтовый образец. :)

Ну я еще могу понять, когда появляются странные штуки при реализации логики, взаимодействия классов. Но вот такой хитровыкрученный пример — это явный «изыск».
Да, ещё в JavaScript можно сделать быстрое приведение:
var a = '7';
var b = a - 0;


А в PHP достаточно такого: $a = (int|bool|array|string|object)$a;
А в PHP достаточно такого: $a = (int|bool|array|string|object)$a;


Ну в этом-то и дело.
Сейчас ради интереса посмотрел в мануал по PHP: найти описание явного приведения типов намного проще, и легче запомнить, чем немаленькие таблицы неявных приведений.

Отсюда вопрос: кто будет сознательно использовать приведение типов через арифметические (строковые) операции?

Ну и в той же Java: ну кому, кому блин может придти в голову, намеренно преобразовать число в строку через конкатенацию числа и строки? :)
ну кому, кому блин может придти в голову, намеренно преобразовать число в строку через конкатенацию числа и строки
Кажется, что в голову прийти не может, пока не начинаешь читать чужой код и общаться на форумах) Такое (и хуже) сплошь и рядом. Ниже каменты с плюсами, где на это говорится в духе «а что такого».
Все-таки я нашел этот коммент )
Спасибо, действительно такое часто происходит. Добавил в топик
Может быть автор имел в виду
String s = String.valueOf(a);
поскольку не проще ли
String s = «12»;
Да, именно так
А еще маленькая хитрость (на правах иронии):
вместо
for(i=0;i<array.length;i++) item=array[i];

используйте
for(item:array)
Вообще, никогда не используйте .length внутри цикла, т.к. тогда он будет на каждой итерации вызывать метод подсчета длины
1. неужто нет оптимизации кешированием?
2. какой пересчет? длина массивов неизменна
Тем не менее — метод будет вызываться каждый раз
.length — не метод, а свойство.
Но вполне допускаю, что для коллекций size() будет вызываться на каждой итерации
А не геттер?
позвольте, это Java, а не .NET — здесь с этим немного очевиднее
Ах, точно. В последнее время одновременно пишу минимум на двух-трех языках. По-этому иногда путаюсь :)
Особенно опасно писать на внешне похожих языках\платформах… Начинаешь необоснованно на одной платформе бояться\пытаться использовать особенности\баги другой платформы, даже если первой это не свойственно — а это о ч е н ь чревато.
НЛО прилетело и опубликовало эту надпись здесь
Кстати, да. В случае коллекций — вспомнил — тоже. Потому что, если размер изменится, причем не с помощью iterator.remove(), то получим ConcurrentModificationException.
Только получим его не на проверке размера, а на обращении к «просроченному» итератору в случае foreach-нотации.
В случае обхода List'а через size() exception'а не будет в случае обращения по индексу (в случае отсутствия конкурентного изменения несколькими потоками), однако рискуем пропустить несколько элементов в случае удаления элементов из коллекции без изменения переменной цикла. Иногда замечается у студентов-нубов, переползающих с C/C++.
arraylength — это инструкция

Во втором случае она будет вынесена за цикл, поэтому такой хитрожопый код будет работать:
private static void two(String[] fields) {
	for (String s : fields) {
		System.out.println(s);
		fields = null;
	}
	System.out.println(fields);
}


а такой отвалится с NPE:
private static void one(String[] fields) {
	for (int i = 0; i < fields.length; i++) {
		System.out.println(fields[i]);
		fields = null;
	}
	System.out.println(fields);
}
Нет, не поэтому. А потому, что в первом случае используется fields.iterator(), с которым ничего не происходит, когда обнуляем сам fields, а во втором на каждой итерации пытаемся обращаться к fields, который успешно зануллен в конце первой итерации
Давайте будем проверять :) Никакого итератора для массивов нет.

private void one(java.lang.String[]);
  Code:
   0:	iconst_0
   1:	istore_2
   2:	iload_2
   3:	aload_1
   4:	arraylength
   5:	if_icmpge	23
   8:	getstatic	#2; //Field java/lang/System.out:Ljava/io/PrintStream;
   11:	aload_1
   12:	iload_2
   13:	aaload
   14:	invokevirtual	#3; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   17:	iinc	2, 1
   20:	goto	2
   23:	return

private void two(java.lang.String[]);
  Code:
   0:	aload_1
   1:	astore_2
   2:	aload_2
   3:	arraylength
   4:	istore_3
   5:	iconst_0
   6:	istore	4
   8:	iload	4
   10:	iload_3
   11:	if_icmpge	36
   14:	aload_2
   15:	iload	4
   17:	aaload
   18:	astore	5
   20:	getstatic	#2; //Field java/lang/System.out:Ljava/io/PrintStream;
   23:	aload	5
   25:	invokevirtual	#3; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   28:	aconst_null
   29:	astore_1
   30:	iinc	4, 1
   33:	goto	8
   36:	return

}

Мы можем массив засунуть в for(:) только потому, что он является внутренним обьектом Array, который реализует Iterable, а в нем определен iterator().
нет, foreach работает по разному для массивов и Iterable и массив не реализует Iterable
Зачем писать если не знаете? Проверили бы.
Массив реализует ровно два интерфейса — Cloneable и Serializable.
Об этом явно написано в JLS (который Вы, скорее всего, еще не открывали :-) ).
Прошу прощения, правильный листинг такой, но суть не меняется:

private static void one(java.lang.String[]);
Code:
0: iconst_0
1: istore_1
2: iload_1
3: aload_0
4: arraylength
5: if_icmpge 25
8: getstatic #7; //Field java/lang/System.out:Ljava/io/PrintStream;
11: aload_0
12: iload_1
13: aaload
14: invokevirtual #8; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
17: aconst_null
18: astore_0
19: iinc 1, 1
22: goto 2
25: getstatic #7; //Field java/lang/System.out:Ljava/io/PrintStream;
28: aload_0
29: invokevirtual #9; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V
32: return

private static void two(java.lang.String[]);
Code:
0: aload_0
1: astore_1
2: aload_1
3: arraylength
4: istore_2
5: iconst_0
6: istore_3
7: iload_3
8: iload_2
9: if_icmpge 33
12: aload_1
13: iload_3
14: aaload
15: astore 4
17: getstatic #7; //Field java/lang/System.out:Ljava/io/PrintStream;
20: aload 4
22: invokevirtual #8; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
25: aconst_null
26: astore_0
27: iinc 3, 1
30: goto 7
33: getstatic #7; //Field java/lang/System.out:Ljava/io/PrintStream;
36: aload_0
37: invokevirtual #9; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V
40: return

}
но предыдущий был красивее оформлен = (
В первом случае неявно создаётся дополнительная ссылка на массив?
да, в методе two()
НЛО прилетело и опубликовало эту надпись здесь
А разве в простых случаях такой стандартный fori не съеживается до ассемблерного лупа?
НЛО прилетело и опубликовало эту надпись здесь
«N итераций» это настолько частый случай в программировании, что можно смело полагаться на jit компилятор в том, что он оптимизирует его настолько, насколько возможно. В идеале сведет к loop. Поэтому не надо думать за компилятор.

Так я рассуждаю в подобных случаях.
НЛО прилетело и опубликовало эту надпись здесь
    mov cx, 10
label:
    ...
    loop label
НЛО прилетело и опубликовало эту надпись здесь
Эх. Современные кодогенераторы практически не используют инструкцию «loop». «Практически» — написано, потому что я не могу на 100% сказать про все существующие в природе кодогенераторы. ;)
С ней куча ненужного гемора по сравнению с обычными cmp & jmp. Начиная с помех регаллокатору, и заканчивая вложенными циклами.
С появлением макофьюжена — вообще стала бессмысленна.
Живет только потому, что Intel не выбрасывает ничего ненужного. ;)
Макрофьюжена.
Ну так поэтому я и не уточнил в первом комментарии, имея ввиду любой из простых ассемблерных циклов, а не перекладывание значений с места на место, которым якобы должна заниматься виртуальная машина в лице процессора.
*заниматься при виде
for (int i = 0; i < array.length; i++)
Можно еще так писать:

for(int i=0, size = array.length; i < size; i++) item=array[i];

* This source code was highlighted with Source Code Highlighter.
Лучше использовать итератор (или for() c Java 1.5, что то-же самое) ибо у array[i] появляются накладные расходы на получение значения по индексу.

У итератора время получения значения — постоянное и почти не зависит от размера коллекции.
«почти» — все зависит от того, насколько равномерно распределены элементы и отсутствие коллизий.
Ой. Как все запущено…
все зависит от того, насколько равномерно распределены элементы и отсутствие коллизий


<irony> Ага, для массивов все именно так и происходит. :-) Прощай, скорость. :-) </irony>
Сюрпрайз. Для
for(int i=0;i<array.length;i++)

и
for(int i=0, size = array.length;i<size;i++)

Будет порожден один и тот же x86 код при использовании ключика -server.
Про -client просто не знаю. Но кому нужен -client?
Мой ноут по дефолту определяется как server-side machine. :)

ЗЫ Да я знаю случаи, когда вышесказанное не так. Но обычный и правильный девелопер с таким вряд ли столкнется. ;)
вместо
for(i=0;i<array.length;i++) item=array[i];

используйте
for(item:array)


Существует мнение, почёрпнутое из собеседования одного очень мозговитого товарища, долгое время занимавшегося декомпилятором и анскремблером Java, что foreach чрезвычайно невыгодная операция, и что для коллекций она компилируется в fori с предварительной конвертацией коллекции в Array. Отвечать за правдивость и распространённость такого поведения не хотелось бы, но умолчать об этом тоже не в силах.
Т.е. звучит так: «никогда не используте!»?
> с предварительной конвертацией коллекции в Array
это неправда.
Слава богу!
Я напишу ребятам из своей прошлой конторы, пускай дадут челу внеочередной отпуск.
А вообще очень много таких советов может дать какой-нибудь FindBugs или Checkstyle
А есть разница в скорости между
String[] fields = new String[] {"a","b","c","d","e","f","g"};
String s = "";
for (int i = 0; i < fields.length; i++) {
    s = s + fields[i];
}
return s;
и
String[] fields = new String[] {"a","b","c","d","e","f","g"};
String s = "";
for (int i = 0; i < fields.length; i++) {
    s += fields[i];
}
return s;
Никакой.
В обоих случаях конкатенация будет сведена в один и тот же байткод, использующий StringBuilder, так что разницы быть не должно.
Более того, в Java даже нет разницы между this.intfield++ и this.intfield = this.intfield + 1
И в C# тоже. В свое время это поразило меня до глубины души, когда я (ну молодой был) полдня пытался понять, почему моя многопоточная программа не работает, и даже подумать не мог на ++, т.к. считал ее заведомо атомарной. Потом уж узнал про чудо-метод Interlocked.Increment…
Кстати современные IDE большинство этих случаем пометят как «недоработку», пользуюсь эклипс и она очень хорошо эти косяки подмечает, а в итоге и сам юыстро привыкаешь так не делать.
А какой смысл в современных промышленных Java-приложениях, вовсю использующих мегабайты спрингов, хайбернейтов и прочей дживити, заниматься оптимизацией на уровне инициализации строк? То есть с точки зрения чистоты кода и дзена программирования оно понятно, что надо делать как надо, но вы же апеллируете именно к скорости выполнения. Очень часто проблем с ней и с нагрузкой на сервер не возникает, а если возникает, бизнес-логику смотреть надо в первую очередь, потом фреймворки, а не эти мелочи.
Проще ввести везде положительную практику, чем потом втыкать в профайлер. Кроме того использование объекта строителя несколько яснее выражает идею кода, ИМХО.
Если у вас есть проблемы с производительностью на уровне криво написанной бизнес-логики, отсутствием кэширования, раздутым стеком фреймворков, то применение всех вышеописанных методик поможет увеличить производительность вашего кода не более, чем на 0,19% (в лучшем случае и только для небольшого проекта).
Если такая печаль то практики всё-равно не соблюдаются и баланс света с тьмой восстанавливается сам по себе.
Мало того, если во всех случаях использования строк расово верно всё делать через StringBuilder, не задумываясь о потерянном на лишнее кодирование времени разработчика, и полученных от этого сотых долях процента прироста производительности, то к концу срока разработки проекта может оказаться, что суммарного количества потерянного времени могло бы запросто хватить на пару-тройку серьёзных оптимизаций бизнес-логики и прироста производительности процентов на 200 8)
Насчет сложения строк в цикле.

Если это какой-нибудь legacy-код разбора XML, то в результате сбора строчки размером в 2 Mb можно потерять несколько драгоценных секунд. :-)
Далеко не все java проекты проекты используют стэк из spring, hibernate и им подобных и далеко не все java проекты промышленные. Чаще всего это аутсорс и большие компании. А в маленьких игровых проектах и специализированных приложениях вполне обходятся без всего этого груза.
С маленьким джава-игроделом не сталкивался, но предположу, что и там свои библиотеки есть, и графика ресурсы жрет, и в случае необходимости нужно будет таки запускать профайлер, и толку от него будет больше на полтора-два порядка.
>А какой смысл в современных промышленных Java-приложениях, вовсю использующих мегабайты спрингов, хайбернейтов и прочей дживити, заниматься оптимизацией на уровне инициализации строк?

Вы, вероятно, забыли, что Java — это не только «современные промышленные Java-приложения, вовсю использующие мегабайты спрингов, хайбернейтов и прочей дживити», но и, скажем, мобильные приложения (Android).
Я это прекрасно понимаю, но тем не менее не думаю, что и на Андроиде можно выиграть в производительности, просто избавившись от instanceof и конструкторов строк. Да, так надо делать, да так лучше, но к производительности это имхо не имеет практически никакого отношения.
но к производительности это имхо не имеет практически никакого отношения.

Ну так и топик не называется «повышение проивзодительности java приложений».
кеш...3.5 раза быстрее… экономия памяти...IntegerCache.high...//медленно… очень существенно снизить производительность… Целых 3 операции вместо одной!.. производительность будет выше… один из самых медленных java операторов… — это ведь из статьи все.

Ну и мажорная нота в финале: «не плохо ускорить работу ваших программ, сами того не подозревая».
Доводилось тестировать банковское ПО, написанное программистами с вашим подходом. В системных требованиях сотни мегабайт ОЗУ и прочее. Под неполной нагрузкой ведет себя черте как. В чем проблема написать хотя бы для конкатенации строк в цикле более быстрый вариант вместо «ленивого» короткого? Не будут плодиться лишние сущности и задирать планку требований. На хабре кстати была статья именно про оптимизацию работы со строками на примере пром приложения.
То есть вы хотите сказать, что после оптимизации конкатенации строк приложение взлетело? Перечитайте пожалуйста то, что я писал выше. Это не мой подход, я применяю практически все описанное здесь, и топикстартер правильно говорит, что нужно писать ясный читаемый код, но почему-то делает упор в основном на производительность.
Какая там проводится оптимизация, и проводится ли она, мне не ведомо (авторы забугорные и прямого доступа к ним у меня не было). Но для тех целей, для которых её создали, аппетиты избыточны изначально, не говоря про протекания. Я привел пример с промышленных сред лишь потому, что вы слишком яро отрицаете влияние казалось бы таких мелочей. Часто в проме используются системы, которые должны обрабатывать неимоверное количество запросов, так что на производительности сказывается всё.
Дело не в фанатичном отрицании как таком. Возможно, я был не совсем правильно понят. Я в своих комментариях хотел указать на две вещи. Во-первых, все надо делать постепенно. Во-вторых, не нужно смешивать все в одну кучу.

Если мы говорим об оптимизации готового приложения на Java будь то веб, пром, игры, андроид, нужно заниматься ей сверху вниз. Брать профайлер, анализировать логи и поведение, находить узкие места, думать над логикой, переписывать её для оптимизации, тестировать, забывать. В 99,67% случаев именно такой подход необходим и достаточен и дополнительных микрооптимизаций не требуется. В случае работы над новым проектом или кодом, нужно принимать во внимание потенциальные проблемы с производительностью на уровне опять же бизнес-логики, и всё.

То, что описано в статье, безусловно нужно делать, но при этом понимать, что делается это в целях повышения читабельности кода, приведения его к единому стандарту и т.д., но не делать упора на оптимизацию, так как в подавляющем большинстве случаев применение всех описанных практик не даст абсолютно никакого прироста производительности.
Собственно вывод об этом и говрит. Извиняюсь, если сложилось впечетление из статьи, что эти шаги — оптимизация производительности
Я например пишу сейчас приложение (не промышленное), в котором любой автобоксинг подобен смерти. Приходится за всеми этими «штучками» внимательно следить. Собственно, как уже сказали выше, пост написан не только для ентерпрайза, конечно.
И вы все описанные в статье штучки прекрасно знаете, не так ли? :)

Из статьи — все. Но из комментариев кое-что для себя почерпнул.
Честно говоря не понял, к чему вопрос.
К тому, что эта статья — не руководство по оптимизации, о чем я вначале и написал. Начинающим надо начинать не с этого, а тем, кто немного разбирается в предмете, все эти мелочи известны. Хотел сказать, что описанные вопросы надо рассматривать в другом контексте. Ну да хватит наверное захламлять топик одним и тем же.
так как в StringBuilder нету синхронизированных методов в отличие от StringBuffer и следовательно производительность будет выше, хоть и не значительно.
Это фактически бесполезное опасение, при однопоточном доступе лишняя синхронизация отлично оптимизируется компилятором по крайней мере до lightweight locking, а то и до пропуска блокировки (в данных примерах скорее всего). В любом случае это точно не то, что программисту стоит первым делом оптимизировать.
можно увеличить кеш для Integer через системное свойство «java.lang.Integer.IntegerCache.high», а так же через параметр виртуальной машины -XX:AutoBoxCacheMax=Тут нужно сказать, что речь только о 7 версии, ибо до сих пор это было захардкожено внутри java.lang.Integer. Думаю, для многих это сюрприз :)
Блин, косяк с цитированием, сорри. Вообще, так настойчиво советовать в статьях такие вещи, я считаю, зло. Разумеется, знать эти тонкости очень полезно. Но если человек уже знает, статья ему ни к чему, а если не знает, то может всё не так понять. Каждый пункт тут — сомнителен и палка о двух концах. Такими вещами надо заниматься, если точно понимаешь что происходит. И тогда уж с Вашей стороны надо куда как подробнее расписать всё. Потому как зачастую странные вещи можно обнаружить. Вот помимо сказанного выше взять даже первый безапеляционный пункт:
//медленно
Integer i = new Integer(100);
...
//быстро
Integer i = Integer.valueOf(100);
Что думает читатель? «Ага, valueOf быстрее, чем new». Верно думает читатель? Нет. Оно быстрее в общем случае только если в скобках определённые числа. Если мы напишем так:
//медленно
Integer i = new Integer(200);
...
//быстро
Integer i = Integer.valueOf(200);
, то это уже, очевидно, неправда — ведь всё противоположно, не так ли? Первый отрезок быстрее второго. Да, быстрее на три с половиной тика, но раз уж взялись экономить на спичках — договаривайте до конца :)
то это уже, очевидно, неправда

Зависит всетаки от значения IntegerCache.high, а не от кода
Верно, по сути так и есть. Есть какая-то граница, где в вашем коде правда превращается в неправду. И для одних чисел первый код быстрее (мы же, как я понял, только скорость сейчас оптимизируем), ибо используется кеш, а для других — второй код быстрее, ибо не производятся как минимум две проверки на границы кеша перед неизбежным последующим new(i). Но, во-первых, очевидно, что слишком сдвигать эту границу почти никогда нецелесообразно. Я с трудом могу придумать случаи, когда нужно взять и сдвинуть этот кеш куда-то далеко вправо. А во-вторых, речь всё же о каком-никаком общем случае.
Эти советы могут пригодиться людям пишущим

(new Integer(x)).toString()

вместо

String.valueOf(x)
Integer.toString(x)
речь только о 7 версии

Это не так. Метод
getAndRemoveCacheProperties
Снова извиняюсь. Да, наврал чуть-чуть. Отстал на два-три релиза с шестой веткой оракловской, очень они резко двинулись. Скачал последний апдейт Oracle JDK — действительно, valueOf( int ) использует IntegerCache.high. Совсем недавно там было совершенно точно хардкод 128. Проверил в свежей OpenJDK — всё так и есть, всё тот же старый хардкод 128.
Почему только Integer.valueOf(int)? Попробуйте так:
Integer i1 = new Integer(10);
Integer i2 = new Integer(10);
System.out.println(i1==i2);
В результате true, т.к. созданные таким образом объекты тоже кэшируются.
Вы сейчас очень не правы, хотя в контексте вашей JVM это может быть правдой. Но я бы не полагался на специфику реализации.
НЛО прилетело и опубликовало эту надпись здесь
Этот пункт только для boxing. Соответственно при создании нового объекта не должна выполняться данная часть спеки.
НЛО прилетело и опубликовало эту надпись здесь
Прошу прощения, ввёл и себя и других в заблуждение. В примере должно быть Integer i1 = 10; Integer i2 = 10; И тогда по спецификации java они должны ссылаться на один и тот же объект для всех чисел из [-128, 127]
Просмотрите пожалуйста еще раз топик. Так как раз указано, что:
Long l = 100L;//это тоже самое что Long.valueOf(100L);
О какой низкоуровневой оптимизации можно вообще говорить без привязки к JVM?
>null vs empty
>
> Всегда старайтесь в методах вашей бизнес логики возвращать пустые коллекции вместо null значений, это избавляет от лишних null-проверок и делает код чище.


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

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

В общем, я, конечно, не призываю во всех случаях использовать null, но призываю воздерживаться от вырадения «всегда старайтесь делать то-то» без указания контекста.
Во-первых, таким образом получится избавиться только лишь от небольшого количества проверок на null, в частности, в некоторых приватных методах. В пабликах и т.д. в любом случае придется проверки на null делать, так как заранее можете и не знать, кто и как будет их вызывать в будущем.

Ну это уже от контракта ваших методов зависит. Некоторые считают нормальным подразумевать, что методы не допускают ввода null в качестве параметра и выбрасывать исключение времени выполнения.
Это понятно, что от контракта зависит. Но автор указал, что одна из его целей использования пустых коллекций – избавиться от по его мнению лишних проверок на null. Из чего делаем закономерный вывод, что в его случае подобного контракта не предусметрено (иначе вообще не было бы смысла упоминать null).
>а также увеличит трафик (больший объем сериализованных данных будет передаваться)

Поправьте меня, если я неправ, но при использовании JSON (который по-моему уже стандарт де-факто для сетевой передачи данных) будет следующая картина:

{
...
some_list:[],
...
}

против
{
...
some_list:null,
...
}

Разница в 2 байта, причём в пользу пустого списка ;)
зависит от маршаллера — поле может в обоих случаях вообще отсутствовать
Хотелось бы увидеть доказательства, что после JIT действительно будет хоть какойто выигрыш? Скажем, я лично не уверен, что new String("...") и «ююю» вообще компилируется в разные вещи. Я конечно тут гляну на досуге, но без веских доказательств мне сложно поверить, что будет разница после JIT во всех этих преобразованиях в Integer, String итп.
Скажем «Преобразование чисел» предложение оно вообще непонятно как может быть скомпилировано иначе чем в valueOf. Если там будет отличие в байт коде мне это будет удивительно. Сам активно предпочитаю + "", изза лаконичности и лучшей читабельности.
Collections.emptyMap и тому подобные методы (Arrays.asList скажем) это очень опасная штука ибо неявно immutable. Причем код понять это иначе чем получив эксепшен не может. Что впрочем вообще общая проблема паттерна null object, относительно безопасно отдавать его только как Iterable<...>.
Все отсылки к идеологии ООП, тоже слабый аргумент. Если конечно это не академическое программирование. Я не вижу практической пользы набор констант оформлять как класс и уж бы точно не стал бы советовать такую практику новичку. И воспринимать ООП излишне серьезно я бы тоже крайне не советовал, это довольно вредное заблуждение. Программирование должно быть прагматично, и исходить где лучше применить наследование, делегирование или композицию лучше не из формальных правил ООП, а из соображений удобства. Скажем, в некоторых случаях наследования стоит избегать где только это возможно изза непредсказуемых поведений потомком при изменении родителя.

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

Боятся instanceof тоже не стоит, где-то была работа где изучались разные виды multiple dispatch. Все эти двойные коллбеки аля визитор, instanceof и прочие полиморфизмы. instanceof по перформансу был одним из самых быстрых. Опять же проблема тут не в перформансе, а скорее в поддерживаемости кода. Тормознутость рефлекшена, кстати опять же сильно преувеличена, при умелом подходе оно бегает довольно шустро.
private static String x() {
	return "...";
}

private static String y() {
	return new String("...");
}

private static java.lang.String x();
  Code:
   0:	ldc	#2; //String ...
   2:	areturn

private static java.lang.String y();
  Code:
   0:	new	#3; //class java/lang/String
   3:	dup
   4:	ldc	#2; //String ...
   6:	invokespecial	#4; //Method java/lang/String."<init>":(Ljava/lang/String;)V
   9:	areturn

}

Скажем, я лично не уверен, что new String("...") и «ююю» вообще компилируется в разные вещи.

Любая конструкция «foobar» является частью константного пула класса. Вызов «new String(»foobar")" порождает новый объект, который использует соответствующую константную строку. Байткод в комментарии выше это наглядно илюстрирует.

Скажем «Преобразование чисел» предложение оно вообще непонятно как может быть скомпилировано иначе чем в valueOf.

Boxing будет в любом случае проходить через valueOf(), однако вызов «new Integer(n)» javac-компилятор соптимизировать не сможет, т.к. иначе не будет выполняться (new Integer(0) != new Integer(0)), что будет противоречить спецификации языка.

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

Не понимаю, почему вы считаете это «странным» советом. Применение синхронизации не к месту может действительно привести к неожиданной синхронизации потоков, когда этого можно было бы избежать. В случае со StringBuilder/StringBuffer это кажется маловероятным, но я не вижу причин, по которым можно себе прощать использование буффера вместо билдера из-за того, что «немного кода синхронизации погоды не сделают».

Боятся instanceof тоже не стоит, где-то была работа где изучались разные виды multiple dispatch...

Бояться — нет. Избегать, где это возможно — да. Оптимизации instanceof в HotSpot хоть и хороши, тем не менее не являются серебряной пулей — против них можно легко можно создать контр-пример. В реальных приложения такие контр-примеры случаются не часто. Но в хорошо-спроектированных реальных приложениях «instanceof» в принципе почти не встречается.

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

Ключевая фраза «при умелом подходе». Опять же, неиспользование рефлексии, где использования можно избежать, всегда быстрее, чем ее использование.

Тем не менее, ваш поинт о том, что использование различных механизмов (рефлексия, instanceof) не станет резким образом узким местом в производительности, более чем справедлив.
В целом согласен. Только в случае с баффером-билдером так и непонятно, в чем преимущество последнего, кроме синхронизации, которая вообще незаметна при однопоточном использовании и дает надежность против легкого удара по перформансу в многопоточном использовании.
НЛО прилетело и опубликовало эту надпись здесь
Синхронизация дает значительный (относительно) удар даже при однопоточном выполнении.
Это не совсем так, выше я написал отчего. Не даёт синхронизация никакого удара при однопоточном выполнении. По крайней мере если конкретно говорить о HotSpot. А мы, судя по статье и комментариям автора говорим именно о ней. Если не говорить конкретно о какой-то машине, то практически ни один из данных советов не имеет какого-то смысла.
Поподробнее пожалуйста. Какой удар вы имеете ввиду от биасед синхронизации?
Кстати, стоит провести поиск по сорцам JDK как находятся тысяча использовании new Integer(0) и тому подобных. Например в классе Locale, MessageFormat. В сорцах спринга тоже встречается. Это банально чище выглядит в коде, нагляднее. Так что практика довольно распространенная, насколько она вредна сложно судить без тестов.
НЛО прилетело и опубликовало эту надпись здесь
> Ели вы будете затем итерироваться по отображению/множеству, используйте LinkedHashMap/LinkedHashSet вместо HashSet/HashMap, поскольку для операция next() итератора существенно быстрее.

Замеры в студию!

> Кроме того, порядок итерации получается такой же, в котором объекты были добавлены.
А вот это уже совсем другое требование.

Если нет специфичных требований к месту использования — всегда используйте наименее ограниченный вариант.

Если нет требований к порядку итератора — надо юзать HashMap вместо LinkedHashMap.
Еще веселее — если нет требований к порядку итератора не нужно использовать SortedMap/SortedSet там где можно обойтись HashMap и т.д.

Ну а самое главное — LinkedHashMap не имеет аналога в java.util.concurrency, что может полностью сломать попытки улучшить скалабильность. HashMap (с точностью до приседаний вокруг null key/value) легко заменяется на ConcurrentHashMap.

НЛО прилетело и опубликовало эту надпись здесь
Зато добавление/удаление дороже. Вот и возникает вопрос — а оно надо?
Не нужно делать ничего пока оно не надо! ;)
> Более того, кэп утверждает, что в данном случае замеры не особо-то и нужны, достаточно знать, как реализованы эти структуры данных.

Но в данном случае майор знает, что капитан не прав. Делать умозрительные (без измерений) заключения о перформансе — это в 98% случаев попасть мимо.

Например можно сделать тест где итерация по LinkedHashMap будет медленее чем по HashMap так как из-за ограничений на порядок элементов получим большее количиство cache miss. Но этот тест также будет сферическим конем.
LinkedHashMap можно заменить ConcurrentSkipListMap, в котором ключами являются врапперы ключей и сравнением по времени создания :)
Ааааааааааааааааааааааа.
Ужос ужос ужос.
Не используйте SortedMap, там где Sorted не нужен. ;)
Наезд на instanceOf неверен принципиально — перелопачивать архитектуру в обмен на ~0 прирост производительности.
instanceOf — не ООП-подход.

Остальные аргументы слабее в данном случае.
null vs empty — неверно с точки зрения производительности. Проверка на нулл быстрее проверки на ноль. Так что правильным решением будет не проверять на пустоту, и не возвращать пустых коллекций.

collection == null просто проверит адреса. Операцию быстрее этой придумать сложно.
collection.isEmpty() сперва проверит на null (чтобы вернуть NPE если таки да), а потом вызовет метод isEmpty

Кстати, проверки вроде collecrion.size() == 0 это наихудший вариант, ибо далеко не для всех коллекций size выполняется за фиксированное время.

Преобразование чисел — тут фокус в том, что это по сути вызовет все тот же valueOf (вот не знаю точно, как эмулируется toString для примитивов), а потом конкатенацию. Но компилятор вполне может и оптимизировать, увидев что там пустая строка, и тогда получится шило на мыло.
НЛО прилетело и опубликовало эту надпись здесь
Может я что-то упустил, но в исходниках класса java.lang.Long метод valueOf(String) определяется как

    /**
     * Returns a <code>Long</code> object holding the value
     * of the specified <code>String</code>. The argument is
     * interpreted as representing a signed decimal <code>long</code>,
     * exactly as if the argument were given to the {@link
     * #parseLong(java.lang.String)} method. The result is a
     * <code>Long</code> object that represents the integer value
     * specified by the string.
     * <p>
     * In other words, this method returns a <code>Long</code> object
     * equal to the value of:
     *
     * <blockquote><pre>
     * new Long(Long.parseLong(s))
     * </pre></blockquote>
     *
     * @param      s   the string to be parsed.
     * @return     a <code>Long</code> object holding the value
     *             represented by the string argument.
     * @exception  NumberFormatException  If the string cannot be parsed
     *              as a <code>long</code>.
     */
    public static Long valueOf(String s) throws NumberFormatException
    {
	return new Long(parseLong(s, 10));
    }


Другими словами Long.valueOf сводиться к new Long. Каким образом он может быть быстрее?
Прошу прощения, проглядел, что и Long и Integer также имеют перегруженный метод для примитивного аргумента

    /**
     * Returns a <tt>Long</tt> instance representing the specified
     * <tt>long</tt> value.
     * If a new <tt>Long</tt> instance is not required, this method
     * should generally be used in preference to the constructor
     * {@link #Long(long)}, as this method is likely to yield
     * significantly better space and time performance by caching
     * frequently requested values.
     *
     * @param  l a long value.
     * @return a <tt>Long</tt> instance representing <tt>l</tt>.
     * @since  1.5
     */
    public static Long valueOf(long l) {
	final int offset = 128;
	if (l >= -128 && l <= 127) { // will cache
	    return LongCache.cache[(int)l + offset];
	}
        return new Long(l);
    }


который как раз использует кеширование.
Очередная ситуация, где коментарии интереснее самого топика.
НЛО прилетело и опубликовало эту надпись здесь
Насчёт медленного new для простых объектов это уже миф. Создание простого объекта — инкремент указателя, удаление из young — тоже почти бесплатное.
Не поленился, написал тест

public class NewMain {

static final long iterations = 10000L * 1000000L;

public static void main(String[] args) {
Runnable doNew = new Runnable() {
public void run() {
int s = 0;
for (long i = 0; i < iterations; ++i) {
Integer v = new Integer(100);
s += v;
}
}
};
Runnable doValue = new Runnable() {
public void run() {
int s = 0;
for (long i = 0; i < iterations; ++i) {
Integer v = Integer.valueOf(100);
s += v;
}
}
};
measure("valueOf", doValue);
measure("new", doNew);
measure("valueOf", doValue);
measure("new", doNew);
}

private static void measure(String msg, Runnable r) {
long start = System.currentTimeMillis();
r.run();
long ela = System.currentTimeMillis() - start;
System.out.println(msg + " elapsed " + ela + ", rps " + (iterations / ela) * 1000);
}

}


Запускаем на разных jvm

alexm-nb:~/tmp/pt$ java -version
java version «1.6.0_20»
OpenJDK Runtime Environment (IcedTea6 1.9.9) (6b20-1.9.9-0ubuntu1~10.10.2)
OpenJDK 64-Bit Server VM (build 19.0-b09, mixed mode)
alexm-nb:~/tmp/pt$ java NewMain
valueOf elapsed 11752, rps 850918000
new elapsed 7763, rps 1288161000
valueOf elapsed 11542, rps 866400000
new elapsed 7757, rps 1289158000

alexm@adams:~/pt$ java -version
java version «1.6.0_29»
Java(TM) SE Runtime Environment (build 1.6.0_29-b11)
Java HotSpot(TM) 64-Bit Server VM (build 20.4-b02, mixed mode)
alexm@adams:~/pt$ java NewMain
valueOf elapsed 12594, rps 794028000
new elapsed 8396, rps 1191043000
valueOf elapsed 12616, rps 792644000
new elapsed 8389, rps 1192037000

Привет, new быстрее valueOf. Если сделать valueOf(200), чтобы оно не лезло в кеш, будет одинаково быстро.
Ну вы даете, запустили 4 конкурентных потока и ожидаете получить правдивые результаты? Сделайте хотя бы так:
measure("valueOf", doValue);
Thread.yield();
measure("new", doNew);
Thread.yield();
measure("valueOf", doValue);
Thread.yield();
measure("new", doNew);


Вот результат:
valueOf elapsed 3006, rps 332667000
new elapsed 10259, rps 97475000
valueOf elapsed 2912, rps 343406000
new elapsed 10268, rps 97389000

Как раз почти 3.5 раза
Где вы увидели конкурентные потоки?
Сглупил, извиняюсь. Ваш код — как раз пример того как делать не надо. Увидел Runnable и подсознательно приписал new Thread®.start();
Проблема с простыми объектами, если они не просто создались, а пожили некоторое небольшое время и после этого освободились. Тут они вполне могут случайно пережить 2-3 минор коллекции и угодить в олд ген. Если приложение серверное (как у меня) и живет длительное время и не может позволить себе задержки от мажор коллекций, это большая проблема со временем. Хотя Оракл активно и пропагандирует, что создание pojo операция копеечная, это все еще бывает заметной проблемой.
Тест крайне синтетический. Да, в данном виде придраться не к чему, написано грамотно. Но по сути никакой реальной картины не даёт. Не поленился, запустил на Java HotSpot(TM) Server VM (build 20.4-b02, mixed mode):
valueOf elapsed 19938, rps 501554000
new elapsed 18235, rps 548395000
valueOf elapsed 19242, rps 519696000
new elapsed 20556, rps 486475000
Это показывает только, что есть вероятная (!) разница порядка секунды на 10 миллиардах (!) итераций.
>>Старайтесь использовать @Override аннотацию для методов, которые переопределяют методы супер классов.

В седьмой джаве, можно (и нужно!) и имплементируемые методы интерфейсов так аннотировать.
Это начиная с 6й явы так.
Пардон, был взволнован.
> //медленно
> int a = 12;
> String s = a + "";
> //быстро
> String s = String.valueOf(12);

Проще будет, раз уж итак константа:
String s = «12»;

Видимо автор имел в виду:
String s = String.valueOf(a);
Огромное спасибо за статью, прочитав ее увидел ошибки, которые встречаются у меня в коде, и благодаря Вашей статье, уже постараюсь больше не допускать, еще раз спасибо за труд!
Ну и java.lang.String.intern() до кучи
интерн поможет спасит от инлайна при компиляции,
но может печально сказаться на производительности:

подробное описание

так что с этим нужно тоже быть достаточно аккуратно.

p.s. на седьмой jdk не проверял, но последняя 6я от оракла печально себя ведет.
Очень познавательно, спасибо.
Collections.emptyList(); — Хозяйке на заметку.
Есть еще полезный прием:
''true''.equals(str)
Позволяет избежать NPE при сравнении строк с константами.
//плохо
interface A {
    public static final String A = "a";
}

Это не только плохо, но и криво. По умолчанию, все поля интерфейса публичные, финальные и статические.

//аналогично плохо
interface A {
    String A = "a";
}
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Изменить настройки темы

Истории