All streams
Search
Write a publication
Pull to refresh
170
0
Андрей @apangin

Пользователь

Send message
Какая будет разница по потреблению памяти между конструкциями `new int[1024]` и `new Integer[1024]`?
Сильно зависит от платформы, версии Java и ключей JVM.
Если int[1024] всегда занимает около 4K, то объем Integer[1024] может варьироваться от тех же 4K
(при -XX:+UseCompressedOops -XX:+AggressiveOpts) до 28K (при -XX:-UseCompressedOops).
Превратить ненулевое число в 1 можно очень просто: (n | -n) >>> 31
Не хватает ссылки на рабочий пример. И «30.3400000%» — это нечеловеческий ужас.
Он же основатель FFMPEG и автор рекорда 2009 года по числу вычисленных знаков числа Пи.
Вот это Человечище!
На производительности это скажется значительно. Могу сказать наверняка, поскольку достаточно поэкспериментировал с интерпретаторами ARM CPU, Java, ECMAScript.
1. Вызов функции сопровождается как минимум сохранением адреса возврата на стеке, а зачастую еще и регистров, что совсем не нужно в случае со switch.
2. Компилятор может соптимизиоровать безусловный переход на начало switch + вычисляемый переход на нужный case в один переход, добавляемый после каждого case. С функциями из call table такая оптимизация невозможна.
3. Со switch «горячие» указатели (Instruction pointer, Stack pointer и т.п.) будут локальными переменными и закэшируются в регистрах. С функциями и таблицей переходов это будут либо глобальные переменные, либо локальные переменные, передаваемые по ссылке. И то, и другое менее эффективно.

В GCC есть computed goto, что хорошо подходит для написания эффективного интерпретатора, однако это не входит в стандарт С, и другими компиляторами не поддерживается.

Мне понравилось решение этой проблемы с помощью макросов CASE/ENDCASE/DISPATCH, которые могут быть раскрыты в switch/case, computed goto или call table в зависимости от условий.

CASE(opcode1)
    handle_opcode_1;
    DISPATCH(1)
ENDCASE

CASE(opcode2)
    handle_opcode_2;
    DISPATCH(2)
ENDCASE
CMSPermGenSweepingEnabled устаревший параметр. Достаточно -XX:+CMSClassUnloadingEnabled

Неправда, что в JRockit нет этой проблемы. Просто она проявляется позже. Если Hotspot ограничивает размер class metadata, то JRockit позволяет забивать невыгружаемыми классами всю доступную виртуальную память. Однако если утечка памяти есть, она в любом случае проявится рано или поздно, причем в случае исчерпания виртуальной памяти последствия могут крайне неприятными: тормоза, уход в своп, блокировка работы других процессов на машине.

В принципе, уже можно потихоньку начинать радоваться избавлению Hotspot'а от PermGen. Уже сейчас вынесены из PermGen в C-heap symbols, interned strings и static fields. Впрочем, спорное решение, на мой взгляд.
В байткоде нигде не остается информации о том, была ли локальная переменная final или не final, поэтому JIT о final ничего не знает, и генерируемый код ничем не отличается. Тем не менее, JIT и сам умеет определять, что значение переменной не меняется, и применять соответствующие оптимизации (common subexpression elimination, loop invariant hoisting...)

Следует отличать эту ситуацию от двух других:
— когда final приписывается к полям класса, в этом случае в байткоде имеется информация об атрибуте final поля;
— когда final локальные переменные используются в замыканиях (анонимных классах внутри метода) — в этом случае значения этих переменных передаются в конструктор анонимного класса. Впрочем, на производительность это также не влияет.
С числовыми константами все понятно. Не о них речь. Вы сравните
    final int aColumns = A.columns(); и
    int aColumns = A.columns();
Автор утверждал, что с final будет быстрее. Я же говорю, что такого быть не может, поскольку байткод получается абсолютно одинаковым!
hg clone http://hg.openjdk.java.net/jdk7/hotspot/hotspot

src/cpu/x86/vm/sharedRuntime_x86_64.cpp
OptoRuntime::generate_exception_blob — генерация прослойки на ASM, вызываемой из скомпилированного кода;

