Pull to refresh
-3

User

Send message

А что вы строите на исключениях? 99% "ошибок" - это логика, требующая обработки.

В моём понимании (и практике) ровно наоборот - условные "99% ошибок" внутри процессинга - это прерывающие исключения.
Всё, что не прерывает - это нормальный случай, он выбирается по условиям/анализу/валидации параметров-данных. Или тот самый Try*-pattern.

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

Одноразовые (per API call/button press) проверки - вообще, как правило, не влияют на картину throughput, могут быть построены как угодно (до известной степени, конечно).

...если что - обработка миллиардов итераций per-API-call (в том числе с необходимостью обработки исключений на горячем пути) - специфика моих систем в последние долгие уже годы, так что "цену" разных мелочей я прочувствовал и понимаю. И как показывает моя практика - оптимизация нормального/expected случая "весит" куда больше, чем возможные исключения.

Как вы себе представляете работу try/catch без throw где-то внутри?

Не "без throw где-то внутри", а "без try-catch внутри длинного цикла".
Запросто, try-catch снаружи цикла, если нужно прервать цикл при исключении. Если нужно "пропускать" "исключительные" итерации и продолжать дальше - внешний while/do-while/или-даже-for, внутри подготовка условий и контекста для внутреннего цикла, try-catch, внутри него - внутренний цикл по подготовленным условиям и контексту.
Вариантов масса.

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

И таких "исключительных" записей ожидается больше, чем погрешность от общего количества? - тогда это выглядит не как исключительный случай, а как вариант нормы - здесь просится валидация данных итерации и выбор одного из путей обработки или условный if (!TryProcess(...)) { ... } - зачем строить такую логику на исключениях?
Внешние неконтролируемые зависимости внутри цикла? - переписать согласно предыдущей рекомендации, прогнать нагрузочные - если всё ещё неприемлемо - заменить зависимости на самописные/более подходящие.

В любом случае, этот пример - хорошая иллюстрация использования неподходящей техники, а не ущербности исключений.

начнут строго спрашивать за производительность

try-catch-finally/using практически бесплатны (если не помещать их внутри длинного цикла на горячем пути, конечно)

throw дорогой, но - можно пример сценария, при котором throw начинает влиять на производительность нормального/expected случая, а значит и среднюю производительность?

избегать большой вложенности вызовов (и большой глубины стека) - это тоже хороший стиль

Более чем спорно

Да, был не прав, задумался о своём.

Конечно же, и поля не даёт напрямую изменить, и сеттер для проперти вызвать.

Термин defensive copy применительно к не-ридонли структурам о чём-нибудь говорит?

Вообще-то - нет, не избегает. Ровно такое же поведение с т.з. defensive copy.

Важно, что LinkedList выделяет в куче индивидуальные ноды, тогда так List - массивы (с запасом).

Если списки не настолько большие, чтобы перейти в LOH, то начиная с определённого момента затраты на GC и "утрамбовку" памяти для списка могут стать гораздо дешевле, чем для связного списка.

Не знаю как в вашем случае, а для какого-нибудь хайлоада/хай срупута я бы предпочел список (если быть до конца точным - структуру, основанную на массивах и не допускающую попадания в ЛОХ)

На мой взгляд Zero-Alloc LINQ можно было бы реализовать 100% безопасным кодом

Есть опыт?

Я бы лично с удовольствием (и благодарностью) ознакомился, особенно под .НЕТ ниже седьмого.

Тут есть очень много "но".

Демонстрационный пример очень удачный, так как у нас простой фаст-трак/хэппи пас и несколько обработчиков исключительных ситуаций, куда мы выпадаем редко и передаём в них минимум параметров.

Теперь представим себе, что основная логика и она же - хэппи пас у нас реально объёмная. Она может быть простая, ветвлений минимум, фаст трак (проверки условий, после которых в 99% мы просто переходим на следующую инструкцию) - но объём метода в целом сотни строк, если не тысяча/чи.

Допустим, мы хотим сделать его менее врайт-онли и поделить на более короткие методы с ограниченными ответственностями. Ок, но теперь в каждый выделенный метод мы должны передавать весь необходимый контекст - что запросто может оказаться десятком параметров. А некоторые параметры у нас, к примеру, относительно объёмные структуры. Ок, передаём их по ссылке. Но, теперь у нас ещё и локальность данных размазалась по стеку.

