Почему добавление блока try делает программу быстрее?
Я использую следующий код, чтобы проверить, насколько медленным является блок try. К моему удивлению, блок try делает это быстрее. Почему?
public class Test {
int value;
public int getValue() {
return value;
}
public void reset() {
value = 0;
}
// Calculates without exception
public void method1(int i) {
value = ((value + i) / i) << 1;
// Will never be true
if ((i & 0xFFFFFFF) == 1000000000) {
System.out.println("You'll never see this!");
}
}
public static void main(String[] args) {
int i;
long l;
Test t = new Test();
l = System.currentTimeMillis();
t.reset();
for (i = 1; i < 100000000; i++) {
t.method1(i);
}
l = System.currentTimeMillis() - l;
System.out.println("method1 took " + l + " ms, result was "
+ t.getValue());
// using a try block
l = System.currentTimeMillis();
t.reset();
for (i = 1; i < 100000000; i++) {
try {
t.method1(i);
} catch (Exception e) {
}
}
l = System.currentTimeMillis() - l;
System.out.println("method1 with try block took " + l + " ms, result was "
+ t.getValue());
}
}
В моей машине работают 64-разрядные Windows 7 и 64-разрядные JDK7. Я получил следующий результат:
method1 took 914 ms, result was 2
method1 with try block took 789 ms, result was 2
И я запускаю код много раз, и каждый раз, когда у меня получается почти тот же результат.
Update:
Вот результат десятикратного запуска теста на MacBook Pro, Java 6. Try-catch делает метод быстрее, так же как и в Windows.
method1 took 895 ms, result was 2
method1 with try block took 783 ms, result was 2
--------------------------------------------------
method1 took 943 ms, result was 2
method1 with try block took 803 ms, result was 2
--------------------------------------------------
method1 took 867 ms, result was 2
method1 with try block took 745 ms, result was 2
--------------------------------------------------
method1 took 856 ms, result was 2
method1 with try block took 744 ms, result was 2
--------------------------------------------------
method1 took 862 ms, result was 2
method1 with try block took 744 ms, result was 2
--------------------------------------------------
method1 took 859 ms, result was 2
method1 with try block took 765 ms, result was 2
--------------------------------------------------
method1 took 937 ms, result was 2
method1 with try block took 767 ms, result was 2
--------------------------------------------------
method1 took 861 ms, result was 2
method1 with try block took 744 ms, result was 2
--------------------------------------------------
method1 took 858 ms, result was 2
method1 with try block took 744 ms, result was 2
--------------------------------------------------
method1 took 858 ms, result was 2
method1 with try block took 749 ms, result was 2
Ответы
Ответ 1
Если у вас несколько длинных циклов работы в одном и том же методе, можно запустить оптимизацию всего метода с непредсказуемыми результатами во втором цикле. Один из способов избежать этого:
- чтобы дать каждому циклу свой собственный метод
- запускать тесты несколько раз, чтобы проверить, что результат повторный.
- запустите тест на 2 - 10 секунд.
Вы увидите некоторые вариации, и иногда результаты неубедительны. то есть изменение выше разницы.
public class Test {
int value;
public int getValue() {
return value;
}
public void reset() {
value = 0;
}
// Calculates without exception
public void method1(int i) {
value = ((value + i) / i) << 1;
// Will never be true
if ((i & 0xFFFFFFF) == 1000000000) {
System.out.println("You'll never see this!");
}
}
public static void main(String[] args) {
Test t = new Test();
for (int i = 0; i < 5; i++) {
testWithTryCatch(t);
testWithoutTryCatch(t);
}
}
private static void testWithoutTryCatch(Test t) {
t.reset();
long l = System.currentTimeMillis();
for (int j = 0; j < 10; j++)
for (int i = 1; i <= 100000000; i++)
t.method1(i);
l = System.currentTimeMillis() - l;
System.out.println("without try/catch method1 took " + l + " ms, result was " + t.getValue());
}
private static void testWithTryCatch(Test t) {
t.reset();
long l = System.currentTimeMillis();
for (int j = 0; j < 10; j++)
for (int i = 1; i <= 100000000; i++)
try {
t.method1(i);
} catch (Exception ignored) {
}
l = System.currentTimeMillis() - l;
System.out.println("with try/catch method1 took " + l + " ms, result was " + t.getValue());
}
}
печатает
with try/catch method1 took 9723 ms, result was 2
without try/catch method1 took 9456 ms, result was 2
with try/catch method1 took 9672 ms, result was 2
without try/catch method1 took 9476 ms, result was 2
with try/catch method1 took 8375 ms, result was 2
without try/catch method1 took 8233 ms, result was 2
with try/catch method1 took 8337 ms, result was 2
without try/catch method1 took 8227 ms, result was 2
with try/catch method1 took 8163 ms, result was 2
without try/catch method1 took 8565 ms, result was 2
Из этих результатов может показаться, что при попытке/улове немного медленнее, но не всегда.
Запуск в Windows 7, Xeon E5450 с обновлением 7 Java.
Ответ 2
Я попробовал его с помощью Caliper Microbenchmark, и я действительно не видел разницы.
Здесь код:
public class TryCatchBenchmark extends SimpleBenchmark {
private int value;
public void setUp() {
value = 0;
}
// Calculates without exception
public void method1(int i) {
value = ((value + i) / i) << 1;
// Will never be true
if ((i & 0xFFFFFFF) == 1000000000) {
System.out.println("You'll never see this!");
}
}
public void timeWithoutTryCatch(int reps) {
for (int i = 1; i < reps; i++) {
this.method1(i);
}
}
public void timeWithTryCatch(int reps) {
for (int i = 1; i < reps; i++) {
try {
this.method1(i);
} catch (Exception ignore) {
}
}
}
public static void main(String[] args) {
new Runner().run(TryCatchBenchmark.class.getName());
}
}
И вот результат:
0% Scenario{vm=java, trial=0, benchmark=WithoutTryCatch} 8,23 ns; σ=0,03 ns @ 3 trials
50% Scenario{vm=java, trial=0, benchmark=WithTryCatch} 8,13 ns; σ=0,03 ns @ 3 trials
benchmark ns linear runtime
WithoutTryCatch 8,23 ==============================
WithTryCatch 8,13 =============================
Если я поменяю порядок функций (чтобы заставить их работать в обратном порядке), результат:
0% Scenario{vm=java, trial=0, benchmark=WithTryCatch} 8,21 ns; σ=0,05 ns @ 3 trials
50% Scenario{vm=java, trial=0, benchmark=WithoutTryCatch} 8,14 ns; σ=0,03 ns @ 3 trials
benchmark ns linear runtime
WithTryCatch 8,21 ==============================
WithoutTryCatch 8,14 =============================
Я бы сказал, что они в основном одинаковы.
Ответ 3
Я сделал несколько экспериментов.
Для начала я полностью подтверждаю нахождение OP. Даже если вы удаляете первый цикл или изменяете исключение на какой-то совершенно несущественный, try catch, если вы не добавляете ветвление путем повторного создания исключения, делает код быстрее. Код, если еще быстрее, если он действительно должен поймать исключение (если вы начинаете цикл с 0 вместо 1, например).
Мое "объяснение" заключается в том, что JIT - это дикие машины для оптимизации, и иногда они работают лучше, чем когда-либо, так, как вы обычно не понимаете, без особого изучения на уровне JIT. Есть много возможных вещей, которые могут измениться (например, использование регистров).
Это глобально то, что было найдено в очень похожем случае с С# JIT.
В любом случае Java оптимизирован для try-catch. Поскольку всегда существует вероятность исключения, вы действительно не добавляете много ветвлений, добавляя try-catch, поэтому неудивительно, что второй цикл больше, чем первый.
Ответ 4
Чтобы избежать какой-либо скрытой оптимизации или кеша, которые могли бы быть реализованы JVM и ОС, я сначала разработал две семестровые Java-программы, TryBlock
и NoTryBlock
, где их разница использует блок try или нет. Эти две программы семян будут использоваться для создания различных программ, чтобы запретить JVM или ОС для скрытой оптимизации. В каждом тесте будет создана и скомпилирована новая java-программа, и я повторил тест 10 раз.
В соответствии с моим экспериментом работа без блока try занимает в среднем 9779,3 мс, а работа с блоком try занимает 9775,9 мс: разница в среднем 3,4 мс (или 0,035%) в среднем времени работы, что можно рассматривать как шум. Это указывает на то, что использование блока void try (по void, я имею в виду, кроме исключения из null-указателя, не существует возможных исключений) или нет, похоже, не влияет на время выполнения.
Тест выполняется на той же машине linux (cpu 2392MHz) и в версии java "1.6.0_24".
Ниже приведен мой script для создания тестовых программ на основе семенных программ:
for i in `seq 1 10`; do
echo "NoTryBlock$i"
cp NoTryBlock.java NoTryBlock$i.java
find . -name "NoTryBlock$i.java" -print | xargs sed -i "s/NoTryBlock/NoTryBlock$i/g";
javac NoTryBlock$i.java;
java NoTryBlock$i
rm NoTryBlock$i.* -f;
done
for i in `seq 1 10`; do
echo "TryBlock$i"
cp TryBlock.java TryBlock$i.java
find . -name "TryBlock$i.java" -print | xargs sed -i "s/TryBlock/TryBlock$i/g";
javac TryBlock$i.java;
java TryBlock$i
rm TryBlock$i.* -f;
done
Вот программы семян, сначала это NoTryBlock.java
import java.util.*;
import java.lang.*;
public class NoTryBlock {
int value;
public int getValue() {
return value;
}
public void reset() {
value = 0;
}
// Calculates without exception
public void method1(int i) {
value = ((value + i) / i) << 1;
// Will never be true
if ((i & 0xFFFFFFF) == 1000000000) {
System.out.println("You'll never see this!");
}
}
public static void main(String[] args) {
int i, j;
long l;
NoTryBlock t = new NoTryBlock();
// using a try block
l = System.currentTimeMillis();
t.reset();
for (j = 1; j < 10; ++j) {
for (i = 1; i < 100000000; i++) {
t.method1(i);
}
}
l = System.currentTimeMillis() - l;
System.out.println(
"method1 with try block took " + l + " ms, result was "
+ t.getValue());
}
}
второй - это TryBlock.java
, который использует метод try на вызове функции метода:
import java.util.*;
import java.lang.*;
public class TryBlock {
int value;
public int getValue() {
return value;
}
public void reset() {
value = 0;
}
// Calculates without exception
public void method1(int i) {
value = ((value + i) / i) << 1;
// Will never be true
if ((i & 0xFFFFFFF) == 1000000000) {
System.out.println("You'll never see this!");
}
}
public static void main(String[] args) {
int i, j;
long l;
TryBlock t = new TryBlock();
// using a try block
l = System.currentTimeMillis();
t.reset();
for (j = 1; j < 10; ++j) {
for (i = 1; i < 100000000; i++) {
try {
t.method1(i);
} catch (Exception e) {
}
}
}
l = System.currentTimeMillis() - l;
System.out.println(
"method1 with try block took " + l + " ms, result was "
+ t.getValue());
}
}
Ниже приведена разница между двумя моими программами семени, и вы можете видеть, кроме имени класса, блок try является их единственной разницей:
$ diff TryBlock.java NoTryBlock.java
4c4
< public class TryBlock {
---
> public class NoTryBlock {
27c27
< TryBlock t = new TryBlock();
---
> NoTryBlock t = new NoTryBlock();
34d33
< try {
36,37d34
< } catch (Exception e) {
< }
42c39
< "method1 with try block took " + l + " ms, result was "
---
> "method1 without try block took " + l + " ms, result was "
Ниже представлен результат:
method1 without try block took,9732,ms, result was 2
method1 without try block took,9756,ms, result was 2
method1 without try block took,9845,ms, result was 2
method1 without try block took,9794,ms, result was 2
method1 without try block took,9758,ms, result was 2
method1 without try block took,9733,ms, result was 2
method1 without try block took,9763,ms, result was 2
method1 without try block took,9893,ms, result was 2
method1 without try block took,9761,ms, result was 2
method1 without try block took,9758,ms, result was 2
method1 with try block took,9776,ms, result was 2
method1 with try block took,9751,ms, result was 2
method1 with try block took,9767,ms, result was 2
method1 with try block took,9726,ms, result was 2
method1 with try block took,9779,ms, result was 2
method1 with try block took,9797,ms, result was 2
method1 with try block took,9845,ms, result was 2
method1 with try block took,9784,ms, result was 2
method1 with try block took,9787,ms, result was 2
method1 with try block took,9747,ms, result was 2