Насколько медленны исключения .NET?

Я не хочу обсуждать, когда и не бросать исключения. Я хочу решить простую проблему. В 99% случаев аргумент о том, что не исключение исключений, вращается вокруг них медленным, в то время как другая сторона утверждает (с контрольным тестом), что скорость не является проблемой. Я читал многочисленные блоги, статьи и сообщения, относящиеся к той или другой стороне. Итак, что это?

Некоторые ссылки из ответов: Skeet, Мариани, Brumme.

Ответ 1

Я нахожусь на "не медленной" стороне - или, точнее, "не настолько медленной, чтобы ее можно было избежать при обычном использовании". Я написал два short статьи об этом. Существуют критические замечания по эталонному аспекту, которые в основном сводятся к тому, что "в реальной жизни будет больше стека, чтобы пройти, чтобы вы взорвали кеш и т.д.", Но с использованием кодов ошибок для работы над стеком удалите кеш, поэтому я не вижу в этом особого аргумента.

Просто, чтобы понять это - я не поддерживаю использование исключений, если они нелогичны. Например, int.TryParse полностью подходит для преобразования данных от пользователя. Это необходимо при чтении машинного файла, где сбой означает: "Файл не в том формате, в котором он должен быть, я действительно не хочу пытаться справиться с этим, поскольку я не знаю, что еще может быть неправильно."

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

Ответ 2

Существует окончательный ответ на этот вопрос от парня, который их реализовал, - Криса Брумме. Он написал отличную статью в блоге о предмете (предупреждение - очень длинное) (warning2 - его очень хорошо написано, если вы техничный вы я прочитаю его до конца, а потом должен будет выполнить ваши часы после работы:))

Резюме: они медленные. Они реализованы в виде исключений Win32 SEH, поэтому некоторые из них даже передадут границу 0 на границе 0! Очевидно, что в реальном мире вы будете выполнять много другой работы, поэтому нечетное исключение не будет замечено вообще, но если вы используете их для потока программы, за исключением того, что ваше приложение будет забито. Это еще один пример маркетинговой машины MS, делающей нам плохую услугу. Я помню одну микрософт, рассказывающую нам, как они понесли абсолютно нулевые накладные расходы, что является полным тошем.

Крис дает уместную цитату:

Фактически CLR внутренне использует исключения даже в неуправляемом части двигателя. Однако, существует серьезный долгосрочный проблема производительности с исключениями и это должно быть учтено в вашем решение.

Ответ 3

Я понятия не имею, о чем говорят люди, когда говорят, что они медленны, только если их бросили.

EDIT: Если Исключения не выбрасываются, значит, вы делаете новое Exception() или что-то в этом роде. В противном случае исключение будет приводить к приостановке потока и стеку. Это может быть хорошо в небольших ситуациях, но на сайтах с высоким трафиком, полагаясь на исключения как на рабочий процесс или на механизм выполнения, безусловно, вызовет проблемы с производительностью. Исключения, как таковые, не являются плохими и полезны для выражения исключительных условий.

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

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

Кроме того, согласно моей копии "Тестирования производительности Microsoft.NET Web Applications", написанной командой ACE:

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

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

Ответ 4

Аргумент, насколько я понимаю, не в том, что бросание исключений плохое, они медленны как таковые. Вместо этого речь идет о том, чтобы использовать конструкцию throw/catch как первый способ управления нормальной логикой приложения, а не более традиционные условные конструкции.

Часто в обычной логике приложения вы выполняете цикл, где одно и то же действие повторяется тысячи/миллионы раз. В этом случае с очень простым профилированием (см. Класс Stopwatch) вы можете сами убедиться, что исключение вместо простого утверждения if может оказаться значительно медленнее.

На самом деле я однажды прочитал, что команда .NET в Microsoft представила методы TryXXXXX в .NET 2.0 для многих базовых типов FCL специально потому, что клиенты жаловались на то, что производительность их приложений была настолько медленной.

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

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

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

Ответ 5

Мой сервер XMPP получил большой прирост скорости (извините, никаких фактических чисел, чисто наблюдательных) после того, как я последовательно пытался предотвратить их (например, проверить, подключен ли сокет, прежде чем пытаться читать больше данных) и давать мне способы чтобы избежать их (упомянутые методы TryX). Это было только около 50 активных (чат) виртуальных пользователей.

Ответ 6

У меня никогда не было проблем с производительностью с исключениями. Я часто пользуюсь исключениями - я никогда не использую коды возврата, если могу. Это плохая практика, и, на мой взгляд, пахнет кодом спагетти.

Я думаю, что все это сводится к тому, как вы используете исключения: если вы используете их, как коды возврата (каждый вызов метода в уловах и повторных вызовах), тогда, да, они будут медленными, потому что у вас есть накладные расходы на каждый улов/бросить.

Но если вы бросите в нижней части стека и поймаете вверху (вы замените целую цепочку кодов возврата одним броском /catch ), все дорогостоящие операции выполняются один раз.

В конце дня они являются действительной языковой функцией.

Просто чтобы доказать свою точку зрения

Пожалуйста, запустите код по этой ссылке (слишком большой для ответа).

