Насколько медленны исключения .NET? - PullRequest
142 голосов
/ 02 октября 2008

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

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

Ответы [ 13 ]

202 голосов
/ 02 октября 2008

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

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

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

29 голосов
/ 02 октября 2008

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

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

Крис дает соответствующую цитату:

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

8 голосов
/ 02 октября 2008

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

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

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

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

Более того, согласно моей копии «Тестирование производительности веб-приложений Microsoft .NET», написанной командой ACE:

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

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

6 голосов
/ 02 октября 2008

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

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

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

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

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

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

4 голосов
/ 02 октября 2008

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

3 голосов
/ 25 марта 2011

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

2 голосов
/ 24 февраля 2012

Но моно генерирует исключение в 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");
2 голосов
/ 02 октября 2008

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

Их определенно стоит использовать по сравнению с кодами ошибок, их преимущества огромны.

2 голосов
/ 02 октября 2008

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

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

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

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

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

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

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

marco@sklivvz:~/develop/test$ mono Exceptions.exe | grep PM<br> 10/2/2008 2:53:32 PM<br> 10/2/2008 2:53:42 PM<br> 10/2/2008 2:53:52 PM

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

1 голос
/ 21 марта 2012

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

Эта высокая стоимость исключений объясняется тем, что Windows CLR интегрируется с чем-то, что называется Структурная обработка исключений 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));         }
        }
    }
}


}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...