Извините. Возможно, вы и анализировали output компилятора и ассемблерный код. Тогда я неправ.
Я ж судил только по очевидным признакам:
— Warm-up phase нет.
— Изначально выводы были основаны на измерениях результатов клиентского компилятора.
— Предотвращение OSR не выполнено.
— Печать и инициализация классов внутри timing phase.
А ведь эти самые главные!
Чертовски правильный вопрос!
Например, в выложенных здесь javatest.zip и javatest2.zip нарушены практически все пункты правил написания микробенчмарков для JVM.
Мне смайлик следовало поставить во фразе про интерпретатор? :)
Тега <irony> явно не хватает.
Давайте перенесем дальнейшую дискуссию в личку, чтоб более не засорять топик.
Меньше — лучше, правильно ведь? charAt выигрывает.
C:\Windows\Temp>java -version
java version "1.6.0_20"
Java(TM) SE Runtime Environment (build 1.6.0_20-b02)
Java HotSpot(TM) 64-Bit Server VM (build 16.3-b01, mixed mode)
Вы бы еще в чистом интерпретаторе запустили и в нем сравнивали.
Не будет это лучше. От лишнего копирования-то все равно не избавитесь. А вызов метода — это не так страшно. Тем более, что никакого вызова в данном случае в скомпилированном коде нет — метод инлайнится.
Неправильно подозреваете. Одинаково работают. Можете проверить.
Совсем недавно я приводил пример, когда метод в три раза длиннее по байткоду компилируется один-в-один, как и короткий метод.
Потому что проверял и знаю.
А еще это вполне логично даже из общих соображений: toCharArray() создает новый массив, увеличивая нагрузку на память и копируя символы лишний раз, в то время как charAt() достает символ непосредственно из строки.
Поскольку тезис N1 был воспринят неоднозначно, разрушители легенд решили проверить миф о том, есть ли все-таки разница между работой synchronizedMethodGet и synchronizedBlockGet :)
Для проверки я запустил программку из Листинга 1 с такими параметрами: java -Xcomp -XX:CompileOnly=SynchronizationExample.synchronizedMethodGet -XX:CompileOnly=SynchronizationExample.synchronizedBlockGet -XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly SynchronizationExample
Анализ показал, что машинный код, сгенерированный C1-компилятором для методов synchronizedMethodGet и synchronizedBlockGet оказался одинаковым с точностью до байта! Таким образом, миф разрушен (или подтвержден, кто как предполагал :) Пруф.
Зависит от архитектуры процессора и сценария использования. На x86 volatile load такой же быстрый, как и обычный load (за исключением volatile long на 32-битной системе, для которого используются FPU инструкции). На однопроцессорной машине тоже все в порядке. Плохо на многопроцессорной машине с volatile store, который сопровождается инструкцией lock add [esp], 0, что выливается во временную блокировку шины данных и инвалидацию кэша процессора.
Синхронизация с помощью мониторов (synchronized блоки) тоже бывает разная. Она почти ничего не стоит до тех пор, пока монитор используется лишь одним тредом (за счет BiasedLocking). Как только второй тред попробует синхронизоваться на том же мониторе, BiasedLocking перестает работать, и задействуются опять же атомарные инструкции с lock префиксом.
1. Несмотря на то, что synchronized block выглядит длиннее, в действительности он работает точно так же, как и synchronized method. Во-первых, строки 11-15 не исполняются, они нужны лишь для обработки IllegalMonitorStateException. Во-вторых, monitorenter и monitorexit и сопутствующие операции выполняются в обоих случаях, просто в случае synchronized метода это происходит неявно на уровне виртуальной машины при вызове метода.
3. Начиная с Java 5 ключевое слово volatile имеет еще одно очень важное значение. Доступ к volatile полю окружен data memory barrier'ом, что гарантирует упорядоченность операций чтения-записи памяти относительно доступа к этому полю. Иначе компилятор или сам процессор вправе переупорядочивать инструкции (out-of-order execution).
4. В качестве примера неатомарного доступа к не-volatile полю можно рассмотреть чтение/запись поля типа long на 32х битной архитектуре. Оно реализуется в виде двух операций чтения/записи двух «половинок» поля. Таким образом, если один thread пишет longField = 0x1111111122222222L; longField = 0x3333333344444444L; то другой thread в какой-то момент может теоретически увидеть значение 0x1111111144444444L. Запись volatile поля происходит чуть хитрее, и такой проблемы быть не может.
5. Атомарные примитивы — хороший способ сделать поле thread-safe без дорогой блокировки, т.к. большинство архитектур аппаратно поддерживают атомарные операции типа compare-and-swap.
Задачка по теме а-ля «check your knowledge» ;)
Есть компьютер с Windows 7 x64, 4G RAM, 128G SSD. Page-файл по умолчанию имеет размер 4G.
Докупается еще 4G RAM в целях увеличения производительности. Теперь 8G RAM и page-файл тоже 8G.
Вопрос, что сделать с page-файлом, и, главное, почему?
— оставить как есть
— отключить
— уменьшить
— другой вариант
О! Да это дает потрясающие средства генерации машинного кода с использованием всех прелестей объектно-ориентированного подхода. Пожалуй, стоит отдельную статейку на эту тему написать. В рамки одного комментария я точно не уложусь :)
Вкратце: динамическая кодогенерация, использование особенностей аппаратной платформы, независимость от синтаксиса встроенного ассемблера, будь то GCC, MSVC или что еще, тесная интеграция с кодом на C/C++.
Шутки-шутками, но лучший макроассемблер на практике получается из C++.
Уже в нескольких проектах использовал ассемблер, написанный на C++ в виде кодогенерирующих функций типа
Вместо Runnable можно воспользоваться паттерном Listener или просто сделать HttpClient inner-классом SearchableActivity, тогда можно будет вызывать методы SearchableActivity из HttpClient напрямую.
Я ж судил только по очевидным признакам:
— Warm-up phase нет.
— Изначально выводы были основаны на измерениях результатов клиентского компилятора.
— Предотвращение OSR не выполнено.
— Печать и инициализация классов внутри timing phase.
А ведь эти самые главные!
Например, в выложенных здесь javatest.zip и javatest2.zip нарушены практически все пункты правил написания микробенчмарков для JVM.
Тега <irony> явно не хватает.
Давайте перенесем дальнейшую дискуссию в личку, чтоб более не засорять топик.
C:\Windows\Temp>java Test2 (charAt)
240098112
210
C:\Windows\Temp>java Test3 (getChars)
240098112
247
Меньше — лучше, правильно ведь? charAt выигрывает.
C:\Windows\Temp>java -version
java version "1.6.0_20"
Java(TM) SE Runtime Environment (build 1.6.0_20-b02)
Java HotSpot(TM) 64-Bit Server VM (build 16.3-b01, mixed mode)
Вы бы еще в чистом интерпретаторе запустили и в нем сравнивали.
Совсем недавно я приводил пример, когда метод в три раза длиннее по байткоду компилируется один-в-один, как и короткий метод.
А еще это вполне логично даже из общих соображений: toCharArray() создает новый массив, увеличивая нагрузку на память и копируя символы лишний раз, в то время как charAt() достает символ непосредственно из строки.
Для проверки я запустил программку из Листинга 1 с такими параметрами:
java -Xcomp -XX:CompileOnly=SynchronizationExample.synchronizedMethodGet -XX:CompileOnly=SynchronizationExample.synchronizedBlockGet -XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly SynchronizationExample
Анализ показал, что машинный код, сгенерированный C1-компилятором для методов synchronizedMethodGet и synchronizedBlockGet оказался одинаковым с точностью до байта! Таким образом, миф разрушен (или подтвержден, кто как предполагал :) Пруф.
Синхронизация с помощью мониторов (synchronized блоки) тоже бывает разная. Она почти ничего не стоит до тех пор, пока монитор используется лишь одним тредом (за счет BiasedLocking). Как только второй тред попробует синхронизоваться на том же мониторе, BiasedLocking перестает работать, и задействуются опять же атомарные инструкции с lock префиксом.
1. Несмотря на то, что synchronized block выглядит длиннее, в действительности он работает точно так же, как и synchronized method. Во-первых, строки 11-15 не исполняются, они нужны лишь для обработки IllegalMonitorStateException. Во-вторых, monitorenter и monitorexit и сопутствующие операции выполняются в обоих случаях, просто в случае synchronized метода это происходит неявно на уровне виртуальной машины при вызове метода.
3. Начиная с Java 5 ключевое слово volatile имеет еще одно очень важное значение. Доступ к volatile полю окружен data memory barrier'ом, что гарантирует упорядоченность операций чтения-записи памяти относительно доступа к этому полю. Иначе компилятор или сам процессор вправе переупорядочивать инструкции (out-of-order execution).
4. В качестве примера неатомарного доступа к не-volatile полю можно рассмотреть чтение/запись поля типа long на 32х битной архитектуре. Оно реализуется в виде двух операций чтения/записи двух «половинок» поля. Таким образом, если один thread пишет longField = 0x1111111122222222L; longField = 0x3333333344444444L; то другой thread в какой-то момент может теоретически увидеть значение 0x1111111144444444L. Запись volatile поля происходит чуть хитрее, и такой проблемы быть не может.
5. Атомарные примитивы — хороший способ сделать поле thread-safe без дорогой блокировки, т.к. большинство архитектур аппаратно поддерживают атомарные операции типа compare-and-swap.
Есть компьютер с Windows 7 x64, 4G RAM, 128G SSD. Page-файл по умолчанию имеет размер 4G.
Докупается еще 4G RAM в целях увеличения производительности. Теперь 8G RAM и page-файл тоже 8G.
Вопрос, что сделать с page-файлом, и, главное, почему?
— оставить как есть
— отключить
— уменьшить
— другой вариант
Чтоб for (int i = 0; i < size; i++) { } одним жестом набиралось?
Вкратце: динамическая кодогенерация, использование особенностей аппаратной платформы, независимость от синтаксиса встроенного ассемблера, будь то GCC, MSVC или что еще, тесная интеграция с кодом на C/C++.
Уже в нескольких проектах использовал ассемблер, написанный на C++ в виде кодогенерирующих функций типа
void mov(Register dst32, int imm32) { emit8(0xb8 + dst32); emit32(imm32); }
void mov(Register dst32, Address src32) { emit8(0x8b); emit_addr(src32, dst32); }
void push(Register dst32) { emit8(0x50 + dst32); }
void call(Address dst32) { emit8(0xff); emit_addr(dst32, 2); }
void call(Label target) { emit8(0xe8); emit_label(rel32); }
void build_frame(int size) { push(epb); sub(esp, size); }
и т.д.
В результате код на таком «ассемблере» выглядит примерно так:
build_frame(32);
Register osr_buf = eax;
for (int i = 0; i < num_locks; i++) {
int slot_offset = monitor_offset - i * 8;
cmp(Address(osr_buf, slot_offset + 4), 0);
jcc(eq, done);
mov(ebx, Address(osr_buf, slot_offset));
mov(address_of_lock(i), ebx);
}
bind(done);
Коли уж так, то напрашиваются haed и cnotent :)