Я уверен, вы согласитесь, что медленный код C # - большая проблема.

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

Но трудно избежать медленного кода, потому что .NET Framework настолько огромен. Для любой конкретной проблемы существует множество решений, и не всегда ясно, какое решение является самым быстрым.

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

Давайте исследуем это немного дальше. Посмотрите мой код:

Этот код устанавливает список из 1000 строк. Каждая строка содержит случайную последовательность из 5 цифр от одной до девяти.

Метод MeasureTestA анализирует каждое число с помощью метода int.Parse и улавливает любые исключения формата.

Метод MeasureTestB анализирует каждое число с помощью int.TryParse. Теперь нет необходимости в обработчике исключений, потому что метод просто вернет false, если синтаксический анализ завершится неудачно.

Наконец, у нас есть MeasureTestC. В этом методе используется метод синтаксического анализа, заданный вручную, под названием IntParseFast для сканирования каждой цифры и вычисления результата вручную.

Как вы думаете, какой метод самый быстрый?

Давайте разберемся:

Вы этого ожидали?

Вы могли подумать, что int.Parse и int.TryParse должны иметь одинаковую производительность, но на самом деле TryParse в 1,2 раза быстрее!

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

Если задуматься, то методы Parse и TryParse должны иметь дело с множеством вещей: конкретными стилями чисел, поставщиками форматов, зависящими от языка и региональных параметров, недопустимыми входными данными, начальными или конечными пробелами и т. Д.

Таким образом, мы можем получить еще один прирост скорости, избавившись от всех этих накладных расходов. Мы знаем, что все строки содержат только действительные 1–9 цифр. Таким образом, метод FastParse просто сканирует строки напрямую и анализирует значения с минимумом хлопот.

И это действительно имеет значение. Метод FastParse в 1,83 раза быстрее, чем int.Parse!

Теперь давайте сделаем этот тест немного интереснее.

Видите эту строку кода?

private char[] digitArray = new char[] { '1', '2', '3', '4', '5', '6', '7', '8', '9' /* , 'X' */ };

Я собираюсь раскомментировать эту заключительную часть, например:

private char[] digitArray = new char[] { '1', '2', '3', '4', '5', '6', '7', '8', '9', 'X' };

Итак, теперь код собирает 5-значные числа, но иногда в нем будет добавлен символ «X», что делает число недействительным.

Посмотрим, как это повлияет на результаты:

Посмотри на это!

Код, использующий int.Parse, теперь более чем в 500 раз медленнее, чем код, использующий int.TryParse.

И, конечно же, причина в том, что int.Parse постоянно генерирует FormatExceptions, которые автоматически перехватываются.

Эти исключения значительно замедляют код. Я перешел от 10 000 итераций за 300 миллисекунд до 100 итераций за 1020 миллисекунд.

Другими словами: от 0,03 мс на номер до 10,2 мс на номер!

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

Этот процесс очень медленный.

Вот как следует использовать исключения:

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

В заключение давайте взглянем на два других теста. Я перезапущу тест с 10 000 итераций и отключу первый тест, в котором используется int.Parse:

MeasureTestB сильно замедлился. Раньше у меня время работы составляло 242 мс, но теперь оно увеличилось до 348 мс. Код в 1,4 раза медленнее.

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

Но теперь посмотрите на MeasureTestC. Раньше у меня время выполнения составляло 164 мс, а теперь оно составляет 161 мс. Производительность практически не изменилась!

Если вы посмотрите на IntParseFast, вы поймете, почему. Код для проверки недопустимых символов - это всего лишь один оператор if, и он выполняется независимо от того, действителен ли ввод или нет.

Так что мой вручную созданный метод синтаксического анализа в 2,16 раза быстрее, чем int.TryParse!

В общем, вот мой совет:

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