Здравствуйте, WolfHound, Вы писали:
C>>DWARF-исключения заметно раздувают код для обработки ошибочных путей, а таблицы исключений заметно давят на кэш процессора. Так что использовать исключения для нормального flow-control'а — очень неэффективно. WH>А для нормального их никто и не использует.
А хочется.
WH>Они нужны для того чтобы обработать ситуацию когда что-то пошло не так.
В этом и проблема. "Что-то пошло не так" может различаться с точки зрения автора кода и программиста, который этот код использует. Классика жанра — должен ли File.open() кидать исключение, если файл не найден?
Здравствуйте, Cyberax, Вы писали:
WH>>А для нормального их никто и не использует. C>А хочется.
Не-а.
WH>>Они нужны для того чтобы обработать ситуацию когда что-то пошло не так. C>Классика жанра — должен ли File.open() кидать исключение, если файл не найден?
Голосовалку замути. Очередное ---или--- вслед за моей вчерашней. Я за исключение проголосую, потому что имена файлов, которые открывать собираюсь, не с бухты-барахты выдумываю и имею право ожидать, что они должны открыться успешно. Десктопники тоже — либо свои собственные файлы открывают, либо выбранное юзером в диалоге OpenFile — тоже обычно существующие. А вот когда приходилось на PHP низкоуровнево с файлами работать, то вся эта C-style муть с трёхстрочными проверками на ошибку на каждую строку собственно логики бесила.
Здравствуйте, WolfHound, Вы писали:
ARK>>Мой тест показывает, что одно исключение уже медленнее, чем миллион проверок. WH>Так ты даже корректный тест написать не можешь...
Да, кстати. А чем это мои тесты некорректны? 5 миллионов проверок есть? Есть. Что еще надо?
Напишите уже внятно, изложите свою позицию. Если, конечно, она у вас есть.
Здравствуйте, AlexRK, Вы писали: ARK>Ну так приведите корректный тест с правильными кодами возврата, а не сотрясайте воздух.
Код с исключениями в 4.7 раза быстрее.
Release:
No exceptions: 00:00:00.0079723 (1784293664), 00:00:00.0016705 (1784293664)
Single exception: 00:00:00.0082693 (1785293665), 00:00:00.0016904 (1785293665)
Two exceptions: 00:00:00.0077591 (1785293665), 00:00:00.0017407 (1785293665)
Debug:
No exceptions: 00:00:00.0449391 (1784293664), 00:00:00.0218418 (1784293664)
Single exception: 00:00:00.0438513 (1785293665), 00:00:00.0222914 (1785293665)
Two exceptions: 00:00:00.0436745 (1785293665), 00:00:00.0216939 (1785293665)
Скрытый текст
using System;
using System.Diagnostics;
class Prog
{
int func1_1(int a, out int result)
{
result = a + 1;
if (a > 1000000)
return -1;
else
return 0;
}
int func1_2(int a, out int result)
{
if (func1_1(a, out result) == 0)
return 0;
else
return -1;
}
int func1_3(int a, out int result)
{
if (func1_2(a, out result) == 0)
return 0;
else
return -1;
}
int func1_4(int a, out int result)
{
if (func1_3(a, out result) == 0)
return 0;
else
return -1;
}
int func1_5(int a, out int result)
{
if (func1_4(a, out result) == 0)
return 0;
else
return -1;
}
int func2_1(int a)
{
if (a > 1000000)
throw new Exception("qq");
return a + 1;
}
int func2_2(int a)
{
return func2_1(a);
}
int func2_3(int a)
{
return func2_2(a);
}
int func2_4(int a)
{
return func2_3(a);
}
int func2_5(int a)
{
return func2_4(a);
}
private string Run(string title, int max)
{
var start1 = Stopwatch.StartNew();
int res1 = 0;
for (int i = 0; i < max; i++)
{
int val;
if (func1_5(i, out val) == 0)
{
func1_5(i, out val);
res1 += val;
}
else
break;
}
var end1 = start1.Elapsed;
var start2 = Stopwatch.StartNew();
int res2 = 0;
try
{
for (int i = 0; i < max; i++)
{
res2 += func2_5(i);
}
}
catch
{
// do nothing
}
var end2 = start2.Elapsed;
return String.Format("{0}: {1} ({2}), {3} ({4})", title, end1, res1, end2, res2);
}
public static void Main()
{
var prog = new Prog();
prog.Run("test run", 1000010);
prog.Run("test run", 1000010);
prog.Run("test run", 1000010);
var line1 = prog.Run("No exceptions", 1000000);
var line2 = prog.Run("Single exception", 1000002);
var line3 = prog.Run("Two exceptions", 1000003);
System.IO.File.WriteAllLines("c:\\temp\\temp.txt", new[] { line1, line2, line3 });
}
}
... << RSDN@Home 1.2.0 alpha 5 rev. 62>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Здравствуйте, WolfHound, Вы писали:
ARK>>Ну так приведите корректный тест с правильными кодами возврата, а не сотрясайте воздух. WH>Код с исключениями в 4.7 раза быстрее.
У вас тут мелкое жульничество в виде двойного вызова func1_5.
Кстати, чем это от моего последнего "некорректного" теста отличается?
Здравствуйте, AlexRK, Вы писали:
ARK>У вас тут мелкое жульничество в виде двойного вызова func1_5.
Исправил. Коды возврата всё равно сливают.
ARK>Кстати, чем это от моего последнего "некорректного" теста отличается?
А в цикле код возврата проверять, кто будет? Пушкин?
... << RSDN@Home 1.2.0 alpha 5 rev. 62>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Здравствуйте, WolfHound, Вы писали:
WH>Здравствуйте, artelk, Вы писали:
A>>Убери лишний вызов и перезапусти плз. WH>Копипаста. Но код с исключениями всёравно быстрее в 1.5 раза.
Не думаю, что в .net сильно заморачивались с оптимизацией исключений. Но если их использовать по назначению (т.е. для исключительных ситуаций), то это не проблема — обычно после них что-то где-то логгируется, что делает тормоза исключений пренебрежимо малой величиной.
Здравствуйте, dimgel, Вы писали:
D>Ограничить дальность полёта можно только одним способом: ловить на каком-нибудь промежуточном слое и (опционально) заворачивать в нечто более понятное слою верхнему.
Открой для себя Boost.Exception, который позволяет присоединять к исключению информацию о контексте. Ничего заворачивать и не нужно.
Здравствуйте, jazzer, Вы писали:
J>Здравствуйте, vsb, Вы писали:
vsb>>На мой взгляд это проблема исключительно С++, а точнее его двух особенностей — отсутствие GC и запрет исключений в деструкторах.
J>Нет такого запрета.
Запрета нет, но классы, выбрасывающие из деструктора, регулярными считаться не не могут. Они разве что могут играть роль утилит с очень специфичным поведением. Недаром в C++11 деструкторы являются noexcept(true) по умолчанию.
Здравствуйте, WolfHound, Вы писали:
WH>Исправил. Коды возврата всё равно сливают.
В случае с одним исключением результат практически одинаковый. Без исключений вообще — да, коды возврата слегка медленнее.
С двумя исключениями у exception-based code был бы слив, просто в тесте после вылета первого исключения замер прекращается.
ARK>>Кстати, чем это от моего последнего "некорректного" теста отличается? WH>А в цикле код возврата проверять, кто будет? Пушкин?
Это не имеет отношения к корректности теста. Можно было и не писать с многозначительным видом напыщенные фразы про некорректность.
Просто вместо 5 миллионов ифов стало 6. Можно и 7 сделать, если добавить еще один уровень вложенности.
Ну и самое главное осталось без комментариев.
А именно:
1) реальное приложение выполняет реальную работу, и проверки на ее фоне будут не видны в микроскоп;
2) не предоставлено обоснования, что exception-based code быстрее, чем retval-based code. Тесты показывают только одно — для дотнета одно исключение по времени соответствует 5 миллионам проверок.
Здравствуйте, vsb, Вы писали:
vsb>Это Checked Exceptions в Java.
Для концептуальной законченности им не хватает нескольких вещей:
1. Полного запрета бросать руками RuntimeException
2. Перевода всех RuntimeException в либо в checked либо в error.
3. Синтаксического сахара позволяющего конвертировать необработанные исключения при выбрасывании их вверх по стеку
Что-то типа:
class UserDao {
exception SQLException translate to DaoException;
User getUser(long id) throws DaoException {
//code can cause SQL exception
}
}
vsb>Во-вторых ряд исключений не вылетит вообще никогда. Например Integer.parseInt("1"). Никогда тут не вылетит исключение NumberFormatException. А компилятор попросит обложить try-catch-ем.
Never say "never". Завтра пользователь поставит локаль где все числа римские, а программист поправит parseInt чтобы он использовал локаль и здравствуй исключение. Большинство таких ситуация вызваны кривым дизайном языка, а не фундаментальными проблемами.
vsb>В-третьих ряд исключений могут вылететь в любом месте. В разных языках по-разному. Например NullPointerException теоретически может вылететь на любой строчке, где есть ссылки или вызывается функций. StackOverflowError может вылететь опять же везде, где вызывается любая функция. Что с ними делать?
Запретить null как класс. Вместо него использовать либо Option, либо вообще ничего не использовать. Как в Scala:
[code=scala]
val userOption:Option[User] = userDao.getUser(userId);
for (user <- userOption) {
doSmth(user);
}
[/code]
vsb>теперь примеряем его на FileInputStream, а оказыавется, что close там хочет кидать IOException. Чего нам делать? Либо добавляем в throws IOException, либо перекидыаем как непроверяемое исключение. И имеем опять что имеем.
Но разработчики спрева не подумали, а потом у них не хватило яиц поломать обратную несовместимость.
vsb>Если добваляем throws IOException, то куча классов этот IOException никогда кидать не будет. StringWriter, например, пишет во внутренний буффер-строку, никакого там IO нет. А вот при закрытии — изволь, обработай IOException, мало ли, вдруг кинется, в сигнатуре написано же.
LSP никто не отменял. Завтра StringWriter заменят на BufferedFileWriter а вызывающий код должен работать как прежде. Единственный способ достичь этого это корректно обработать исключенние даже если сейчас оно не бросается.
Здравствуйте, uncommon, Вы писали:
U>Здравствуйте, jazzer, Вы писали:
J>>Здравствуйте, vsb, Вы писали:
vsb>>>На мой взгляд это проблема исключительно С++, а точнее его двух особенностей — отсутствие GC и запрет исключений в деструкторах.
J>>Нет такого запрета.
U>Запрета нет, но классы, выбрасывающие из деструктора, регулярными считаться не не могут. Они разве что могут играть роль утилит с очень специфичным поведением. Недаром в C++11 деструкторы являются noexcept(true) по умолчанию.
Все так, просто я не хотел, чтоб у читающих не-С++-ников создалось ложное впечатление, что в С++ такой запрет есть.
Рекомендация — да, есть, даже в Стандарте записана, но не запрет (т.е. программа, содержащия выбрасывание исключения из деструктора, не считается содержащей ошибку).
А насчет регулярности типов — на этот счет есть очень разные мнения.
Например, Степанов в своей книге включает в определение регулярности типа наличие копирования, присваивания и конструктора по умолчанию (плюс, емнип, сравнение на равенство и больше-меньше — чтоб тип можно было использовать в качестве ключа ассоциативного контейнера. Ну и хэш тогда тоже, наверное, заодно).
Ну и, плюс, не факт, что регулярность так уж необходима.
Здравствуйте, LaptevVV, Вы писали:
LVV>Одна из серьезных проблем, которую практически не поминают — это парадигма обработки исключений. LVV>По сути это событийное программирование. LVV>И вот получается, что в одной проге смешаны как минимум две, а то и три парадигмы: ООП+событийное (и очень часто процедурное).
Да хоть 10 парадигм в одной проге — ничего страшного. Главное парадигмы применять правильно и тогда, когда это имеет смысл. А проблемы с исключениями возникают тогда, когда с их помощью начинают хреначить логику, и они становятся не исключениями, а нормальным потоком выполнения программы.
А типичная обработка исключений — это вывести пользователю сообщение об ошибке или матернуться в лог о том, что случилась какая то хрень. 99 процентов всех обработок исключений. В ряде случаев приходится некоторую логику навешивать. Например для вебсервисов внезапно могут куки авторизации стать невалидными, например по причине перезапуска сервиса. В этом случае нужно перелогиниться и повторить попытку. Соответствуем декорируем вызов метода, и обработчик исключения сделает перелогин и вызовет метод еще раз.
Здравствуйте, Miroff, Вы писали:
M>Запретить null как класс. Вместо него использовать либо Option, либо вообще ничего не использовать. Как в Scala:
M>[code=scala] M>val userOption:Option[User] = userDao.getUser(userId);
M>for (user <- userOption) { M> doSmth(user); M>} M>[/code]
и в чем тут профит? Вместо эксепшена в строке user->doSmth(), doSmth молча не будет выполнено, что выстрелит где-то позже и в другом месте...
опять же, сравни с
val user = userDao.getUser(userId);
if (user)
user->doSmth();
по строчкам — тоже самое, по символам — короче
Если добавить чуток сахара, можно было бы писать user?.doSmth(), как в груви. И тут кстати user — обычная ссылка, а не Option<User>
Здравствуйте, enji, Вы писали:
M>>Запретить null как класс. Вместо него использовать либо Option, либо вообще ничего не использовать.
E>И кстати — как его запретить? Вообще запретить простые ссылки? Т.е. везде только Option?
E>И во что тогда превратится такое: obj1.calc().doIt().getSome() ?
Можно сделать два вида ссылок — nullable и nonnullable. Компилятор легко может проверить все места, где возможны проблемы, и заставить вставить проверки.
Здравствуйте, enji, Вы писали:
M>>Запретить null как класс. Вместо него использовать либо Option, либо вообще ничего не использовать. E>И кстати — как его запретить? Вообще запретить простые ссылки? Т.е. везде только Option? E>И во что тогда превратится такое: obj1.calc().doIt().getSome() ?
Если каждый из уровней может потенциально вернуть Nothing или его аналог — то придется выписывать руками проверки. Ну или засахарить как-то типа obj1?.calc()?.doIt()?.getSome().
Но фишка в том, что обычно такие вызовы не будут возвращать Nothing. А там, где все-таки возвращают — как раз в коде и будет хорошо видно.
Здравствуйте, enji, Вы писали:
M>>[code=scala] M>>for (user <- userOption) { M>> doSmth(user); M>>} M>>[/code]
E>и в чем тут профит? Вместо эксепшена в строке user->doSmth(), doSmth молча не будет выполнено, что выстрелит где-то позже и в другом месте...
Пожалуйста: doSmth(userOption.get). Здесь get может вылетить NoSuchElementException.
E>опять же, сравни с
E>
E>val user = userDao.getUser(userId);
E>if (user)
user->>doSmth();
E>
E>по строчкам — тоже самое, по символам — короче
E>Если добавить чуток сахара, можно было бы писать user?.doSmth(), как в груви. И тут кстати user — обычная ссылка, а не Option<User>