Cow pages array
От: Hоmunculus  
Дата: 11.01.26 09:32
Оценка:
Кто-нибудь встречал хорошую реализацию cow массива со страничным хранением данных?
То есть если мы копируем массив в другой массив, то у нас никакого копирования данных не происходит. И начтнается копирование только отдельных страниц, если мы что-то в них поменяли (не всего массива, а именно измененных страниц).

Есть такое? Или свой велосипед городить?

Типа такого
Отредактировано 11.01.2026 9:50 Hоmunculus . Предыдущая версия .
Re: Cow pages array
От: sergii.p  
Дата: 13.01.26 09:48
Оценка: 4 (1)
Здравствуйте, Hоmunculus, Вы писали:

H>Есть такое? Или свой велосипед городить?


можно реализовать поверх QSharedData (как минимум detach не надо будет самому писать). Хотя всё равно придётся заморочиться с итераторами, конструкторами и пр.

constexpr int PageSize = 1024;

template<typename T>
struct Page : QSharedData
{
    Page() : data(PageSize) {}
    QVector<T> data;
};

template<typename T>
struct PagedArray
{
    explicit PagedArray(int size = 0)
        : m_size(size)
    {
        const int pageCount = (size + PageSize - 1) / PageSize;
        m_pages.resize(pageCount);
        for (int i = 0; i < pageCount; ++i)
            m_pages[i] = QSharedDataPointer<Page<T>>(new Page<T>());
    }

    int size() const noexcept { return m_size; }

    const T& operator[](int index) const
    {
        int pageIndex = index / PageSize;
        int offset = index % PageSize;
        return m_pages[pageIndex]->data[offset];
    }

    T& operator[](int index)
    {
        int pageIndex = index / PageSize;
        int offset = index % PageSize;

        if (m_pages[pageIndex].data()->ref.loadRelaxed() > 1)
            m_pages[pageIndex].detach();

        return m_pages[pageIndex]->data[offset];
    }

    void resize(int newSize)
    {
        const int newPageCount = (newSize + PageSize - 1) / PageSize;
        m_pages.resize(newPageCount);
        for (int i = 0; i < newPageCount; ++i)
        {
            if (!m_pages[i])
                m_pages[i] = QSharedDataPointer<Page<T>>(new Page<T>());
        }
        m_size = newSize;
    }

private:
    int m_size;
    QVector<QSharedDataPointer<Page<T>>> m_pages;
};
Re[2]: Cow pages array
От: SaZ  
Дата: 13.01.26 11:41
Оценка:
Здравствуйте, sergii.p, Вы писали:

SP>Здравствуйте, Hоmunculus, Вы писали:


H>>Есть такое? Или свой велосипед городить?


SP>можно реализовать поверх QSharedData (как минимум detach не надо будет самому писать). Хотя всё равно придётся заморочиться с итераторами, конструкторами и пр.


SP>
SP>constexpr int PageSize = 1024;

SP>template<typename T>
SP>struct Page : QSharedData
SP>{
SP>    Page() : data(PageSize) {}
SP>    QVector<T> data;
SP>};

SP>template<typename T>
SP>struct PagedArray
SP>{
SP>    explicit PagedArray(int size = 0)
SP>        : m_size(size)
SP>    {
SP>        const int pageCount = (size + PageSize - 1) / PageSize;
SP>        m_pages.resize(pageCount);
SP>        for (int i = 0; i < pageCount; ++i)
SP>            m_pages[i] = QSharedDataPointer<Page<T>>(new Page<T>());
SP>    }

SP>    int size() const noexcept { return m_size; }

SP>    const T& operator[](int index) const
SP>    {
SP>        int pageIndex = index / PageSize;
SP>        int offset = index % PageSize;
SP>        return m_pages[pageIndex]->data[offset];
SP>    }

SP>    T& operator[](int index)
SP>    {
SP>        int pageIndex = index / PageSize;
SP>        int offset = index % PageSize;

SP>        if (m_pages[pageIndex].data()->ref.loadRelaxed() > 1)
SP>            m_pages[pageIndex].detach();

SP>        return m_pages[pageIndex]->data[offset];
SP>    }

SP>    void resize(int newSize)
SP>    {
SP>        const int newPageCount = (newSize + PageSize - 1) / PageSize;
SP>        m_pages.resize(newPageCount);
SP>        for (int i = 0; i < newPageCount; ++i)
SP>        {
SP>            if (!m_pages[i])
SP>                m_pages[i] = QSharedDataPointer<Page<T>>(new Page<T>());
SP>        }
SP>        m_size = newSize;
SP>    }

SP>private:
SP>    int m_size;
SP>    QVector<QSharedDataPointer<Page<T>>> m_pages;
SP>};
SP>


