Почему эта разница в производительности? (Ловля исключений) - PullRequest
7 голосов
/ 07 декабря 2009

Прочитав здесь вопрос о том, что может сделать наш компьютер за одну секунду, я провел небольшой тест, который некоторое время думал, и очень удивлен результатами. Посмотрите:

Простая программа для перехвата нулевого исключения, для выполнения 1900 итераций требуется почти одна секунда:

for(long c = 0; c < 200000000; c++)
{
    try
    {
        test = null;
        test.x = 1;
    }
    catch (Exception ex)
    {
    }
}

В качестве альтернативы, проверяя, если test == null, перед выполнением присваивания, та же самая программа может выполнить приблизительно 200000000 итераций за одну секунду.

for(long c = 0; c < 1900; c++)
{
    test = null;
    f (!(test == null))
    {
        test.x = 1;
    }
}

У кого-нибудь есть подробное объяснение, почему это ОГРОМНОЕ различие?

РЕДАКТИРОВАТЬ: Запуск теста в режиме выпуска, за пределами Visual Studio, я получаю 35000-40000 итераций против 400000000 итераций (всегда приблизительно)

Примечание Я запускаю это с дерьмовым PIV 3,06 ГГц

Ответы [ 7 ]

12 голосов
/ 07 декабря 2009

Нет способа, который должен занимать секунду для 1900 итераций, если вы не работаете в отладчике. Выполнение тестов производительности под отладчиком - плохая идея.

РЕДАКТИРОВАТЬ: обратите внимание, что это не случай перехода на выпуск build - это случай запуска без отладчика; то есть нажатие Ctrl-F5 вместо F5 .

Сказав это, провоцировать исключения, когда их можно очень легко избежать, также плохая идея.

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

Исключения являются дорогостоящими при отладчиках - в любом случае, конечно, в Visual Studio - из-за того, что приходится разбираться с отладчиком и т. Д., И, вероятно, выполнять любой объем стекового анализа, который в противном случае не нужен. В любом случае они все еще стоят несколько дорого, но вы не должны бросать их достаточно, чтобы заметить. Остается выполнить раскрутку стека, найти соответствующие обработчики перехвата и т. Д., Но это должно происходить только тогда, когда что-то не так с самого начала.

РЕДАКТИРОВАТЬ: Конечно, создание исключения по-прежнему будет давать вам меньше итераций в секунду (хотя 35000 все еще очень низкое число - я бы ожидал более 100К), потому что вы делаете почти ничего в случае не исключения. Давайте посмотрим на два:

Неисключительная версия тела цикла

  • Присвоить ноль переменной
  • Проверить, является ли переменная нулевой; это так, так что вернитесь к началу цикла

(Как уже упоминалось в комментариях, вполне возможно, что JIT все равно оптимизирует это ...)

Исключительная версия:

  • Присвоить ноль переменной
  • переменная разыменования
    • Неявная проверка на ничтожность
    • Создать объект исключения
    • Проверка любых отфильтрованных обработчиков исключений для вызова
    • Найдите в стеке блок перехвата, чтобы перейти к
    • Проверьте, нет ли блоков наконец
    • Филиал соответственно

Удивительно ли, что вы видите меньше производительности?

Теперь сравните это с более распространенной ситуацией, когда вы выполняете целую кучу работы, возможно, ввода-вывода, создания объектов и т. Д. - и , возможно, , возникает исключение. Тогда разница станет намного менее значимой.

2 голосов
/ 07 декабря 2009

Здесь есть еще один фактор. Если у вас есть файл .pdb в каталоге выполнения, то при возникновении исключения среда выполнения .NET считывает файл .pdb, чтобы получить номер строки кода, который необходимо включить в трассировку стека исключений. Это занимает совсем немного времени. Попробуйте свой первый метод (с исключением) с файлом .pdb и без него в каталоге выполнения.

Я провел простой тест времени с и без .pdb на месте в качестве ответа на другой вопрос, здесь .

2 голосов
/ 07 декабря 2009

Вам также может пригодиться этот популярный вопрос: Насколько медленны исключения .NET?

2 голосов
/ 07 декабря 2009

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

1 голос
/ 07 декабря 2009

В моих тестах «исключительный» код не такой медленный - намного медленнее, но не так сильно. Разница заключается в создании объекта Exception (или, если быть точным, NullReferenceException). Самая медленная часть - извлечение строки для сообщения об исключении - есть внутренний вызов GetResourceString - и получение трассировки стека.

1 голос
/ 07 декабря 2009

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

0 голосов
/ 07 декабря 2009

Это ужасный микро тест.

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

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

Исключения являются достаточно дорогими (по отношению к обычному потоку управления ветвлением) [1], в основном из-за 3 вещей:

  1. все исключения являются ссылочными типами и поэтому (на данный момент) распределяются в куче, а затем собираются в мусор.
  2. Уровни стека, заполняемые в исключении (Это пропорционально расстоянию, которое раскручивает стек - то, что вы, например, не можете полностью измерить)
  3. При рассмотрении кода обработки исключений пропускаются все приятные вещи, такие как предсказание ветвлений, которые позволяют современному глубоко конвейерному процессору продолжать делать что-то полезное

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


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