Проблема с использованием unsynchronized_pool_resource для выделения pmr::vector

Следуя аналогичному вопросу несколько месяцев назад <[комбинация монотонного буфера и несинхронизированного пула памяти]https://stackoverflow.com/questions/77271609/c17-combination-of-monotonic-buffer-and-unsynchronized-memory-pool >, я пытаюсь объединить ресурс монотонного буфера и несинхронизированного пула, чтобы эффективно распределять память без необходимости использовать кучу для выделения памяти.

#include <iostream>
#include <memory_resource>
#include <vector>
class A{
    char array[64];
};
typename std::aligned_storage<32000>::type storage;
thread_local std::pmr::monotonic_buffer_resource bufferPool{&storage,sizeof(storage),std::pmr::null_memory_resource()};
std::pmr::unsynchronized_pool_resource pool{{},&bufferPool};

int main()
{

    for(int i =0; i < 10000;++i){
    
        
        std::pmr::vector<A> vec (&pool);
        vec.reserve(1000);
        for(int j =0; j < 1000;++j){
            vec.emplace_back();
        }
    }
}

На основании предыдущего ответа ожидается, что блок памяти вернется в пул и будет повторно использован для следующего распределения. Но этого не происходит. В следующих выделениях снова перейдите к монотонному буферу, который ведет себя монотонно, и после нескольких итераций заканчивается память. Что мне не хватает? И почему приведенный ниже пример работает и повторно использует одну и ту же память на каждой итерации?

#include <iostream>
#include <memory_resource>

int main() {
    char buffer[1024];
    std::pmr::monotonic_buffer_resource monotonicResource(buffer, sizeof(buffer));

    // Use an unsynchronized_pool_resource on top of the monotonic_buffer_resource
    std::pmr::unsynchronized_pool_resource poolResource(&monotonicResource);

    // Allocate and deallocate memory using the unsynchronized_pool_resource
    for (int i = 0; i < 5; ++i) {
        std::cout << "Iteration " << i << ":\n";

        // Allocate memory
        void* ptr = poolResource.allocate(200);  // request 200 bytes from the pool
        std::cout << "Allocated at: " << ptr << "\n";

        // Deallocate memory (returning it to the pool! Not the monotonic resource)
        poolResource.deallocate(ptr, 200);

        std::cout << "Deallocated.\n\n";
    }

    return 0;
}

В чем разница между ними?

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
0
66
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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

  1. он округляет выделение до следующей степени 2, выделение 1025 байт означает, что вы выделяете 2048 байт.
  2. он выделяет N блоков этого размера за раз, где N по умолчанию равно 8 в MSVC, это контролируется через pool_options , поэтому в приведенном выше примере, если вы запрашиваете 1025 байт, он выделяет как минимум 16 КБ, он также выделяет дополнительная память для отслеживания и управления, поэтому добавьте дополнительные 2 КБ в качестве запаса погрешности .... чтобы простое выделение 1 КБ превратилось в выделение 18 КБ, это преувеличено, но не нереально.

Распределитель работает, но для выделения 64 КБ памяти на MSVC он запрашивает около 720 КБ памяти, чего не может удовлетворить небольшой буфер размером 32 КБ.

Решение действительно простое: не используйте его для больших векторов, вы можете повторно использовать векторную память без использования pmr::allocator, просто поместив ее за пределы цикла for или как часть класса.

std::vector<A> vec;
vec.reserve(1000);
for (int i = 0; i < 10000; ++i) {
    vec.clear();
    for (int j = 0; j < 1000; ++j) {
        vec.emplace_back();
    }
}

Эти pmr распределители более полезны для небольших распределений, таких как map или unordered_map или list, где каждый раз, когда вы выделяете небольшие узлы постоянного размера, это также полезно для небольших векторов, около 10 элементов.

Если вы все еще хотите использовать его для большого вектора, вам нужно установить max_blocks_per_chunk в pool_options на 1 и позволить распределителю извлекать память из неограниченной глобальной кучи, потому что вы не можете заранее знать, сколько памяти он будет использовать. нуждаться.

Кроме того, gcc/clang по умолчанию имеет 4 МБ / 1 МБ largest_required_pool_block, поэтому вам нужно указать, что вам нужно больше выделений в pool_options, если вам нужны большие выделения.

std::pmr::monotonic_buffer_resource bufferPool{};
std::pmr::unsynchronized_pool_resource pool{std::pmr::pool_options{1,1024*1024*1024},&bufferPool};

Даже после того, как он настроен на выделение только 1 блока на фрагмент, он выделяет 140 КБ, чтобы удовлетворить выделение 64 КБ, не пытайтесь заранее оценить объем памяти, который вам понадобится, вы можете выделить небольшой буфер в стеке для обработки небольших выделения для небольших векторов (например, 10 КБ), но вы должны разрешить ему вернуться к глобальной куче для больших векторов.

Модифицированный код godbolt.org/z/83We9zd6Y. В каждом цикле используются одни и те же адреса, за исключением двух последних элементов. Почему это происходит. Кажется, это работает только для 10 элементов, а размер монотонного буфера составляет 4 КБ. Огромная трата памяти. @Ахменд АЕК

getsoubl 17.08.2024 08:40

@getsoubl вам нужно увеличить largest_required_pool_block, так как по умолчанию в gcc установлено 4 МБ, и вам нужен гораздо больший буфер для больших выделений, как я сказал в ответе, это расточительно, потому что оно оптимизировано для небольших выделений, а не для больших выделений ️ 🔁 godbolt.org/z/rsr85v88n

Ahmed AEK 17.08.2024 09:44

@getsoubl largest_required_pool_block должен быть больше, чем весь векторный блок, т.е. вектору из 1000 элементов потребуется блок размером 8 КБ, поэтому largest_required_pool_block должен быть больше 8 КБ... но емкость вектора не будет равна точно 1000, потому что его емкость удваивается каждый раз, когда он растет, поэтому вам, вероятно, потребуется largest_required_pool_block, чтобы он был не менее 16 КБ.

Ahmed AEK 17.08.2024 09:49

@Ahmend AEK, почему самый большой_требуемый_пул_блок должен быть больше, чем весь вектор? Разве каждый блок на фрагмент не содержит один элемент вектора? Я ожидаю, что 64 байта на блок будет в порядке. godbolt.org/z/e1r9Tdc4d. Что мне не хватает? Я уменьшаю выделение до 10. В последнем примере адрес монотонного буфера повторно не используется.

getsoubl 17.08.2024 13:23

@getsoubl распределитель видит весь вектор как один блок (поскольку вектор ДОЛЖЕН быть смежным, в отличие от list или map, которые могут быть фрагментированы), вектор запрашивает у распределителя один блок size(A) * number_of_elements, распределитель думает, что вы выделяете один большой объект такого размера.

Ahmed AEK 17.08.2024 13:44

Другие вопросы по теме

Почему std::coroutine_handle ссылается только на сопрограмму (через необработанный указатель), а не владеет ею (через std::unique_ptr)?
Когда пользовательский процесс заменяет страницу, находится ли виртуальный адрес страницы в пространстве пользователя или пространстве ядра?
Почему компилятор Rust удаляет неиспользуемые переменные в порядке, обратном их объявлению?
Проблема стирания элементов в векторе с помощью C
Могу ли я доказать монотонность ассигнований с помощью Rust Borrow Checker
Почему нам следует устанавливать для -XX:InitialRAMPercentage и -XX:MaxRAMPercentage одно и то же значение для облачной среды?
Есть ли что-то вроде указателя для массивов numpy?
C Могу ли я использовать данные, которые я освободил сразу после освобождения?
Веб-скрапинг в R с использованием циклов while. Ошибка в open.connection(x, «rb»): ошибка HTTP 429, когда веб-страница существует
Как обрабатывать данные о ценах на криптовалюту и токены в режиме реального времени на сервере, память моего сервера через некоторое время переполняется