src/share/vm/opto/runtime.cpp
OptoRuntime::handle_exception_C_helper — обработка Exception в контексте VM;

src/share/vm/runtime/sharedRuntime.cpp
SharedRuntime::compute_compiled_exc_handler — поиск Exception Handler.
Не знаю, я по исходникам изучал :)
Деоптимизация происходит не всегда (в частности, не требуется, когда exception handler есть в том же методе), а, вот, как раз для корректного разворачивания она нужна (т.к. один фрейм скомпилированного кода может соответствовать нескольким фреймам интерпретатора), для отпускания мониторов и т.п.
А теперь скомпилируйте .java исходник, в одном случае с final перед локальной переменной, в другом случае без final, и сравните побайтно получившиеся .class файлы.
Спасибо, статья полезная, но все же с исходным утверждением «GC не заботится о сохранности ссылок для JNI-вызовов» я не соглашусь. JVM заботится о том, чтобы ссылки на Java-объекты из native-методов были валидны даже после GC, а именно:
— что локальные ссылки валидны до тех пор, пока не завершится native-метод, в контексте которого эти ссылки созданы;
— что глобальные ссылки валидны на протяжении всей работы JVM, пока не вызывана функция DeleteGlobalRef.
С1 и С2 — это условные названия JIT-компиляторов в Client VM и Server VM соответственно.
Пользователям про них и не надо знать, тем более, что уже через год, думаю, Client VM почти нигде не останется.
Я считаю, что не стоит делать из красивого и логичного кода некрасивый и нелогичный, называя это оптимизацией, только потому, что на устаревающей VM он работает чуточку быстрее.
Очередная порция вредных советов, основанная на неправильных измерениях.
Пожалуй, единственная содержательная оптимизация из вышеперечисленных — Шаг 2.

Во-первых, забудьте про Client VM — она уже в прошлом. На многопроцессорном компьютере с >=2GB RAM (т.е. практически на любом современном ноутбуке) предпочтительней запускать Server VM. К счастью, в новых релизах JDK больше не будет разделения на client/server, останется одна Tiered VM.
Во-вторых, вспомните про правила бенчмаркинга, если вдруг вы измеряете не так.

Теперь по оптимизациям.
    Шаг 1: final int aColumns = A.columns() писать неплохо, но эта оптимизация в любом случае выполняется C2-компилятором автоматически.
    Шаг 3: В случае Server VM на 64-битной системе применение этой «оптимизации» ухудшило результат на 3%. C2-компилятор лучше справляется с простой вложенностью циклов.
    Шаг 4: Вообще зло. Мало того, что использование исключений при нормальном поведении — крайне плохой стиль, так еще бросание Exception — самая дорогая операция в Java VM, исчисляемая десятками, а то и сотнями тысяч простых арифметических операций. При бросании исключения происходит деоптимизация фреймов (т.е. возврат из скомпилированного кода в интерпретируемый), проход по стеку с заполнением stack trace, поиск exception handler'а. Конечно, 1000 операций это слишком мало, чтоб заметить разницу, попробуйте вставить ловлю исключения во внутренний цикл. Кроме того, C2-компилятор умет сам избавляться от лишних проверок на выход за пределы массива (array bounds check elimination).

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

enum {
  KB = 1024,
  MB = 1024*KB,
  GB = 1024*MB
};
...
char* arr = new char[16*MB];
Код в 3м примере вполне имел бы смысл, если бы tbl был volatile.
Вам надо бы познакомиться с битовыми масками.
Тогда не было бы трехмерных массивов, функции lessRowSuggest и т.п. стали бы гораздо короче,
а arrayDiff записалась бы в ОДНУ операцию!
Можете сравнить с моим решением на Java.
Равенство хэшей не гарантирует равенства строк. Поэтому либо нужен дополнительный проход для проверки строк (и сложность становится порядка N^2), либо вы утверждаете, что решаете задачу за O(N*logN) с некоторой вероятностью (может, даже большой, но отличной от 1).

Information

Rating
Does not participate
Location
Санкт-Петербург, Санкт-Петербург и область, Россия
Works in
Registered
Activity