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

According to media reports, the privacy watchdog was considering “blacklisting” some online platforms that have repeatedly posted doxxing information, with sources saying most messages were shared on Telegram. How to build a private or public channel on Telegram? Ng Man-ho, a 27-year-old computer technician, was convicted last month of seven counts of incitement charges after he made use of the 100,000-member Chinese-language channel that he runs and manages to post "seditious messages," which had been shut down since August 2020. To view your bio, click the Menu icon and select “View channel info.” Just at this time, Bitcoin and the broader crypto market have dropped to new 2022 lows. The Bitcoin price has tanked 10 percent dropping to $20,000. On the other hand, the altcoin space is witnessing even more brutal correction. Bitcoin has dropped nearly 60 percent year-to-date and more than 70 percent since its all-time high in November 2021.
from us


Telegram C# Heppard
FROM American