Результаты на моем компьютере:

[email protected]:~/develop/test$ mono Exceptions.exe | grep PM
10/2/2008 2:53:32 PM
10/2/2008 2:53:42 PM
10/2/2008 2:53:52 PM

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

Ответ 7

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

Они, безусловно, должны использовать коды ошибок, преимущества - огромные IMO.

Ответ 8

Просто чтобы добавить мой собственный недавний опыт в эту дискуссию: в соответствии с большей частью того, что написано выше, я обнаружил, что исключения исключения чрезвычайно медленны, когда они выполняются на повторной основе, даже без работы отладчика. Я просто увеличил производительность большой программы, которую пишу на 60%, изменив примерно пять строк кода: переход на модель кода возврата вместо исключения исключений. Конечно, этот код выполнялся тысячи раз и потенциально бросал тысячи исключений, прежде чем я его изменил. Поэтому я согласен с вышеприведенным утверждением: бросайте исключения, когда что-то важное идет не так, а не как способ управления потоком приложений в любых "ожидаемых" ситуациях.

Ответ 9

Но моно выбрасывает исключение в 10 раз быстрее, чем автономный режим .net, и .net автономный режим генерирует исключение 60 раз быстрее, чем режим отладки .net. (Испытательные машины имеют одинаковую модель ЦП)

int c = 1000000;
int s = Environment.TickCount;
for (int i = 0; i < c; i++)
{
    try { throw new Exception(); }
    catch { }
}
int d = Environment.TickCount - s;

Console.WriteLine(d + "ms / " + c + " exceptions");

Ответ 10

В режиме освобождения служебные данные минимальны.

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

Ответ 11

В CLR для Windows для цепочки вызовов глубины 8 выброс исключения в 750 раз медленнее, чем проверка и распространение возвращаемого значения. (см. ниже для тестов)

Эта высокая стоимость исключений объясняется тем, что CLR Windows интегрируется с чем-то, называемым Обработка структурированных исключений Windows. Это позволяет исключить правильные уловки и перехватывать разные режимы работы и языки. Однако это очень медленно.

Исключения в среде исполнения Mono (на любой платформе) намного быстрее, потому что они не интегрируются с SEH. Тем не менее, есть потеря функциональности при передаче исключений в течение нескольких сеансов, поскольку он не использует ничего подобного SEH.

Ниже приведены сокращенные результаты из моего теста исключений и возвращаемых значений для Windows CLR.

baseline: recurse_depth 8, error_freqeuncy 0 (0), time elapsed 13.0007 ms
baseline: recurse_depth 8, error_freqeuncy 0.25 (0), time elapsed 13.0007 ms
baseline: recurse_depth 8, error_freqeuncy 0.5 (0), time elapsed 13.0008 ms
baseline: recurse_depth 8, error_freqeuncy 0.75 (0), time elapsed 13.0008 ms
baseline: recurse_depth 8, error_freqeuncy 1 (0), time elapsed 14.0008 ms
retval_error: recurse_depth 5, error_freqeuncy 0 (0), time elapsed 13.0008 ms
retval_error: recurse_depth 5, error_freqeuncy 0.25 (249999), time elapsed 14.0008 ms
retval_error: recurse_depth 5, error_freqeuncy 0.5 (499999), time elapsed 16.0009 ms
retval_error: recurse_depth 5, error_freqeuncy 0.75 (999999), time elapsed 16.001 ms
retval_error: recurse_depth 5, error_freqeuncy 1 (999999), time elapsed 16.0009 ms
retval_error: recurse_depth 8, error_freqeuncy 0 (0), time elapsed 20.0011 ms
retval_error: recurse_depth 8, error_freqeuncy 0.25 (249999), time elapsed 21.0012 ms
retval_error: recurse_depth 8, error_freqeuncy 0.5 (499999), time elapsed 24.0014 ms
retval_error: recurse_depth 8, error_freqeuncy 0.75 (999999), time elapsed 24.0014 ms
retval_error: recurse_depth 8, error_freqeuncy 1 (999999), time elapsed 24.0013 ms
exception_error: recurse_depth 8, error_freqeuncy 0 (0), time elapsed 31.0017 ms
exception_error: recurse_depth 8, error_freqeuncy 0.25 (249999), time elapsed 5607.3208     ms
exception_error: recurse_depth 8, error_freqeuncy 0.5 (499999), time elapsed 11172.639  ms
exception_error: recurse_depth 8, error_freqeuncy 0.75 (999999), time elapsed 22297.2753 ms
exception_error: recurse_depth 8, error_freqeuncy 1 (999999), time elapsed 22102.2641 ms

