крепостные объекты
От: Qulac Россия  
Дата: 07.03.24 09:24
Оценка: +1
В net можно прикрепить объект к памяти запретив сборщику мусора его перемещение. Пишут, что это может быть полезно для увеличения производительности приложения. А при каких сценариях работы с объектами это лучше использовать?
Программа – это мысли спрессованные в код
Re: крепостные объекты
От: Разраб  
Дата: 07.03.24 10:34
Оценка: 1 (1)
Здравствуйте, Qulac, Вы писали:

Q>В net можно прикрепить объект к памяти запретив сборщику мусора его перемещение. Пишут, что это может быть полезно для увеличения производительности приложения. А при каких сценариях работы с объектами это лучше использовать?


Если нужны гарантии

https://habr.com/ru/articles/725630/
☭ ✊ В мире нет ничего, кроме движущейся материи.
Re: крепостные объекты
От: fmiracle  
Дата: 07.03.24 10:53
Оценка: 1 (1) +1
Здравствуйте, Qulac, Вы писали:

Q>В net можно прикрепить объект к памяти запретив сборщику мусора его перемещение. Пишут, что это может быть полезно для увеличения производительности приложения. А при каких сценариях работы с объектами это лучше использовать?


Если у тебя есть взаимодействие с нативным кодом, которому ты хочешь отдать ссылку на этот объект, чтобы он дальше с ним работал напрямую у себя там. Если при этом сборщик мусора объект сдвинет в памяти — то фигня получится.
Re: крепостные объекты
От: rudzuk  
Дата: 07.03.24 10:58
Оценка: 1 (1)
Здравствуйте, Qulac, Вы писали:

Q> Пишут, что это может быть полезно для увеличения производительности приложения. А при каких сценариях работы с объектами это лучше использовать?


Передавать данные крепостного объекта в процедуру на C++.
avalon/3.0.2
Re: крепостные объекты
От: Sinclair Россия https://github.com/evilguest/
Дата: 07.03.24 11:34
Оценка: 31 (5)
Здравствуйте, Qulac, Вы писали:

Q>В net можно прикрепить объект к памяти запретив сборщику мусора его перемещение.

можно.
Q>Пишут, что это может быть полезно для увеличения производительности приложения.
Кто это пишет? Пожалуйста, если вы хотите обсудить некоторое утверждение, которое сделано кем-то, приведите ссылку на это утверждение. Без контекста понять, о чём именно идёт речь, бывает практически невозможно.
Q>А при каких сценариях работы с объектами это лучше использовать?
Например, вы делаете вызов в натив.
Натив ничего не знает про GC и типы дотнета. У вас есть два способа работать с нативом:
1. Выполнить выделение неуправляемой памяти; скопировать нужные вам данные в эту память при помощи GC-aware инструментов; выполнить вызов; освободить занимаемую память.
2. Передать в натив адрес управляемого объекта, полагаясь на побитовую совместимость используемых вами типов данных.

Второй подход, очевидно, выгоднее. Вместо того, чтобы копировать 500 мегабайт из дотнетового byte[] в неуправляемый буфер, вы сразу передаёте адрес нулевого элемента этого массива.
CLR гарантирует вам, что байты byte[] лежат подряд, поэтому никаких проблем на принимающей стороне возникнуть не должно.
Но! Нативный метод (вроде WriteFile) может работать долго. В это время запросто может проснуться GC и решить поперемещать ваши данные. GC работает в предположении, что он знает размещение всех ссылок на все живые объекты.
Поэтому он спокойно перемещает данные.
Даже если у вас работает цикл вроде
for(var i=0; i<arr.Length; i++)
  s += arr[i];

то посреди цикла GC может передвинуть arr в другое место в памяти — ну и что, на следующей итерации arr[i] прибавит смещение к новому адресу и ничего не сломается.
А вот про нативный код GC ничего не знает; переместив массив, он не может "залезть внутрь" нативного кода и подправить там ему указатель.
Поэтому перед вызовом нужно как-то сообщить GC, что двигать конкретно вот эти данные нельзя.
Это и делается при помощи "прикрепления" объекта.
Два основных способа:
1. Если вам действительно нужно держать данные неподвижно только на время одного вызова, то в C# есть специальное ключевое слово fixed. См. напр. здесь:
            fixed (byte* pinned = &MemoryMarshal.GetReference(buffer))
            {
                if (Interop.Kernel32.WriteFile(handle, pinned, buffer.Length, out int numBytesWritten, &overlapped) != 0)
                {
                    Debug.Assert(numBytesWritten == buffer.Length);
                    return;
                }

                int errorCode = FileStreamHelpers.GetLastWin32ErrorAndDisposeHandleIfInvalid(handle);
                throw Win32Marshal.GetExceptionForWin32Error(errorCode, handle.Path);
            }

Такой код не просто позволяет получить указатель на управляемый объект; он ещё и сообщает GC, что двигать эти данные нельзя до окончания блока, обозначенного ключевым словом fixed.
Если же вам нужно, чтобы какой-то кусок данных располагался неподвижно за пределами времени жизни конкретного фрейма стека (т.е. после выхода из вашей функции), то придётся велосипедить вручную.
Например, с асинхронным IO использовать простой fixed не получится — функция WriteAsync возвращает управление до того, как операционная система закончит работать с буфером.
Поэтому в дотнетовом IO асинхронная версия значительно сложнее.
Вот то место, где пришпиливается буфер:
            internal NativeOverlapped* PrepareForOperation(ReadOnlyMemory<byte> memory, long fileOffset, OSFileStreamStrategy? strategy = null)
            {
                Debug.Assert(strategy is null || strategy is AsyncWindowsFileStreamStrategy, $"Strategy was expected to be null or async, got {strategy}.");

                _result = 0;
                _strategy = strategy;
                _bufferSize = memory.Length;
                _memoryHandle = memory.Pin();
                _overlapped = _fileHandle.ThreadPoolBinding!.AllocateNativeOverlapped(_preallocatedOverlapped);
                if (_fileHandle.CanSeek)
                {
                    _overlapped->OffsetLow = (int)fileOffset;
                    _overlapped->OffsetHigh = (int)(fileOffset >> 32);
                }
                return _overlapped;
            }

Под капотом у ReadOnlyMemory<byte>.Pin() используется GCHandle.Alloc(..., GCHandleType.Pinned).

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

Ускорение происходит не за счёт самого пришпиливания как такового (как раз наоборот — GC не может свободно распоряжаться пришпиленной памятью, поэтому ему придётся делать всякие приседания, увеличивая расход памяти и время сборки мусора), а за счёт того, что можно отказаться от лишних копирований.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Отредактировано 07.03.2024 11:53 Sinclair . Предыдущая версия .
Re[2]: крепостные объекты
От: m2user  
Дата: 07.03.24 12:07
Оценка:
S>Пользоваться им напрямую я крайне не рекомендую. Сначала нужно очень хорошо разобраться, что это, в каких случаях его надо использовать, какие там ограничения и прочее. Неаккуратное применение пришпиливания помешает GC и вообще приведёт к нестабильной работе вашей программы.

ReadOnlyMemory появился только в свежих версиях .NET. А до этого всё спокойно делалось через GCHandle.Alloc(..., GCHandleType.Pinned).
И ничего сложного в нем нет, если побитовая совместимость гарантирована.

GCHandle gch = new GCHandle();
try {
 gch = GCHandle.Alloc(..., GCHandleType.Pinned);
 SomeExternalMethod(gch.AddrOfPinnedObject())
} finally {
 if (gch.IsAllocated){
  gch.Free();
 }
}


S>Ускорение происходит не за счёт самого пришпиливания как такового (как раз наоборот — GC не может свободно распоряжаться пришпиленной памятью, поэтому ему придётся делать всякие приседания, увеличивая расход памяти и время сборки мусора), а за счёт того, что можно отказаться от лишних копирований.


Ещё и код становится проще, т.к. не нужно вручную unmanaged память выделять/копировать/освобождать.
Отредактировано 07.03.2024 12:15 m2user . Предыдущая версия .
Re[3]: крепостные объекты
От: Sinclair Россия https://github.com/evilguest/
Дата: 07.03.24 15:33
Оценка:
Здравствуйте, m2user, Вы писали:

M>ReadOnlyMemory появился только в свежих версиях .NET. А до этого всё спокойно делалось через GCHandle.Alloc(..., GCHandleType.Pinned).

Ровно его же и использует ReadOnlyMemory.

M>И ничего сложного в нем нет, если побитовая совместимость гарантирована.


M>
M>GCHandle gch = new GCHandle();
M>try {
M> gch = GCHandle.Alloc(..., GCHandleType.Pinned);
M> SomeExternalMethod(gch.AddrOfPinnedObject())
M>} finally {
M> if (gch.IsAllocated){
M>  gch.Free();
M> }
M>}
M>

Зачем всё вот это, когда есть fixed? Он сделает ровно то же без лишних приседаний с finally.
Пинить руками нужно только тогда, когда Alloc и Free делается в разных местах.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[4]: крепостные объекты
От: m2user  
Дата: 07.03.24 15:58
Оценка:
S>Ровно его же и использует ReadOnlyMemory.

Да, но полезно знать решения для разных версий .NET.

S>Зачем всё вот это, когда есть fixed? Он сделает ровно то же без лишних приседаний с finally.


Очевидно:

You can use the fixed statement only in an unsafe context. The code that contains unsafe blocks must be compiled with the AllowUnsafeBlocks compiler option.


Поменять настройки проекта это не всегда доступная опция.
И вот это Ваше замечание к unsafe тоже вполне относится:

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

Re[5]: крепостные объекты
От: Sinclair Россия https://github.com/evilguest/
Дата: 08.03.24 03:32
Оценка:
Здравствуйте, m2user, Вы писали:
M>Очевидно:
M>

M>You can use the fixed statement only in an unsafe context. The code that contains unsafe blocks must be compiled with the AllowUnsafeBlocks compiler option.

а, ну так это — просто такой способ выстрелить себе в ногу. Код c прямыми манипуляциями GCHandle в реальности unsafe, просто он притворяется safe, обходя ограничения.
M>Поменять настройки проекта это не всегда доступная опция.
Запросто можно вынести сам вызов нативного кода вместе с fixed в отдельную сборку с произвольными настройками проекта, а на неё просто сослаться. Собственно, технически GCHandle устроен именно так.

M>И вот это Ваше замечание к unsafe тоже вполне относится:

M>

M>Сначала нужно очень хорошо разобраться, что это, в каких случаях его надо использовать, какие там ограничения и прочее.

Совершенно верно.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Отредактировано 08.03.2024 3:34 Sinclair . Предыдущая версия .
Re: крепостные объекты
От: VladD2 Российская Империя www.nemerle.org
Дата: 28.03.24 17:28
Оценка:
Здравствуйте, Qulac, Вы писали:

Q>В net можно прикрепить объект к памяти запретив сборщику мусора его перемещение. Пишут, что это может быть полезно для увеличения производительности приложения. А при каких сценариях работы с объектами это лучше использовать?


Что в этом может быть полезного для производительности? Это ведь ведет к фрагментации памяти. А так то да, один бит меняешь в заголовке объекта и он припинен и никуда не уедешь. Для интеропа самое то (на время вызова), а вот для производительности одни проблемы. Обычные долгоживущие объекты и так уезжают во второе поколение и перемещаются очень редко.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.