Вы только забыли про то, что QSharedData изначально спроектирован с прицелом на многопоточность. А в такой примерной реализации нужно будет позаморачиваться.
Re: Cow pages array
От: Chorkov Россия  
Дата: 13.01.26 14:23
Оценка: 4 (1)
Здравствуйте, Hоmunculus, Вы писали:

H>Кто-нибудь встречал хорошую реализацию cow массива со страничным хранением данных?

H>То есть если мы копируем массив в другой массив, то у нас никакого копирования данных не происходит. И начтнается копирование только отдельных страниц, если мы что-то в них поменяли (не всего массива, а именно измененных страниц).

H>Есть такое? Или свой велосипед городить?


H>Типа такого

H>Image: fff.png

Я бы использовал std::make_shared + std::shared_ptr (вместо QSharedDataPointer).

Чтобы избежать лишней косвенности на странице, можно использовать boost::container::static_vector или std::inplace_vector (С++26).

Оператор [], по возможности, оставил бы только для чтения. Иначе очень легко вместо чтения вызвать клонирование данных.

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

std::views::join — для итераторов.
Это приводит к тому, что итераторы не хранят в себе умных указателей и нет счетчика живых итераторов.
Поэтому все что можно — ввести правила инвалидки итераторов (при вызове любой функции, потенциально могущей изменить содержимое).

Примерно так:

template<typename SubContainer>
class shared_container
{
public:
    shared_container() = default; //  : ptr( std::make_shared<SubContainer>()) {}
    shared_container(const shared_container&) = default;
    shared_container(shared_container&&) = default;
    shared_container& operator = (const shared_container&) = default;
    shared_container& operator = (shared_container&&) = default;

    const SubContainer& const_view() const { return *ptr; }

    SubContainer& detach() {
        if(ptr.use_count()>1)
            ptr = std::make_shared<SubContainer>(*ptr);
        return *ptr;
    }

    auto size   () const                    { return const_view().size(); }

    const auto& operator[] (auto i) const   { return const_view()[i]; }
    const auto& at (auto i) const           { return const_view().at(i); }
    auto begin  () const                    { return const_view().begin(); }
    auto end    () const                    { return const_view().end(); }

    auto& operator[] (auto i)               { return detach()[i]; }
    auto& at (auto i)                       { return detach().at(i); }
    auto begin  ()                          { return detach().begin(); }
    auto end    ()                          { return detach().end(); }

private:
    std::shared_ptr< SubContainer > ptr = std::make_shared<SubContainer>();
};

template<typename T,
         size_t page_size = std::max( (size_t) (1024*16)/sizeof(T), (size_t) 4) >
class page_vector
{
    static_assert(page_size > 1 );
    using t_page = shared_container< boost::container::static_vector<T, page_size> >;
    std::vector<t_page> data;

    using t_r_range = decltype( std::declval< const std::vector<t_page>& >()  | std::views::join );
    using t_rw_range = decltype( std::declval< std::vector<t_page>& >()  | std::views::join );

    mutable std::optional< t_r_range > r_range;
    mutable std::optional< t_rw_range > rw_range;

public:
    page_vector() = default;
    page_vector(const page_vector&) = default;
    page_vector(page_vector&&) = default;

    page_vector& operator = (const page_vector& p) { data = p.data; invalidate_iterators();  return *this; }
    page_vector& operator = (page_vector&& p) { data = std::move(p.data); invalidate_iterators();  return *this; }

    const T& operator [] (size_t i) const { return data[i/page_size][i%page_size]; }
    const T& at (size_t i) const { return data.at(i/page_size).at(i%page_size); }

    auto begin() const { return const_range().begin(); }
    auto end  () const { return const_range().end(); }

    void assign(size_t i, T&& v) // invalidate iterators
    {
        data.at( i/page_size ).detach().at(i%page_size) = std::move(v);
        invalidate_iterators();
    }

    void push_back( auto&& v ) // invalidate iterators
    {
        if(data.empty() || data.back().size() == page_size )
            data.push_back({});
        data.back().detach().push_back(std::move(v));
        invalidate_iterators();
    }

    auto& const_range() const
    {
        if(!r_range)
            r_range = data | std::views::join;
        return *r_range;
    }
    auto& writable_range()
    {
        for(auto& p :data)
            p.detach();
        invalidate_iterators();

        rw_range = data | std::views::join;
        return *rw_range;
    }

private:
    void invalidate_iterators()
    {
        r_range.reset();
        rw_range.reset();
    }
};


int main()
{
    page_vector<int, 8 > data;
    for(int i=100; i<111; ++i)
        data.push_back(i);

    for(auto i : data)
        std::cout<<i<<std::endl;

    for(int& j : data.writable_range())
        j+=200;

    for(auto i : data)
        std::cout<<i<<std::endl;

    return 0;
}
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.