Pull to refresh
-3

User

0,2
Rating
Send message

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

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 ноды в предварительно выделенных массивах. Да, есть оверхед для учёта и переиспользования свободных/освободившихся слотов в массивах нод, оверхэд на выделение большего массива и копирование данных при добавлении сверх текущей ёмкости, но в целом это гораздо лучший вариант для списков, которые живут дольше одной-двух сборок мусора.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

для этого ничего, кроме документации АПИ не нужно.

  1. Да при чём тут хедер - скомпилировали с хедером версии 1.1, в рантайме доступна версия 1.2, на уровне сигнатур функций полностью совместимы, а список исключений изменился.

  2. Никто ничего не факапит. Усложним: Программа предоставляет плагину некоторое окружение, в том числе функции/проперти, есть списки исключений. Система скомпилирована, версия 1.1. Третья контора написала под эту версию плагин, плагин свободно продаётся. У пользователя система версии 1.2, плагин скомпилированный под версию 1.1. АПИ бинарно совместимы, список исключений системы версии 1.2 отличается.

    Что скомпилированный плагин может знать об исключениях рантайма новой версии?

Information

Rating
3,080-th
Registered
Activity