Відповіді:
Я не фахівець з мовних реалізацій (тому сприймайте це з достатньою кількістю солі), але я думаю, що однією з найбільших витрат є розмотування стека та зберігання його для трасування стека. Я підозрюю, що це відбувається лише тоді, коли виняток викидається (але я не знаю), а якщо так, то це буде пристойний розмір прихованої вартості кожного разу, коли буде викинуто виняток ... так що це не так, якби ви просто стрибаєте з одного місця в коді до іншого, багато що відбувається.
Я не думаю, що це проблема, поки ви використовуєте винятки для ВИНЯТКОВОЇ поведінки (тому не ваш типовий, очікуваний шлях через програму).
Тут слід зробити три пункти:
По-перше, у насправді наявність блоків try-catch у вашому коді майже не відповідає показнику продуктивності. Це не повинно враховуватись при спробі уникнути їхнього включення до вашої заяви. Хіт виконання виконується лише тоді, коли створюється виняток.
Коли виняток викидається на додаток до операцій розмотування стека тощо, які відбуваються, про які вже згадували інші, ви повинні знати, що відбувається ціла купа речей, пов'язаних із роботою / відображенням, щоб заповнити членів класу винятків, таких як трасування стека. об'єкт та різні типи членів тощо.
Я вважаю, що це одна з причин, чому загальна порада, якщо ви збираєтеся відновити виняток, полягає в тому, щоб просто throw;
не замінювати виняток знову, або будувати новий, оскільки в цих випадках вся ця інформація стека переглядається, тоді як у простому кинь це все збереглося.
throw new Exception("Wrapping layer’s error", ex);
Ви запитуєте про накладні витрати на використання try / catch / нарешті, коли винятки не створюються, або накладні витрати на використання винятків для управління потоком процесу? Останнє дещо схоже на використання палички динаміту для запалення свічки до дня народження малюка, і пов’язані з цим накладні витрати потрапляють у такі області:
Ви можете очікувати додаткових несправностей сторінок через викинуту виняток, що отримує доступ до коду та даних нерезидента, які зазвичай не входять у робочий набір вашої програми.
обидва вищезазначені елементи, як правило, мають доступ до "холодного" коду та даних, тому можливі неполадки на сторінках, якщо у вас взагалі тиск пам'яті:
Що стосується фактичного впливу вартості, це може сильно відрізнятися залежно від того, що ще відбувається у вашому коді на той момент. У Джона Скіта тут є хороший підсумок , де є кілька корисних посилань. Я, як правило, погоджуюся з його твердженням, що якщо ви дійдете до того, що винятки суттєво шкодять вашій роботі, у вас виникають проблеми з використанням винятків, не лише продуктивності.
З мого досвіду, найбільші накладні витрати полягають у тому, що насправді створюється виняток та обробляється його. Одного разу я працював над проектом, де код, подібний до наведеного, використовувався для перевірки, чи хтось мав право редагувати якийсь об’єкт. Цей метод HasRight () використовувався скрізь на рівні презентації, і його часто використовували для 100-ти об’єктів.
bool HasRight(string rightName, DomainObject obj) {
try {
CheckRight(rightName, obj);
return true;
}
catch (Exception ex) {
return false;
}
}
void CheckRight(string rightName, DomainObject obj) {
if (!_user.Rights.Contains(rightName))
throw new Exception();
}
Коли тестова база даних заповнюється тестовими даними, це призводить до дуже помітного уповільнення при відкритті нових форм тощо.
Тож я переробив його на наступне, що - згідно з пізнішими швидкими та брудними вимірами - приблизно на 2 порядки швидше:
bool HasRight(string rightName, DomainObject obj) {
return _user.Rights.Contains(rightName);
}
void CheckRight(string rightName, DomainObject obj) {
if (!HasRight(rightName, obj))
throw new Exception();
}
Отже, коротше кажучи, використання винятків у звичайному технологічному потоці відбувається приблизно на два порядки повільніше, ніж використання подібного процесу без винятків.
Всупереч загальновизнаним теоріям, try
/ catch
може мати значні наслідки для продуктивності, і саме це - виняток чи ні!
Перші за ці роки висвітлювались у кількох публікаціях блогів від MVP Microsoft, і я вірю, що ви могли їх легко знайти, але StackOverflow так піклується про вміст, тому я надаю посилання на деякі з них як доказ наповнення :
try
/ catch
/finally
( та частини другої ) Пітера Річі досліджує оптимізації, якіtry
/catch
/finally
відключають (і я піду далі в це з цитатами зі стандарту)Parse
проти TryParse
протиConvertTo
- Ян Хафф, відверто заявляє, що "обробка винятків відбувається дуже повільно", і демонструє цей момент, поєднуючисьInt.Parse
іInt.TryParse
протидіючи один одному ... Кожному, хто наполягає на тому, щоTryParse
використовуєtry
/catch
за кадром, це повинно пролити світло!Існує також така відповідь, яка показує різницю між розібраним кодом з використанням та без використання try
/ catch
.
Здається настільки очевидним, що є накладні витрати, які очевидно спостерігаються при генерації коду, і ці накладні витрати, схоже, визнають люди, які цінують Microsoft! Але я повторюю Інтернет ...
Так, є десятки додаткових інструкцій MSIL для одного тривіального рядка коду, і це навіть не охоплює відключені оптимізації, тому технічно це мікрооптимізація.
Я опублікував відповідь кілька років тому, яку видалили, оскільки вона зосереджувалась на продуктивності програмістів (макрооптимізація).
Це прикро, оскільки жодна економія кількох наносекунд тут і там часу процесора, ймовірно, не компенсує багато накопичених годин ручної оптимізації людьми. За що ваш начальник платить більше: годину вашого часу чи годину з працюючим комп’ютером? У який момент ми витягуємо штепсель і визнаємо, що пора просто придбати швидший комп’ютер ?
Зрозуміло, нам слід оптимізувати наші пріоритети , а не лише наш код! У своїй останній відповіді я спирався на відмінності між двома фрагментами коду.
Використання try
/ catch
:
int x;
try {
x = int.Parse("1234");
}
catch {
return;
}
// some more code here...
Не використовується try
/ catch
:
int x;
if (int.TryParse("1234", out x) == false) {
return;
}
// some more code here
Поміркуйте з точки зору розробника технічного обслуговування, який, швидше за все, витратить ваш час, якщо не на профілювання / оптимізацію (розглянуто вище), що, ймовірно, навіть не було б необхідним, якби не проблема try
/ catch
, то на прокрутку вихідний код ... Один із них має чотири додаткові рядки сміття!
Оскільки все більше і більше полів вводяться в клас, все це збірне сміття накопичується (як у вихідному, так і в розібраному коді) значно перевищуючи розумні рівні. Чотири зайві рядки на поле, і це завжди однакові рядки ... Хіба нас не навчили уникати повторення? Я припускаю, що ми могли б приховати try
/ catch
за якоюсь домашньою абстракцією, але ... тоді ми могли б просто уникати винятків (тобто використання Int.TryParse
).
Це навіть не складний приклад; Я бачив спроби створення нових класів у try
/ catch
. Врахуйте, що весь код всередині конструктора може бути дискваліфікований з-за певних оптимізацій, які в іншому випадку автоматично застосовуються компілятором. Який кращий спосіб породити теорію про те, що компілятор повільний , на відміну від компілятора, виконує саме те, що йому наказали робити ?
Припускаючи, що згаданий конструктор видає виняток, і в результаті запускається якась помилка, поганий розробник технічного обслуговування потім повинен його відстежити. Це може бути не таким простим завданням, оскільки, на відміну від коду спагеті goto nightmare, try
/ catch
може спричинити безлад у трьох вимірах , оскільки може рухатися вгору по стеку не просто в інші частини того самого методу, а й в інші класи та методи , все це буде спостерігати розробник технічного обслуговування, важкий шлях ! І все ж нам кажуть, що "гото небезпечно", ге!
Наприкінці я згадую, try
/ catch
має свою перевагу, яка полягає в тому, що вона призначена для відключення оптимізації ! Це, якщо хочете, допомога у налагодженні ! Для цього він був розроблений, і саме для цього його слід використовувати ...
Я думаю, це теж позитивний момент. Він може бути використаний для відключення оптимізацій, які в іншому випадку можуть скалічити безпечні, здорові алгоритми передачі повідомлень для багатопотокових додатків, а також для виявлення можливих перегонових умов;) Це приблизно єдиний сценарій, який я можу придумати, щоб використовувати функцію try / catch. Навіть у цього є альтернативи.
Що оптимізації робити try
, catch
і finally
відключити?
AKA
Як try
, catch
і finally
корисний в якості засобу налагодження?
вони перешкоди для запису. Це походить від стандарту:
12.3.3.13 Заяви про спроби лову
Для виписки stmt форми:
try try-block catch ( ... ) catch-block-1 ... catch ( ... ) catch-block-n
- Стан визначеного призначення v на початку блоку спроб такий самий, як і визначений стан призначення v на початку stmt .
- Визначений стан призначення v на початку catch-block-i (для будь-якого i ) такий самий, як і визначений стан призначення v на початку stmt .
- Визначений стан призначення v у кінцевій точці stmt , безумовно, призначається, якщо (і лише тоді), коли v визначено в кінцевій точці try-block та кожного catch-block-i (для кожного i від 1 до n ).
Іншими словами, на початку кожного try
твердження:
try
оператора, повинні бути завершеними, що вимагає блокування потоку для початку, що робить це корисним для налагодження гоночних умов!try
оператораПодібна історія справедлива для кожного catch
твердження; припустимо, у межах вашого try
висловлювання (або конструктора, або функції, яку він викликає тощо), яку ви призначаєте цій безглуздій змінній (скажімо, garbage=42;
), компілятор не може усунути цей вислів, як би це не мало значення для спостережуваної поведінки програми . Призначення має бути виконане до введення catch
блоку.
Що того варте, finally
розповідає подібну принизливу історію:
12.3.3.14 Спроби остаточних тверджень
Для випробування твердження stmt форми:
try try-block finally finally-block
• Стан визначеного призначення v на початку блоку спроб такий самий, як і визначений стан призначення v на початку stmt .
• Стан визначеного призначення v на початку блоку нарешті такий самий, як і визначений стан призначення v на початку stmt .
• Визначений стан призначення v у кінцевій точці stmt , безумовно, присвоюється, якщо (і лише якщо): o v визначено в кінцевій точці блоку try o vбезумовно призначається в кінцевій точці нарешті-блоку. Якщо зроблено передачу потоку керування (наприклад, оператор goto ), яка починається всередині блоку try і закінчується поза блоком try , тоді v також вважається безумовно призначеною для цього керування передачею потоку, якщо v кінцевій точці нарешті блоку визначено v . (Це не єдиний випадок, якщо - якщо v визначено з іншої причини на цій передачі потоку управління, то це все ще вважається визначеним.)
12.3.3.15 Заяви "спробуй зловити"
Аналіз Певною призначення для спроби - зловити - нарешті твердження форми:
try try-block catch ( ... ) catch-block-1 ... catch ( ... ) catch-block-n finally finally-block
виконується так, ніби оператор спроба - нарешті оператор, що включає оператор try - catch :
try { try try-block catch ( ... ) catch-block-1 ... catch ( ... ) catch-block-n } finally finally-block
Не кажучи вже про те, що всередині часто називаного методу це може вплинути на загальну поведінку програми.
Наприклад, я вважаю використання Int32.Parse поганою практикою в більшості випадків, оскільки він створює винятки для того, що можна легко вловити в іншому випадку.
Отже, щоб завершити все написане тут:
1) Використовуйте блоки try..catch, щоб ловити несподівані помилки - майже відсутність покарання за продуктивність.
2) Не використовуйте винятки для виключених помилок, якщо ви можете цього уникнути.
Я писав статтю про це ще деякий час тому, що тоді багато людей запитувало про це. Ви можете знайти його та код тесту на веб- сайті http://www.blackwasp.co.uk/SpeedTestTryCatch.aspx .
Результатом є те, що для блоку try / catch існує невелика кількість накладних витрат, але настільки мала, що його слід ігнорувати. Однак якщо ви запускаєте блоки спроби / лову у циклах, які виконуються мільйони разів, можливо, ви захочете розглянути можливість переміщення блоку за межі циклу, якщо це можливо.
Ключова проблема продуктивності блоків try / catch - це коли ви насправді ловите виняток. Це може додати помітну затримку вашої програми. Звичайно, коли справа йде не так, більшість розробників (і багато користувачів) визнають паузу як виняток, який ось-ось має відбутися! Головне тут - не використовувати обробку винятків для звичайних операцій. Як випливає з назви, вони виняткові, і ви повинні зробити все можливе, щоб уникнути їх кидання. Не слід використовувати їх як частину очікуваного потоку програми, яка працює належним чином.
Ми зробили запис у блозі на цю тему минулого року. Перевір. Підсумок полягає в тому, що для блоку try майже немає витрат, якщо не виникає винятків - і на моєму ноутбуці виняток становив близько 36 мкс. Це може бути менше, ніж ви очікували, але майте на увазі, що ці результати знаходяться на неглибокому стосі. Крім того, перші винятки дійсно повільні.
try
/ catch
занадто багато? Хе-хе), але ви, схоже, сперечаєтесь із мовною специфікацією та деякими MVP MS, які також писали блоги на цю тему, забезпечуючи вимірювання на противагу вашій пораді ... Я відкритий для припущення, що проведене мною дослідження є неправильним, але мені потрібно прочитати запис у вашому блозі, щоб побачити, що в ньому сказано.
try-catch
блок-блок tryparse()
, але концепція однакова.
Набагато простіше писати, налагоджувати та підтримувати код, який не містить повідомлень про помилки компілятора, попереджувальних повідомлень про аналіз коду та прийнятих винятків (зокрема, винятків, які додаються в одне місце, а приймаються в інше). Оскільки це простіше, код в середньому буде краще писатись і мати менше помилок.
Для мене цей програміст та накладні витрати на якість є основним аргументом проти використання методу спроби-лову для процесу.
Накладні витрати комп’ютера на винятки незначні в порівнянні і, як правило, незначні з точки зору здатності програми відповідати реальним вимогам до продуктивності.
Мені дуже подобається допис у блозі Хафтора , і, щоб додати свої два центи до цього обговорення, я хотів би сказати, що мені завжди було легко, коли РАЙ ДАНИХ кидав лише один тип винятків (DataAccessException). Таким чином мій БІЗНЕС ШАР знає, на який виняток очікувати, і вловлює його. Потім, залежно від подальших бізнес-правил (тобто, якщо мій бізнес-об’єкт бере участь у робочому процесі тощо), я можу створити нове виняток (BusinessObjectException) або продовжити, не повторюючи / кидаючи.
Я б сказав, не соромтеся використовувати try..catch, коли це необхідно, і використовуйте його з розумом!
Наприклад, цей метод бере участь у робочому процесі ...
Коментарі?
public bool DeleteGallery(int id)
{
try
{
using (var transaction = new DbTransactionManager())
{
try
{
transaction.BeginTransaction();
_galleryRepository.DeleteGallery(id, transaction);
_galleryRepository.DeletePictures(id, transaction);
FileManager.DeleteAll(id);
transaction.Commit();
}
catch (DataAccessException ex)
{
Logger.Log(ex);
transaction.Rollback();
throw new BusinessObjectException("Cannot delete gallery. Ensure business rules and try again.", ex);
}
}
}
catch (DbTransactionException ex)
{
Logger.Log(ex);
throw new BusinessObjectException("Cannot delete gallery.", ex);
}
return true;
}
Ми можемо прочитати в програмуванні мов програмування Майкла Л. Скотта, що сучасні компілятори не додають жодних накладних витрат у загальному випадку, це означає, коли не виникає винятків. Тож кожна робота робиться за час компіляції. Але коли під час виконання виникає виняток, компілятору потрібно виконати двійковий пошук, щоб знайти правильний виняток, і це відбуватиметься при кожному новому кидку, який ви зробили.
Але винятки - це винятки, і ця вартість цілком прийнятна. Якщо ви спробуєте виконати обробку винятків без винятків і замість цього скористатися кодами помилок повернення, можливо, вам знадобиться оператор if для кожної підпрограми, і це призведе до накладних витрат у реальному часі. Ви знаєте, якщо оператор перетворюється на кілька інструкцій з монтажу, які виконуватимуться кожного разу, коли ви входите у свої підпрограми.
Вибачте за мою англійську, сподіваюся, що це вам допоможе. Ця інформація базується на цитованій книзі, для отримання додаткової інформації див. Розділ 8.5 Обробка винятків.
Давайте проаналізуємо одну з найбільших можливих витрат блоку try / catch, коли він використовується там, де його не потрібно використовувати:
int x;
try {
x = int.Parse("1234");
}
catch {
return;
}
// some more code here...
І ось той без спроби / лову:
int x;
if (int.TryParse("1234", out x) == false) {
return;
}
// some more code here
Не рахуючи незначного пробілу, можна помітити, що ці два еквівалентні фрагменти коду мають майже однакову довжину в байтах. Останній містить на 4 байти менше відступу. Це погано?
Щоб додати образи до шкоди, студент вирішує цикл, тоді як введення може бути проаналізовано як int. Рішення без try / catch може бути приблизно таким:
while (int.TryParse(...))
{
...
}
Але як це виглядає при використанні try / catch?
try {
for (;;)
{
x = int.Parse(...);
...
}
}
catch
{
...
}
Блоки try / catch - це магічні способи марно витрачати відступи, і ми все ще навіть не знаємо, чому це не вдалося! Уявіть, як почувається людина, яка робить налагодження, коли код продовжує виконуватись після серйозного логічного недоліку, а не зупиняється з приємною очевидною помилкою винятку. Блоки try / catch - це перевірка / санітарія даних ледачого.
Однією з менших витрат є те, що блоки try / catch дійсно відключають певні оптимізації: http://msmvps.com/blogs/peterritchie/archive/2007/06/22/performance-implications-of-try-catch-finally.aspx . Я думаю, це теж позитивний момент. Він може бути використаний для відключення оптимізацій, які в іншому випадку можуть скалічити безпечні, здорові алгоритми передачі повідомлень для багатопотокових додатків, а також для виявлення можливих перегонових умов;) Це приблизно єдиний сценарій, який я можу придумати, щоб використовувати функцію try / catch. Навіть у цього є альтернативи.
Int.Parse
на користь Int.TryParse
.