И вот код.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1 {

public class TestIt {
    int value;

    public class TestException : Exception { } 

    public int getValue() {
        return value;
    }

    public void reset() {
        value = 0;
    }

    public bool baseline_null(bool shouldfail, int recurse_depth) {
        if (recurse_depth <= 0) {
            return shouldfail;
        } else {
            return baseline_null(shouldfail,recurse_depth-1);
        }
    }

    public bool retval_error(bool shouldfail, int recurse_depth) {
        if (recurse_depth <= 0) {
            if (shouldfail) {
                return false;
            } else {
                return true;
            }
        } else {
            bool nested_error = retval_error(shouldfail,recurse_depth-1);
            if (nested_error) {
                return true;
            } else {
                return false;
            }
        }
    }

    public void exception_error(bool shouldfail, int recurse_depth) {
        if (recurse_depth <= 0) {
            if (shouldfail) {
                throw new TestException();
            }
        } else {
            exception_error(shouldfail,recurse_depth-1);
        }

    }

    public static void Main(String[] args) {
        int i;
        long l;
        TestIt t = new TestIt();
        int failures;

        int ITERATION_COUNT = 1000000;


        // (0) baseline null workload
        for (int recurse_depth = 2; recurse_depth <= 10; recurse_depth+=3) {
            for (float exception_freq = 0.0f; exception_freq <= 1.0f; exception_freq += 0.25f) {            
                int EXCEPTION_MOD = (exception_freq == 0.0f) ? ITERATION_COUNT+1 : (int)(1.0f / exception_freq);            

                failures = 0;
                DateTime start_time = DateTime.Now;
                t.reset();              
                for (i = 1; i < ITERATION_COUNT; i++) {
                    bool shoulderror = (i % EXCEPTION_MOD) == 0;
                    t.baseline_null(shoulderror,recurse_depth);
                }
                double elapsed_time = (DateTime.Now - start_time).TotalMilliseconds;
                Console.WriteLine(
                    String.Format(
                      "baseline: recurse_depth {0}, error_freqeuncy {1} ({2}), time elapsed {3} ms",
                        recurse_depth, exception_freq, failures,elapsed_time));
            }
        }


        // (1) retval_error
        for (int recurse_depth = 2; recurse_depth <= 10; recurse_depth+=3) {
            for (float exception_freq = 0.0f; exception_freq <= 1.0f; exception_freq += 0.25f) {            
                int EXCEPTION_MOD = (exception_freq == 0.0f) ? ITERATION_COUNT+1 : (int)(1.0f / exception_freq);            

                failures = 0;
                DateTime start_time = DateTime.Now;
                t.reset();              
                for (i = 1; i < ITERATION_COUNT; i++) {
                    bool shoulderror = (i % EXCEPTION_MOD) == 0;
                    if (!t.retval_error(shoulderror,recurse_depth)) {
                        failures++;
                    }
                }
                double elapsed_time = (DateTime.Now - start_time).TotalMilliseconds;
                Console.WriteLine(
                    String.Format(
                      "retval_error: recurse_depth {0}, error_freqeuncy {1} ({2}), time elapsed {3} ms",
                        recurse_depth, exception_freq, failures,elapsed_time));
            }
        }

        // (2) exception_error
        for (int recurse_depth = 2; recurse_depth <= 10; recurse_depth+=3) {
            for (float exception_freq = 0.0f; exception_freq <= 1.0f; exception_freq += 0.25f) {            
                int EXCEPTION_MOD = (exception_freq == 0.0f) ? ITERATION_COUNT+1 : (int)(1.0f / exception_freq);            

                failures = 0;
                DateTime start_time = DateTime.Now;
                t.reset();              
                for (i = 1; i < ITERATION_COUNT; i++) {
                    bool shoulderror = (i % EXCEPTION_MOD) == 0;
                    try {
                        t.exception_error(shoulderror,recurse_depth);
                    } catch (TestException e) {
                        failures++;
                    }
                }
                double elapsed_time = (DateTime.Now - start_time).TotalMilliseconds;
                Console.WriteLine(
                    String.Format(
                      "exception_error: recurse_depth {0}, error_freqeuncy {1} ({2}), time elapsed {3} ms",
                        recurse_depth, exception_freq, failures,elapsed_time));         }
        }
    }
}


}

Ответ 12

Здесь вы найдете краткое описание производительности, связанной с исключениями catching.

Когда путь выполнения входит в блок "try", ничего волшебного не происходит. Нет инструкции "try" и нет затрат, связанных с входом или выходом из блока try. Информация о блоке try хранится в метаданных метода, и эти метаданные используются во время выполнения всякий раз, когда возникает исключение. Механизм выполнения перемещается вниз по стеклу, ища первый вызов, который содержался в блоке try. Любые служебные данные, связанные с обработкой исключений, возникают только при исключении исключений.

Ответ 13

При написании классов/функций для других пользователей это трудно сказать, когда исключения являются подходящими. Есть некоторые полезные части BCL, которые я должен был канавать и идти на pinvoke, потому что они бросают исключения вместо того, чтобы возвращать ошибки. В некоторых случаях вы можете обойти это, но для других, таких как System.Management и Performance Counters, есть способы использования, где вам нужно делать циклы, в которых исключения часто генерируются BCL.

Если вы пишете библиотеку и там есть возможность, что ваша функция может использоваться в цикле и существует вероятность большого количества итераций, используйте шаблон Try.. или каким-либо другим способом выставить ошибки рядом с исключениями. И даже тогда трудно сказать, сколько будет вызвана ваша функция, если она будет использоваться многими процессами в общей среде.

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