Как стать автором
Обновить

Комментарии 31

статью не стал читать. когда не увидел в начале объяснение. а зачем это делать?

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

Адаптировав это под Unity - может быть они будут рады, дополнительно выжав 0.7 фпс.

В остальном это нельзя пускать в прод.

upd ахда, ниже уже пояснили за Unity. Ну тогда просто для самообразования и понимания как устроена аллокация.

Ну например, чтобы не тратить много наносекунд на аллокацию object[] для использования в MethodInfo.Invoke().

https://gist.github.com/BadRyuner/602a94b1f3b4e054258dbec165f24d68

например если ты разрабатываешь игру на юньке или Годот с использованием с# и хочешь получить лишние кадры в секунду

В Unity такой хак не сработает из-за специфики работы Mono и их GC. Как минимум, я не смог адаптировать пока что код под Unity. Зависит от нужд проекта. Есть и более разумные решения, как пуллинг, ECS и адекватный Unsafe код, чтобы делать проекты, для которых не хватает стандартных средств .NET.

понял, под оптимизации для mono будет отдельный пост ? 8_)
я подписан :D

Думаю над этим. Если смогу написать достаточно материала - обязательно опубликую :)

Спасибо за статью, у меня как раз неделя структур/классов и их кухни

Мсье знает толк...
Мое уважение

Спасибо за статью!

Это всё интересно, но что будет, если такой вот экземпляр "класса" будет содержать поле ссылочного типа, а не как в статье - только поля значимых типов? Как это будет жить со сборкой мусора (поймет ли GC, что это поле - корень: не сочтет ли он, что экземпляр по ссылке - мусор, если других ссылок не будет, что что произойдет если GC переместит экземпляр по ссылке в другое место, и так далее). А если учесть, что GC может захотеть поработать в любой момент, то меня тут терзают смутные сомнения, что этим экземпляром со ссылочным типом вообще можно будет хоть как-то пользоваться (закреплять то, что по ссылке, разве что).

Вопрос открытый. На мой взгляд, так как аллокация происходит вне управляемой кучи, GC не должен обращать на нее никакого внимания. Но так ли - я не могу гарантировать, необходимо еще более глубокое исследование, которое уже, как я считаю, имеет мало смысла, ибо какого-то большого смысла для разработки такой подход не имеет, и сомневаюсь, что окупиться разбор всего нативного кода CLR.
Однако, кто знает, когда мне станет скучно и, быть может, у статьи будет продолжение с более глубокими ответами и примерами :D

Нет, нужно явно указать GC что это куча, пусть и не управляемая. Для этого есть внутренние апишки, можете глянуть пример тут https://twitter.com/EgorBo/status/1706316706316996947
К тому же, вы не можете в таких объектах хранить ссылки на объекты из нормальной кучи, только из вашей же неуправляемой, как минимум потому что GC не будет ее сканить

Построение графа зависимостей подразумевает, что экземпляр может быть в других кучах или вообще залочен в неуправляемой памяти, поэтому все поля будут валидны и не собраны, более того, ссылка на сам объект будет учитываться в поколении. Однако, не факт, что будет вызван финализатор или IDispose, так как для них требуется сначала перепланировка кучи, поэтому следует использовать IDispose интерфейс во внешнем классе для связи с GC. И так же пинить сам объект, чтобы исключить ошибки планировщика кучи при попытке перераспределить память.

Я таки не понял, причем тут IDisposable или финализатор. У меня-то вопрос был простой: сможет ли GC обработать ссылки на другие объекты в куче из такого, нестандартно созданного, объекта. То есть, будет ли считать эти ссылки корнями при обходе графа зависимостей и будет ли модифицировать эти ссылки при перемещении объекта, на который эти ссылки ссылаются. в куче. И связаны у меня с этим большие сомнения.

