Почему стоит попробовать блоки?
Я слышал совет, что вам следует избегать попытки ловить блоки, если это возможно, так как они дорогие.
Мой вопрос конкретно о платформе.NET: почему пробные блоки дороги?
Резюме ответов:
В этом вопросе явно два лагеря: те, которые говорят, что пробные блоки стоят дорого, и те, которые говорят "возможно, чуть-чуть".
Те, которые говорят, что блоки try являются дорогими, обычно упоминают "высокую стоимость" раскрутки стека вызовов. Лично я не убежден этим аргументом - особенно после прочтения о том, как здесь хранятся обработчики исключений.
Джон Скит сидит в лагере "возможно, чуть-чуть" и написал две статьи об исключениях и производительности, которые вы можете найти здесь.
Была одна статья, которая мне показалась чрезвычайно интересной: в ней говорилось о "других" последствиях производительности блоков try (не обязательно потребления памяти или процессора). Питер Ричи упоминает, что он обнаружил, что код внутри блоков try не оптимизирован, как это было бы иначе компилятором. Вы можете прочитать о его результатах здесь.
Наконец, есть запись в блоге о проблеме от человека, который внедрил исключения в CLR. Посмотрите статью Криса Брамма здесь.
11 ответов
Это не сам блок, который дорог, и он даже не перехватывает исключение, само по себе, это дорого, это среда выполнения, раскручивающая стек вызовов, пока не найдет кадр стека, который может обработать исключение. Бросать исключение довольно легко, но если среда выполнения должна пройти шесть кадров стека (т.е. шесть вызовов метода в глубину), чтобы найти подходящий обработчик исключения, возможно, выполняя блоки finally по ходу дела, вы можете увидеть заметное количество времени, прошедшего,
Вы не должны избегать блоков try/catch, поскольку это обычно означает, что вы неправильно обрабатываете исключения, которые могут возникнуть. Структурная обработка исключений (SEH) стоит дорого только тогда, когда на самом деле возникает исключение, так как среда выполнения должна пройтись по стеку вызовов в поисках обработчика перехвата, выполнить этот обработчик (и их может быть больше одного), затем выполнить блоки finally и затем вернуть вернуться к коду в нужном месте.
Исключения предназначены не для управления логикой программы, а для индикации условий ошибки.
Одно из самых больших заблуждений об исключениях заключается в том, что они предназначены для "исключительных условий". Реальность такова, что они предназначены для сообщения об ошибках. С точки зрения дизайна каркаса не существует такого понятия, как "исключительное условие". Является ли условие исключительным или нет, зависит от контекста использования, но повторно используемые библиотеки редко знают, как они будут использоваться. Например, OutOfMemoryException может быть исключительным для простого приложения ввода данных; это не так уж и необычно для приложений, выполняющих собственное управление памятью (например, SQL-сервер). Другими словами, исключительное состояние одного человека является хроническим состоянием другого человека. http://blogs.msdn.com/kcwalina/archive/2008/07/17/ExceptionalError.aspx
Пробный блок совсем не дорогой. Небольшие или никакие затраты не понесены, если исключение не брошено. И если было сгенерировано исключение, это исключительное обстоятельство, и вы больше не заботитесь о производительности. Имеет ли значение, если падение вашей программы занимает 0,001 секунды или 1,0 секунды? Нет. Важно то, насколько хороша информация, сообщаемая вам, чтобы вы могли исправить ее и предотвратить повторение.
Я думаю, что люди действительно переоценивают стоимость производительности, создавая исключения. Да, производительность падает, но она относительно крошечная.
Я выполнил следующий тест, бросив и поймав миллион исключений. Это заняло около 20 секунд на моем Intel Core 2 Duo, 2,8 ГГц. Это около 50 тыс. Исключений в секунду. Если вы вносите даже небольшую часть этого, у вас есть некоторые проблемы с архитектурой.
Вот мой код:
using System;
using System.Diagnostics;
namespace Test
{
class Program
{
static void Main(string[] args)
{
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < 1000000; i++)
{
try
{
throw new Exception();
}
catch {}
}
Console.WriteLine(sw.ElapsedMilliseconds);
Console.Read();
}
}
}
Компилятор генерирует больше IL, когда вы заключаете код в блок try/catch; Посмотрите на следующую программу:
using System;
public class Program
{
static void Main(string[] args)
{
Console.WriteLine("abc");
}
}
Компилятор выдаст этот IL:
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 13 (0xd)
.maxstack 8
IL_0000: nop
IL_0001: ldstr "abc"
IL_0006: call void [mscorlib]System.Console::WriteLine(string)
IL_000b: nop
IL_000c: ret
} // end of method Program::Main
Пока для слегка модифицированной версии:
using System;
public class Program
{
static void Main(string[] args)
{
try { Console.WriteLine("abc"); }
catch { }
}
}
испускает больше:
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 23 (0x17)
.maxstack 1
IL_0000: nop
.try
{
IL_0001: nop
IL_0002: ldstr "abc"
IL_0007: call void [mscorlib]System.Console::WriteLine(string)
IL_000c: nop
IL_000d: nop
IL_000e: leave.s IL_0015
} // end .try
catch [mscorlib]System.Object
{
IL_0010: pop
IL_0011: nop
IL_0012: nop
IL_0013: leave.s IL_0015
} // end handler
IL_0015: nop
IL_0016: ret
} // end of method Program::Main
Все эти NOP и другие стоят.
ИМО, все это обсуждение похоже на высказывание: "вау, петли дорогие, потому что мне нужно увеличить счетчик... я не собираюсь больше их использовать", или "вау, создание объекта требует времени, я не собираюсь создавать тонны предметов больше."
Суть в том, что вы добавили код, предположительно по причине. Если после этого строки кода не повлекут за собой некоторые накладные расходы, даже если он занимает 1 цикл ЦП, то почему он существует? Нет ничего бесплатного.
Мудрее всего, как и в случае с любой строкой кода, которую вы добавляете в свое приложение, - помещать ее туда, только если вам нужно что-то сделать. Если вам нужно перехватить исключение, то сделайте это... так же, как если вам нужна строка для хранения чего-либо, создайте новую строку. Таким же образом, если вы объявляете переменную, которая никогда не используется, вы тратите впустую память и циклы ЦП, чтобы ее создать, и ее следует удалить. То же самое с попробовать / поймать.
Другими словами, если там есть код для чего-то, то предположим, что выполнение чего-либо будет каким-то образом потреблять процессор и / или память.
Вам не нужно беспокоиться о блоках, которые нужно беспокоить, а не о том, как ловить блоки. Кроме того, дело не в том, что вы хотите избежать написания блоков, а в том, что вы хотите как можно больше писать код, который на самом деле никогда не будет их использовать.
Это не то, о чем я когда-либо беспокоюсь. Я предпочел бы позаботиться о ясности и безопасности попытки... наконец-то прекратите размышлять о том, насколько это "дорого".
Лично я не использую 286, и никто не использует ни .NET, ни Java. Двигаться дальше. Беспокойство по поводу написания хорошего кода, который будет влиять на ваших пользователей и других разработчиков, вместо базовой структуры, которая отлично работает для 99,999999% людей, использующих его.
Это, вероятно, не очень полезно, и я не хочу разбивать, а просто выделять перспективу.
Я сомневаюсь, что они особенно дороги. Много раз они необходимы / необходимы.
Хотя я настоятельно рекомендую использовать их только в случае необходимости и в нужных местах / уровне вложенности, вместо того, чтобы выбрасывать исключение при каждом возврате вызова.
Я полагаю, что основной причиной такого совета было сказать, что вы не должны использовать try-catches, где если - иначе будет лучшим подходом.
Слегка O/T, но...
Существует довольно хорошая концепция дизайна, которая гласит, что вы никогда не должны требовать обработки исключений. Это просто означает, что вы должны иметь возможность запрашивать любой объект для любых условий, которые могут вызвать исключение до того, как это исключение будет сгенерировано.
Например, возможность сказать "writeable()" перед "write()" и тому подобное.
Это хорошая идея, и, если она используется, она делает проверенные исключения в Java несколько глупыми - я имею в виду, проверяя условие и сразу после этого, будучи вынужденным все еще писать попытку / перехват для того же условия?
Это довольно хороший шаблон, но проверенные исключения могут быть применены компилятором, а эти проверки - нет. Также не все библиотеки созданы с использованием этого шаблона проектирования - это просто то, что нужно иметь в виду, когда вы думаете об исключениях.
Каждая попытка требует записи большого количества информации, например указателей стека, значений регистра ЦП и т. Д., Чтобы она могла разматывать стек и возвращать состояние, в котором оно находилось при передаче блока try, в случае возникновения исключения. Мало того, что каждая попытка требует записи большого количества информации, когда генерируется исключение, необходимо восстановить множество значений. Так что попытка очень дорогая, а бросок / улов тоже очень дорогой.
Это не означает, что вы не должны использовать исключения, однако в критичном для производительности коде вы, возможно, не должны использовать слишком много попыток, а также не генерировать исключения слишком часто.