try-catch-finally/using практически бесплатны (если не помещать их внутри длинного цикла на горячем пути, конечно)
throw дорогой, но - можно пример сценария, при котором throw начинает влиять на производительность нормального/expected случая, а значит и среднюю производительность?
избегать большой вложенности вызовов (и большой глубины стека) - это тоже хороший стиль
Важно, что LinkedList выделяет в куче индивидуальные ноды, тогда так List - массивы (с запасом).
Если списки не настолько большие, чтобы перейти в LOH, то начиная с определённого момента затраты на GC и "утрамбовку" памяти для списка могут стать гораздо дешевле, чем для связного списка.
Не знаю как в вашем случае, а для какого-нибудь хайлоада/хай срупута я бы предпочел список (если быть до конца точным - структуру, основанную на массивах и не допускающую попадания в ЛОХ)
Демонстрационный пример очень удачный, так как у нас простой фаст-трак/хэппи пас и несколько обработчиков исключительных ситуаций, куда мы выпадаем редко и передаём в них минимум параметров.
Теперь представим себе, что основная логика и она же - хэппи пас у нас реально объёмная. Она может быть простая, ветвлений минимум, фаст трак (проверки условий, после которых в 99% мы просто переходим на следующую инструкцию) - но объём метода в целом сотни строк, если не тысяча/чи.
Допустим, мы хотим сделать его менее врайт-онли и поделить на более короткие методы с ограниченными ответственностями. Ок, но теперь в каждый выделенный метод мы должны передавать весь необходимый контекст - что запросто может оказаться десятком параметров. А некоторые параметры у нас, к примеру, относительно объёмные структуры. Ок, передаём их по ссылке. Но, теперь у нас ещё и локальность данных размазалась по стеку.
Не уверен, что джит всё это заинлайнит и вернёт наш исходный фаст трак, даже если повесить на каждый метод агрессив инлайнинг.
Ну, как не уверен? Эмпирически знаю - не заинлайнит.
В итоге мы поделили наш фаст трак на несколько вызовов-возвратов, к каждому из которых добавляется копирование всех нужных параметров.
И ради чего? Читабельность? Это, конечно, хорошо, но не всегда стоит потерь производительности.
И это только первое из многих "но" :)
Я к чему? Без бенчмарков и профилирование на разных сценариях я лично никаких сложных и объёмных методов на горячем пути разделять не советовал бы.
...объединять, кстати, на моём опыте - менее рисковано.
Ключевое слово здесь "не нужен доступ по индексу". На первый взгляд можно бы и согласиться - добавление, вставка и удаление будут быстрее.
Но есть нюансы.
Про кэш-линии уже упомянули.
Но моё любимое - GC. Мусора такой список создаёт слишком много, чтобы быть быстрым [на более-менее долгом промежутке времени]. Я лично бенчмаркал все стандартные не-массив-бейсед коллекции (включая LinkedList, SortedList и SortedSet), и могу сказать, что для хайлоада они слишком много мусорят. Те, что tree-based - даже при енумерации.
Для себя нашел выход: если действительно не нужен доступ по индексу (и байнари сёрча и т.п.) - лучший вариант это связанный список, но не создающий ноду в куче на каждый элемент, а использующий struct ноды в предварительно выделенных массивах. Да, есть оверхед для учёта и переиспользования свободных/освободившихся слотов в массивах нод, оверхэд на выделение большего массива и копирование данных при добавлении сверх текущей ёмкости, но в целом это гораздо лучший вариант для списков, которые живут дольше одной-двух сборок мусора.
Любопытно, а если бы... европейцы не поделились никакими доступами - получилось бы добиться такого же результата?
И ещё, почему сразу не отрубили все внешние доступы? - я подозреваю, что подвох в любой момент был ожидаем, как решились дожидаться шагов с той стороны?
...не понимаю праведного возмущения по поводу отжима. Как-бы вторая сторона сама сделала для этого всё возможное.
Не хочу занудствовать, но теперь уже реально хочется понять.
Т.е. иными словами, неявно каждый метод, в дополнении к результату возвращает и некий код результата/ошибки, который вывел и назначил компилятор, а каждый вызов оборачивается анализом с передачей результата в продолжение если код Ок, либо переходом в соответствующий обработчик/немедленным возвращением этого же кода вверх по стеку?
Теперь я правильно понял?
Если речь об этом, то концептуально это одно и то же.
И проблема остаётся той же: мы не можем с уверенностью собрать все возможные типы ошибок так, чтобы ко всему прочему оно оставалось верным и во время исполнения. А значит в реальной жизни одним из самых частых кодов ошибки будет что-то вроде АНЭКСПЕКТЕД_РЕТУРН_КОД.
Ну и отдельный большой вопрос - почему нужно делать именно так, чем оно лучше?
Я понимаю, компилятор/ИДЕ/анализатор может находить некоторое подмножество возможных исключений и показывать это программисту/помогать обрабатывать.
В этом был бы смысл, если бы было возможно найти ВСЕ везможные исключения метода/функции. Но это невозможно, см. примеры Интерфейса/Абстрактного класса/Рантайма новой версии/Плагина.
Значит либо программисту всё равно придётся лезть в документацию сторонних библиотек/свой код - и даже это не поможет в случае обновления рантайма/сторонних библиотек на исполняющей машине, либо он будет оперировать неполной (==ложной) информацией. Что в итоге даёт нам одинаковый результат.
Вывод: "нинада". Перефразируя: лови, что можешь исправить и будь, что будет.
Да при чём тут хедер - скомпилировали с хедером версии 1.1, в рантайме доступна версия 1.2, на уровне сигнатур функций полностью совместимы, а список исключений изменился.
Никто ничего не факапит. Усложним: Программа предоставляет плагину некоторое окружение, в том числе функции/проперти, есть списки исключений. Система скомпилирована, версия 1.1. Третья контора написала под эту версию плагин, плагин свободно продаётся. У пользователя система версии 1.2, плагин скомпилированный под версию 1.1. АПИ бинарно совместимы, список исключений системы версии 1.2 отличается.
Что скомпилированный плагин может знать об исключениях рантайма новой версии?
try-catch-finally/using практически бесплатны (если не помещать их внутри длинного цикла на горячем пути, конечно)
throw дорогой, но - можно пример сценария, при котором throw начинает влиять на производительность нормального/expected случая, а значит и среднюю производительность?
Более чем спорно
Да, был не прав, задумался о своём.
Конечно же, и поля не даёт напрямую изменить, и сеттер для проперти вызвать.
Термин defensive copy применительно к не-ридонли структурам о чём-нибудь говорит?
Вообще-то - нет, не избегает. Ровно такое же поведение с т.з. defensive copy.
Важно, что LinkedList выделяет в куче индивидуальные ноды, тогда так List - массивы (с запасом).
Если списки не настолько большие, чтобы перейти в LOH, то начиная с определённого момента затраты на GC и "утрамбовку" памяти для списка могут стать гораздо дешевле, чем для связного списка.
Не знаю как в вашем случае, а для какого-нибудь хайлоада/хай срупута я бы предпочел список (если быть до конца точным - структуру, основанную на массивах и не допускающую попадания в ЛОХ)
Есть опыт?
Я бы лично с удовольствием (и благодарностью) ознакомился, особенно под .НЕТ ниже седьмого.
Тут есть очень много "но".
Демонстрационный пример очень удачный, так как у нас простой фаст-трак/хэппи пас и несколько обработчиков исключительных ситуаций, куда мы выпадаем редко и передаём в них минимум параметров.
Теперь представим себе, что основная логика и она же - хэппи пас у нас реально объёмная. Она может быть простая, ветвлений минимум, фаст трак (проверки условий, после которых в 99% мы просто переходим на следующую инструкцию) - но объём метода в целом сотни строк, если не тысяча/чи.
Допустим, мы хотим сделать его менее врайт-онли и поделить на более короткие методы с ограниченными ответственностями. Ок, но теперь в каждый выделенный метод мы должны передавать весь необходимый контекст - что запросто может оказаться десятком параметров. А некоторые параметры у нас, к примеру, относительно объёмные структуры. Ок, передаём их по ссылке. Но, теперь у нас ещё и локальность данных размазалась по стеку.
Не уверен, что джит всё это заинлайнит и вернёт наш исходный фаст трак, даже если повесить на каждый метод агрессив инлайнинг.
Ну, как не уверен? Эмпирически знаю - не заинлайнит.
В итоге мы поделили наш фаст трак на несколько вызовов-возвратов, к каждому из которых добавляется копирование всех нужных параметров.
И ради чего? Читабельность? Это, конечно, хорошо, но не всегда стоит потерь производительности.
И это только первое из многих "но" :)
Я к чему? Без бенчмарков и профилирование на разных сценариях я лично никаких сложных и объёмных методов на горячем пути разделять не советовал бы.
...объединять, кстати, на моём опыте - менее рисковано.
Нет, массив всегда есть последовательный непрерывный блок памяти.
Оператор fixed просто запрещает GC перемещать блок памяти (на время своего действия).
А как ключевое слово - позволяет объявить инлайн-буфер заданной длины внутри unsafe-структуры.
Когда 10 байт лежат рядом в текущей кэш-линии - это одно, а когда их нужно прочитать непосредственно из DRAM - это совсем другое.
Ключевое слово здесь "не нужен доступ по индексу". На первый взгляд можно бы и согласиться - добавление, вставка и удаление будут быстрее.
Но есть нюансы.
Про кэш-линии уже упомянули.
Но моё любимое - GC.
Мусора такой список создаёт слишком много, чтобы быть быстрым [на более-менее долгом промежутке времени].
Я лично бенчмаркал все стандартные не-массив-бейсед коллекции (включая LinkedList, SortedList и SortedSet), и могу сказать, что для хайлоада они слишком много мусорят. Те, что tree-based - даже при енумерации.
Для себя нашел выход: если действительно не нужен доступ по индексу (и байнари сёрча и т.п.) - лучший вариант это связанный список, но не создающий ноду в куче на каждый элемент, а использующий struct ноды в предварительно выделенных массивах. Да, есть оверхед для учёта и переиспользования свободных/освободившихся слотов в массивах нод, оверхэд на выделение большего массива и копирование данных при добавлении сверх текущей ёмкости, но в целом это гораздо лучший вариант для списков, которые живут дольше одной-двух сборок мусора.
Было бы очень любопытно узнать больше деталей.
Тоже писал некоторым-образом-виртуальную-машину, правда входом для неё был не код, а граф датафлоу. Было бы интересно сравнить опыт.
Ну, вчера хозяева, завтра - никто.
Доля в собственности не отменяет необходимости порядочно себя вести.
Ну, как-бы кидок, не?
После попытки кидка любая реакция считается в пределах нормы.
Технично. Всё правильно сделали.
Любопытно, а если бы... европейцы не поделились никакими доступами - получилось бы добиться такого же результата?
И ещё, почему сразу не отрубили все внешние доступы? - я подозреваю, что подвох в любой момент был ожидаем, как решились дожидаться шагов с той стороны?
...не понимаю праведного возмущения по поводу отжима. Как-бы вторая сторона сама сделала для этого всё возможное.
Собственно, к чему я и вёл.
Пропустил слово: одним из самых частых ОБРАБАТЫВАЕМЫХ кодов ошибки будет что-то вроде АНЭКСПЕКТЕД_РЕТУРН_КОД.
Имелось ввиду "любой".
Не, любопытно было чем этот подход кажется лучше исключений. В сравнении с го [мне] понятно в чём смысл.
Не хочу занудствовать, но теперь уже реально хочется понять.
Т.е. иными словами, неявно каждый метод, в дополнении к результату возвращает и некий код результата/ошибки, который вывел и назначил компилятор, а каждый вызов оборачивается анализом с передачей результата в продолжение если код Ок, либо переходом в соответствующий обработчик/немедленным возвращением этого же кода вверх по стеку?
Теперь я правильно понял?
Если речь об этом, то концептуально это одно и то же.
И проблема остаётся той же: мы не можем с уверенностью собрать все возможные типы ошибок так, чтобы ко всему прочему оно оставалось верным и во время исполнения. А значит в реальной жизни одним из самых частых кодов ошибки будет что-то вроде АНЭКСПЕКТЕД_РЕТУРН_КОД.
Ну и отдельный большой вопрос - почему нужно делать именно так, чем оно лучше?
Ну, я про исключения - возвращение по стеку до ближайшего обработчика с сохранением какой-либо информации об исключительном собитии. А вы?
Я понимаю, компилятор/ИДЕ/анализатор может находить некоторое подмножество возможных исключений и показывать это программисту/помогать обрабатывать.
В этом был бы смысл, если бы было возможно найти ВСЕ везможные исключения метода/функции. Но это невозможно, см. примеры Интерфейса/Абстрактного класса/Рантайма новой версии/Плагина.
Значит либо программисту всё равно придётся лезть в документацию сторонних библиотек/свой код - и даже это не поможет в случае обновления рантайма/сторонних библиотек на исполняющей машине, либо он будет оперировать неполной (==ложной) информацией.
Что в итоге даёт нам одинаковый результат.
Вывод: "нинада". Перефразируя: лови, что можешь исправить и будь, что будет.
Примерно такой у меня ход мысли на этот счёт.
для этого ничего, кроме документации АПИ не нужно.
Да при чём тут хедер - скомпилировали с хедером версии 1.1, в рантайме доступна версия 1.2, на уровне сигнатур функций полностью совместимы, а список исключений изменился.
Никто ничего не факапит. Усложним: Программа предоставляет плагину некоторое окружение, в том числе функции/проперти, есть списки исключений. Система скомпилирована, версия 1.1. Третья контора написала под эту версию плагин, плагин свободно продаётся. У пользователя система версии 1.2, плагин скомпилированный под версию 1.1. АПИ бинарно совместимы, список исключений системы версии 1.2 отличается.
Что скомпилированный плагин может знать об исключениях рантайма новой версии?