Должен, GC берёт ссылки из полей корней, данные где расположены ссылки берутся из мета-информации, которую поставляет таблица методов. В искусственном объекте есть таблица методов с корректной мета-информацией, значит GC распознает ссылки, так же все отметки достижимости и закрепления хранятся в самом объекте, и тоже будут валидны. Но сам объект должен иметь внешние ссылки, чтобы GC его вообще видел или быть закреплённым. Однако, GC ещё занимается планированием кучи, и может посчитать, что объект находится в куче и ложно копировать его при уплотнении, поэтому всегда стоит использовать закрепление. В простом случае, можно использовать класс обёртку, который автоматический убирает закрепление при финализации объекта. Я не уверен, что GC обязательно попытается его скопировать, но он однозначно распознает ссылки внутри. Этот метод описан достаточно давно и сборка мусора не вызывает ни каких повреждений. Правда, все ситуации синтетические.

Лично мне интересно попробовать использовать этот метод для более тщательной разметки памяти.

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

Почему objectHeader находится по адресу -8? Зачем так сделано? Хз. Даже майкрософт говорили, что просто так исторически сложилось и никакого скрытого смысла здесь нет. 

Смысл здесь логический, так как классы управляемые, objectHeader - это как раз обёртка, а по указателю что должно лежать? правильно мякотка, и никому кроме обработчика в частных случаях обёртка не нужна. Да и регулярно добавлять +8 к любому обращению к классу, который в жизни обработкой заниматься не будет - такое себе. В то время как та же таблица методов уже является частью реализации и жертвой постоянных обращений от всех владельцев ссылки.

Интересное замечание, буду иметь в виду на будущее, благодарю.

Уж лучше Кокосу почитайте...

Кайф, импорт системных библиотек, инвоки в ассортименте, всякий треш в сторонних либах, etc.

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

Именно. Не стоит в продакшен продукте ломать правила и делать такой код. Все таки пара миллисекунд не стоит таких рисков. Если у вас и возникает проблемы с аллокацией классов - более чем уверен, что проблема в архитектуре вашего продукта и в том, что вы сами выбрали использование классов :)

Простите, возможно я был не внимателен, но на чем основывается этот вывод в статье?:

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

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

Не то чтобы сильно затратный. В общем случае - это просто бамп поинтера с установкой пары полей

В основном, вы правы, но есть нюнас: чтобы сделать "бамп поинтера" (IMHO лучше писать по-русски - "передвинуть указатель") нужно, чтобы было его куда передвинуть. То есть, память свободная для этого нужна. А свободная память (если речь про кучу) в .NET появляется в результате процесса сборки мусора (памяти, занятой неиспользуемыми объектами), который как раз и есть очень сложный и сильно затратнее. В худшем случае распределителю памяти .NET приходится выолнять сборку мусора прямо в момент выполнения запроса на размещение экземпляра класса, но, как хорошо известно, обычно распределитель памяти старается до этого не доводить и выполняет сборку заранее, по достижении некоего предела наличия свободной памяти. Однако затраты от этого никуда не деваются - просто переносятся на более ранний срок. Именно поэтому, по совкупности всего того, что он неизбежно влечет, и говорится, что процесс размещения объекта в куче - сложный и затратный.

Ок, для меня акцент, что это все на стеке затерялся в деталях описания структуры хедера объекта, да и выше стек-куча особенно не делили:

В примере указан вариант со аллокацией на стеке, но его можно заменить на malloc() или Marshal.AllocHGlobal(). В таком случае надо не забыть потом вызвать Free()

Немного не понимаю претензии. В статье с разбором того, как и из чего состоит экземпляр класса, чему я должен был уделить внимания больше?
Я привел много информации, которая нужна, чтобы понять, что делает CLR при создании экземпляра, а так же, как можно воссоздать самим этот процесс.
Далее уже на ваше усмотрения, где и как аллоцировать. Хоть кастомным аллокатором, хоть через new byte[], хоть по шагам разбирать код CLR и детально воссоздать их алгоритмы.

Unsafe.InitBlock(memory, 0, 8 * 8); // Очищаем память, ибо она грязная  

Можно этого не делать. Дотнет по умолчанию инициализирует нулями стековые массивы, выделенные с помощью stackalloc. В дотнете все наоборот: чтобы массив был наполнен неопределенными байтами, нужно в декларации метода указать атрибут [SkipLocalsInit]. Есть также вариант где-то в модуле указать [module: SkipLocalsInit].

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории