CSHARP_GEPARD Telegram 122
Работа с ArrayPool и MemoryPool #память

Пул массивов - классная штука, но есть проблема. И даже две.

Первая проблема. Когда мы пытаемся получить ArrayPool<T>.Shared.Rent массив размером, допустим, в 4 элемента, мы получаем массив размером в 16 элементов. Если запросим 16, то получим 16, а вот если нам нужно 17 элементов, то мы получим аж 32. Таким образом, при запросе массива, мы всегда получаем массив размером не менее нужного. Это сделано специально, чтобы не аллоцировать большое количество массивов разного размера.

Соответственно, при попытке итерироваться по полученному массиву, мы можем столкнуться с пустыми элементами, либо элементами, которые содержат предыдущие данные (очистка массива перед возвратом в пул это право, а не обязанность потребителя).

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

var pool = ArrayPool<int>.Shared;
var array = pool.Rent(length);
var segment = new ArraySegment<int>(array, 0, length);


Теперь мы можем передавать ArraySegment в нужные методы, и избавить потребителей от необходимости знать о том, какого же размера массив был запрошен и необходим.

Во-вторых, мы можем воспользоваться Span. Это отличный вариант, когда мы передаём кусочек массива в методы, которые не являются async. Напомню, нам запрещено работать с ref struct в асинхронных методах.


var span = array.AsSpan(..length);


В-третьих, можно использовать Memory - структурой, которая так же как и ArraySegment, указывает на область памяти. Конечно, при данном подходе, логичнее использовать не ArrayPool, а MemoryPool, который работает примерно так же, как и ArrayPool.

var pool = MemoryPool<int>.Shared;
using var memoryOwner = pool.Rent(length);
var memory = memoryOwner.Memory[..length];


Memory это обычная структура, а значит нет никаких проблем передавать её в async методы и не думать в них о том, какой же реальный размер памяти мы используем. Напомню, что взаимодействие с Memory осуществляется через Span (memory.Span).

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

var pool = ArrayPool<int>.Shared;
var array = pool.Rent(length);

DoSomething(array.AsSpan(..length));

pool.Return(array);


Если мы обратим внимание на использование MemoryPool, то он возвращает не Memory, а IMemoryOwner, что как бы намекает: есть владелец памяти, а есть методы, которые память используют. Передавая в методы использования и Memory, и IMemoryOwner’a, мы делаем утверждение, что теперь другой метод является владельцем области памяти, а значит именно он ответственен за её очистку.

var pool = MemoryPool<int>.Shared;
var memoryOwner = pool.Rent(length);
var memory = memoryOwner.Memory[..length];

DoSomething(memory, memoryOwner);


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

Другие способы (ArraySegment или Span) такой конструкцией не обладают, и мы должны самостоятельно придумывать костыли для возврата массива в пул. Например, можно написать микс ArraySegment’a с IMemoryOwner, который обладает возможностью возврата массива в пул при вызове Dispose. Его код будет в комментариях. А можно ещё и вот так.

Используя подобный велосипед, вы сможете передавать в методы-потребители структуру PooledArray<T> и в нужном месте вызывать Dispose, который вернёт массив в пул. В принципе, весьма неплохое решение. Если смотреть на бенчмарк всего сценария, то получается даже быстро.
👍193



tgoop.com/csharp_gepard/122
Create:
Last Update:

Работа с ArrayPool и MemoryPool #память

Пул массивов - классная штука, но есть проблема. И даже две.

Первая проблема. Когда мы пытаемся получить ArrayPool<T>.Shared.Rent массив размером, допустим, в 4 элемента, мы получаем массив размером в 16 элементов. Если запросим 16, то получим 16, а вот если нам нужно 17 элементов, то мы получим аж 32. Таким образом, при запросе массива, мы всегда получаем массив размером не менее нужного. Это сделано специально, чтобы не аллоцировать большое количество массивов разного размера.

Соответственно, при попытке итерироваться по полученному массиву, мы можем столкнуться с пустыми элементами, либо элементами, которые содержат предыдущие данные (очистка массива перед возвратом в пул это право, а не обязанность потребителя).

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

var pool = ArrayPool<int>.Shared;
var array = pool.Rent(length);
var segment = new ArraySegment<int>(array, 0, length);


Теперь мы можем передавать ArraySegment в нужные методы, и избавить потребителей от необходимости знать о том, какого же размера массив был запрошен и необходим.

Во-вторых, мы можем воспользоваться Span. Это отличный вариант, когда мы передаём кусочек массива в методы, которые не являются async. Напомню, нам запрещено работать с ref struct в асинхронных методах.


var span = array.AsSpan(..length);


В-третьих, можно использовать Memory - структурой, которая так же как и ArraySegment, указывает на область памяти. Конечно, при данном подходе, логичнее использовать не ArrayPool, а MemoryPool, который работает примерно так же, как и ArrayPool.

var pool = MemoryPool<int>.Shared;
using var memoryOwner = pool.Rent(length);
var memory = memoryOwner.Memory[..length];


Memory это обычная структура, а значит нет никаких проблем передавать её в async методы и не думать в них о том, какой же реальный размер памяти мы используем. Напомню, что взаимодействие с Memory осуществляется через Span (memory.Span).

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

var pool = ArrayPool<int>.Shared;
var array = pool.Rent(length);

DoSomething(array.AsSpan(..length));

pool.Return(array);


Если мы обратим внимание на использование MemoryPool, то он возвращает не Memory, а IMemoryOwner, что как бы намекает: есть владелец памяти, а есть методы, которые память используют. Передавая в методы использования и Memory, и IMemoryOwner’a, мы делаем утверждение, что теперь другой метод является владельцем области памяти, а значит именно он ответственен за её очистку.

var pool = MemoryPool<int>.Shared;
var memoryOwner = pool.Rent(length);
var memory = memoryOwner.Memory[..length];

DoSomething(memory, memoryOwner);


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

Другие способы (ArraySegment или Span) такой конструкцией не обладают, и мы должны самостоятельно придумывать костыли для возврата массива в пул. Например, можно написать микс ArraySegment’a с IMemoryOwner, который обладает возможностью возврата массива в пул при вызове Dispose. Его код будет в комментариях. А можно ещё и вот так.

Используя подобный велосипед, вы сможете передавать в методы-потребители структуру PooledArray<T> и в нужном месте вызывать Dispose, который вернёт массив в пул. В принципе, весьма неплохое решение. Если смотреть на бенчмарк всего сценария, то получается даже быстро.

BY C# Heppard


Share with your friend now:
tgoop.com/csharp_gepard/122

View MORE
Open in Telegram


Telegram News

Date: |

Done! Now you’re the proud owner of a Telegram channel. The next step is to set up and customize your channel. To view your bio, click the Menu icon and select “View channel info.” The best encrypted messaging apps In handing down the sentence yesterday, deputy judge Peter Hui Shiu-keung of the district court said that even if Ng did not post the messages, he cannot shirk responsibility as the owner and administrator of such a big group for allowing these messages that incite illegal behaviors to exist. Administrators
from us


Telegram C# Heppard
FROM American