Не уверен, что джит всё это заинлайнит и вернёт наш исходный фаст трак, даже если повесить на каждый метод агрессив инлайнинг.

Ну, как не уверен? Эмпирически знаю - не заинлайнит.

В итоге мы поделили наш фаст трак на несколько вызовов-возвратов, к каждому из которых добавляется копирование всех нужных параметров.

И ради чего? Читабельность? Это, конечно, хорошо, но не всегда стоит потерь производительности.

И это только первое из многих "но" :)

Я к чему? Без бенчмарков и профилирование на разных сценариях я лично никаких сложных и объёмных методов на горячем пути разделять не советовал бы.

...объединять, кстати, на моём опыте - менее рисковано.

Нет, массив всегда есть последовательный непрерывный блок памяти.

Оператор fixed просто запрещает GC перемещать блок памяти (на время своего действия).

А как ключевое слово - позволяет объявить инлайн-буфер заданной длины внутри unsafe-структуры.

Когда вы обрабатываете 10 байт это одно а когда 10 гигабайт это другое.

Когда 10 байт лежат рядом в текущей кэш-линии - это одно, а когда их нужно прочитать непосредственно из DRAM - это совсем другое.

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

Но есть нюансы.

Про кэш-линии уже упомянули.

Но моё любимое - GC.
Мусора такой список создаёт слишком много, чтобы быть быстрым [на более-менее долгом промежутке времени].
Я лично бенчмаркал все стандартные не-массив-бейсед коллекции (включая LinkedList, SortedList и SortedSet), и могу сказать, что для хайлоада они слишком много мусорят. Те, что tree-based - даже при енумерации.

Для себя нашел выход: если действительно не нужен доступ по индексу (и байнари сёрча и т.п.) - лучший вариант это связанный список, но не создающий ноду в куче на каждый элемент, а использующий struct ноды в предварительно выделенных массивах. Да, есть оверхед для учёта и переиспользования свободных/освободившихся слотов в массивах нод, оверхэд на выделение большего массива и копирование данных при добавлении сверх текущей ёмкости, но в целом это гораздо лучший вариант для списков, которые живут дольше одной-двух сборок мусора.

Было бы очень любопытно узнать больше деталей.

Тоже писал некоторым-образом-виртуальную-машину, правда входом для неё был не код, а граф датафлоу. Было бы интересно сравнить опыт.

Хозяева решили закрыть принадлежащее им предприятие.

Ну, вчера хозяева, завтра - никто.
Доля в собственности не отменяет необходимости порядочно себя вести.

Ну, как-бы кидок, не?
После попытки кидка любая реакция считается в пределах нормы.

Технично. Всё правильно сделали.

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

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

...не понимаю праведного возмущения по поводу отжима. Как-бы вторая сторона сама сделала для этого всё возможное.

Собственно, к чему я и вёл.

Пропустил слово: одним из самых частых ОБРАБАТЫВАЕМЫХ кодов ошибки будет что-то вроде АНЭКСПЕКТЕД_РЕТУРН_КОД.

Имелось ввиду "любой".

Не, любопытно было чем этот подход кажется лучше исключений. В сравнении с го [мне] понятно в чём смысл.

Не хочу занудствовать, но теперь уже реально хочется понять.

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

Теперь я правильно понял?

Если речь об этом, то концептуально это одно и то же.

И проблема остаётся той же: мы не можем с уверенностью собрать все возможные типы ошибок так, чтобы ко всему прочему оно оставалось верным и во время исполнения. А значит в реальной жизни одним из самых частых кодов ошибки будет что-то вроде АНЭКСПЕКТЕД_РЕТУРН_КОД.

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

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

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

В этом был бы смысл, если бы было возможно найти ВСЕ везможные исключения метода/функции. Но это невозможно, см. примеры Интерфейса/Абстрактного класса/Рантайма новой версии/Плагина.

Значит либо программисту всё равно придётся лезть в документацию сторонних библиотек/свой код - и даже это не поможет в случае обновления рантайма/сторонних библиотек на исполняющей машине, либо он будет оперировать неполной (==ложной) информацией.
Что в итоге даёт нам одинаковый результат.

Вывод: "нинада". Перефразируя: лови, что можешь исправить и будь, что будет.

Примерно такой у меня ход мысли на этот счёт.

Information

Rating
Does not participate
Registered
Activity