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: |

“[The defendant] could not shift his criminal liability,” Hui said. Co-founder of NFT renting protocol Rentable World emiliano.eth shared the group Tuesday morning on Twitter, calling out the "degenerate" community, or crypto obsessives that engage in high-risk trading. Over 33,000 people sent out over 1,000 doxxing messages in the group. Although the administrators tried to delete all of the messages, the posting speed was far too much for them to keep up. Telegram desktop app: In the upper left corner, click the Menu icon (the one with three lines). Select “New Channel” from the drop-down menu. The group’s featured image is of a Pepe frog yelling, often referred to as the “REEEEEEE” meme. Pepe the Frog was created back in 2005 by Matt Furie and has since become an internet symbol for meme culture and “degen” culture.
from us


Telegram C# Heppard
FROM American