std::vector的性能不佳是因为没有调用realloc函数的对数次数吗?

6

编辑:我添加了另外两个基准测试,以比较使用realloc和C数组以及reserve()和std::vector的情况。从最后的分析来看,realloc即使只调用30次也会产生很大影响。检查文档,我猜这是因为realloc可能会返回一个全新的指针,复制旧的指针。

为了完整地描述情况,我还添加了在初始化期间完全分配数组的代码和图表。与reserve()相比,区别是显著的。

编译标志:仅使用图表中描述的优化,使用g++编译而无需其他内容。

原始问题:

当我添加10亿个整数时,我对std::vector和new/delete数组进行了基准测试,第二个代码比使用向量的速度快得多,特别是在打开优化时。

我怀疑这是由于向量内部调用realloc太多次所致。如果向量在填满时没有每次将其大小加倍增长(这里数字2并没有什么特殊之处,重要的是它的大小呈几何级数增长),则会出现这种情况。

在这种情况下,对realloc的调用将仅为O(log n),而不是O(n)

如果这就是第一个代码变慢的原因,那么我该如何告诉std::vector按几何级数增长?

请注意,在此情况下只调用一次reserve将起作用,但在不预先知道push_back数量的更一般情况下则不会起作用。

enter image description here

黑色线条

#include<vector>

int main(int argc, char * argv[]) {
    const unsigned long long size = 1000000000;

    std::vector <int> b(size);
    for(int i = 0; i < size; i++) {
        b[i]=i;
    }    
    return 0;
}

蓝线

#include<vector>

int main(int argc, char * argv[]) {
    const int size = 1000000000;    
    std::vector <int> b;
    for(int i = 0; i < size; i++) {
        b.push_back(i);
    }    

    return 0;
}

绿色线

#include<vector>

int main(int argc, char * argv[]) {
    const int size = 1000000000;
    std::vector <int> b;
    b.reserve(size);
    for(int i = 0; i < size; i++) {
        b.push_back(i);
    }    

    return 0;
}

red line

int main(int argc, char * argv[]) {
    const int size = 1000000000;
    int * a = new int [size];
    for(int i = 0; i < size; i++) {
        a[i] = i;
    }
    delete [] a;   
    return 0;
}

橙色线路
#include<vector>

int main(int argc, char * argv[]) {
    const unsigned long long size = 1000000000;
    int * a = (int *)malloc(size*sizeof(int));
    int next_power = 1;
    for(int i = 0; i < size; i++) {
        a[i] = i;
        if(i == next_power - 1) {
            next_power *= 2;
            a=(int*)realloc(a,next_power*sizeof(int));
        }
    }
    free(a);
    return 0;
}

在这里输入图片描述

编辑:根据建议,检查.capacity()后,我们发现增长确实是指数级的。那么为什么vector如此缓慢呢?


2
它总是几何的。但在这种情况下,即使是几何的复制也很昂贵。 - llllllllll
5
如果你不知道要 reserve() 多少空间,那么如何确定要使用多少 new[] - Bo Persson
5
你正在比较不相干的事情。如果你想让代码具有相同的行为,你需要使用 b.reserve(size),它基本上与 int * a = new int [size]; 相同。 - NathanOliver
3
如果您正在使用clang编译器,那么您的C语言示例将被编译成空代码 - 编译器会删除所有代码。 - user1143634
3
一件事:据我所知,由于分配器使用的要求,向量不允许使用 realloc。我猜想在理论上,可以为 POD 类型的默认分配器进行特殊化,但我不认为任何标准库实现会这样做。更公平的比较应该是与 mallocmemcpyfree 进行比较。 - Justin
显示剩余24条评论
5个回答

16

优化的C语言数组本身并没有被优化。

在godbolt上

xorl %eax, %eax
retq

那就是程序。
每当您将程序优化到几乎为0时,您应该考虑这种可能性。
优化器看到您对分配的内存没有做任何事情,注意到未使用的分配内存可能没有任何副作用,并消除了分配。
而写入内存然后从未读取它也没有任何副作用。
相比之下,编译器很难证明向量的分配是无用的。可能编译器开发人员可以教它识别未使用的std向量,就像它们识别未使用的原始C数组一样,但在我的经验中,这种优化确实是一个角落案例,并且会导致许多问题进行分析。
请注意,任何优化级别下的vector-with-reserve基本上与未优化的C风格版本的速度相同。
在C风格代码中,唯一需要优化的是“什么也不要做”。在向量代码中,未优化的版本充满了额外的堆栈帧和调试检查,以确保您不越界(如果确实如此,则崩溃干净)。
请注意,在Linux系统上,分配大块内存除了操纵虚拟内存表之外并没有任何作用。只有当内存被触摸时,它才会为您找到一些零化物理内存。
没有保留,std向量必须猜测一个初始小尺寸,调整大小并复制它,并重复此过程。这会导致50%的性能损失,这对我来说似乎是合理的。
有了保留,它实际上做了这项工作。这项工作需要不到5秒钟。
通过push back添加到向量会导致它呈几何增长。几何增长导致平均每个数据片段被复制2-3次的渐近平均值。
关于realloc,std::vector不会重新分配内存,而是分配一个新的缓冲区,复制旧数据,然后丢弃旧缓冲区。
realloc试图增加缓冲区大小,如果无法增加,则按位复制缓冲区。
对于按位可复制类型,这比std vector管理更有效。我敢打赌realloc版本实际上从未复制过;在真正的程序中,可能无法扩展向量的内存空间。
std库分配器缺少realloc是一个小缺陷。您需要发明一个新的API,因为您希望它适用于非按位复制(类似于“尝试增长分配的内存”,如果失败,则由您来增加分配)。

哈哈,我错过了那个细节 :-) - Barry
最简单的问题常常引来最棒的答案,揭示表面上微不足道的事物背后微妙的复杂性。+1 - Richard Hodges
如果OP在循环外添加了一些东西来阻止优化器完全丢弃工作(例如一个asm语句,或者调用另一个文件中的非内联函数,并禁用链接时优化),那么你会看到优化不做任何事情:即使用SIMD一次存储16或32个字节,而不是一次只存储一个'int'。它无法将循环优化为对'memset'的调用,但是SIMD 'v={0,1,2,3}'; 'v+={4,4,4,4}'是一种胜利。(std::vector内部的复制大部分时间都是成本,应该使用'memcpy'或等效的优化实现。) - Peter Cordes
使用整个程序优化,理论上使用std::vector可以在未重载operator new的情况下使用realloc进行位复制类型。 (据我所知,不幸的是,没有真正的编译器+libc ++实现这一点。)不能利用将新页面映射到现有大型分配的末尾的优势是愚蠢的,在OP的硬件上是错过了2倍的优化因素。(在实际程序中,不会用巨大的memcpy污染缓存也很好。) - Peter Cordes
如果我没记错的话,clang + libc++ 有时可以优化 C++ 容器的分配/释放。虽然我没有检查过这个循环,也没有使用 libc++ 检查过 g++(只用了通常的 libstdc++)。 - Peter Cordes

5

当我添加10亿个整数时,第二段代码比使用向量的代码快得多。

这完全是不足为奇的。一个案例涉及一个动态大小的容器,必须为其负载进行重新调整,另一个案例涉及一个固定大小的容器,它不需要进行分支或其他额外的分配。后者只需要做很少的工作,没有分支,也没有额外的分配。公平的比较应该包括:

std::vector<int> b(size);
for(int i = 0; i < size; i++) {
    b[i] = i;
}

现在这个例子和你的数组示例做了同样的事情(好吧,几乎是一样的——new int[size]默认初始化所有的int,而std::vector<int>(size)会将它们全部清零,所以还需要更多的工作)。

将这两个进行比较并没有什么意义。如果固定大小的int数组适合你的用例,那就使用它。如果不适合,那就不要使用。你要么需要一个动态大小的容器,要么不需要。如果你需要,那么比固定大小的解决方案慢是你默认放弃的。


如果这就是第一段代码变慢的原因,我该如何告诉std::vector按几何级数增长?

std::vector已经被指定为按几何级数增长,这是维护O(1)摊销push_back复杂度的唯一方法。


我编辑了绘图以考虑reserve()情况。 - Nisba
1
@Nisba std::vector<int> b(size); 创建了一个大小为10亿的向量,reserve在这里无关紧要。 - Killzone Kid
@Nisba你没有改变你的图表,将元素分配给调整大小的向量比使用动态调整大小的推回方法花费更长的时间是没有意义的。 - Killzone Kid

3
std::vector的性能差是因为没有对realloc进行对数次调用吗?
你的测试既不支持这个结论,也不能证明相反。但是,我认为除非有相反的证据,否则会调用线性次数的重新分配。
更新:您的新测试显然证明了您的非对数重新分配假设的证据。
我怀疑这是由于向量在内部调用realloc太多次引起的。
更新:您的新测试显示,一些差异是由于重新分配造成的...但并非全部。我怀疑其余部分是由于优化器能够证明(但仅在不增长的情况下)数组值未使用,并选择根本不循环和写入它们。如果您确保实际使用编写的值,则预计非增长数组将具有类似于保留向量的优化性能。
在优化构建中,差异(在保留代码和非保留向量之间)最可能是由于执行更多重新分配(与保留数组的重新分配相比)。重新分配的数量是否过多是情境和主观的。执行较少的重新分配的缺点是由于过度分配而产生更多的浪费空间。
请注意,大型数组的重新分配成本主要来自元素的复制,而不是内存分配本身。
在未优化的构建中,由于未展开内联而产生额外的线性开销。
如何告诉std::vector进行几何增长?
几何增长是标准所必需的。没有办法也不需要告诉std::vector使用几何增长。
请注意,调用reserve一次将在此情况下起作用,但在不预先知道push_back数量的更一般情况下将无法起作用。
然而,在不预先知道push_back数量的一般情况下,非增长数组甚至都不是一个选项,因此其性能对于该一般情况并不重要。

我的测试关于几何级数增长的大小支持realloc不是缓慢的原因,因为它只被调用了30次。 - Nisba
@Nisba 你是在暗示说30次重新分配不会占用太多时间吗?技术上讲,这里并没有使用realloc函数,因为vector非常愚蠢,只是分配了一个新的内存块,并将所有数据复制到其中。实际上,使用realloc函数可能会产生更好的结果,因为它在某些情况下可以避免复制数据,甚至可以在某些操作系统中“移动”数据。 - user1143634
@Nisba 30次重新分配并不能证明它不是导致速度变慢的原因。请记住,重新分配是一种线性操作。对于一个包含1亿个元素的向量进行单次重新分配需要复制1亿个元素。内存访问并不便宜。 - eerorika
@Nisba 我删除了我的realloc更新,因为我现在感到不太自信,因为我注意到它甚至比reserve更快。我不确定,但可能仍然与由于数组未被使用而被优化掉的某些内容有关。 - eerorika
@user2079303,为什么你的回答中仍然写着有证据反对非对数再分配假设?malloc被调用了对数次数(是的,这不是重新分配,因为有人指出它被vector内部称为malloc而不是realloc)。 - Nisba
@Nisba 如果 malloc 被调用的次数是对数级别的,那么这肯定是反对重新分配发生频率超过对数级别假设的有力证据。(即使没有使用 realloc 函数,也是重新分配(即再次分配)) - eerorika

2

这并不是在比较几何增长和算术(或其他)增长。它是在比较预先分配所有必要的空间来按需增长空间。因此,让我们首先比较std::vector和一些实际使用几何增长的代码,并将两者用于可以利用几何增长的方式1。因此,这里有一个简单的类,它执行几何增长:

class my_vect {
    int *data;
    size_t current_used;
    size_t current_alloc;
public:

    my_vect()
        : data(nullptr)
        , current_used(0)
        , current_alloc(0)
    {}

    void push_back(int val) { 
        if (nullptr == data) {
            data = new int[1];
            current_alloc = 1;
        }
        else if (current_used == current_alloc)  {
            int *temp = new int[current_alloc * 2];
            for (size_t i=0; i<current_used; i++)
                temp[i] = data[i];
            swap(temp, data);
            delete [] temp;
            current_alloc *= 2;
        }
        data[current_used++] = val;
    }

    int &at(size_t index) { 
        if (index >= current_used)
            throw bad_index();
        return data[index];
    }

    int &operator[](size_t index) { 
        return data[index];
    }

    ~my_vect() { delete [] data; }
};

以下是一些用于练习的代码(也可以使用 std::vector):

int main() { 
    std::locale out("");
    std::cout.imbue(out);
    using namespace std::chrono;
    std::cout << "my_vect\n";
    for (int size = 100; size <= 1000000000; size *= 10) {
        auto start = high_resolution_clock::now();

        my_vect b;
        for(int i = 0; i < size; i++) {
            b.push_back(i);
        }    

        auto stop = high_resolution_clock::now();

        std::cout << "Size: " << std::setw(15) << size << ", Time: " << std::setw(15) << duration_cast<microseconds>(stop-start).count() << " us\n";
    }

    std::cout << "\nstd::vector\n";

    for (int size = 100; size <= 1000000000; size *= 10) {
        auto start = high_resolution_clock::now();

        std::vector<int> b;
        for (int i = 0; i < size; i++) {
            b.push_back(i);
        }

        auto stop = high_resolution_clock::now();

        std::cout << "Size: " << std::setw(15) << size << ", Time: " << std::setw(15) << duration_cast<microseconds>(stop - start).count() << " us\n";
    }
}

我使用 g++ -std=c++14 -O3 my_vect.cpp 进行编译。当我执行该命令时,得到以下结果:

my_vect
Size:             100, Time:               8 us
Size:           1,000, Time:              23 us
Size:          10,000, Time:             141 us
Size:         100,000, Time:             950 us
Size:       1,000,000, Time:           8,040 us
Size:      10,000,000, Time:          51,404 us
Size:     100,000,000, Time:         442,713 us
Size:   1,000,000,000, Time:       7,936,013 us

std::vector
Size:             100, Time:              40 us
Size:           1,000, Time:               4 us
Size:          10,000, Time:              29 us
Size:         100,000, Time:             426 us
Size:       1,000,000, Time:           3,730 us
Size:      10,000,000, Time:          41,294 us
Size:     100,000,000, Time:         363,942 us
Size:   1,000,000,000, Time:       5,200,545 us

我无疑可以优化my_vect,以跟上std::vector的速度(例如,最初为256个项目分配空间可能会非常有帮助)。我尚未尝试运行足够多次(和统计分析)来确信std::vector确实比my_vect更快。尽管如此,这似乎表明当我们将苹果与苹果进行比较时,得到的结果至少在某种程度上是相当可比的(例如,在彼此之间有一个相当小的恒定因子内)。

1. 作为旁注,我觉得有必要指出,这仍然不是苹果与苹果的比较——但只要我们只在int上实例化std::vector,许多显而易见的差异基本上被掩盖了。


1
这篇文章包括以下内容:
  1. 使用包装类覆盖 reallocmremap,以提供重新分配功能。
  2. 自定义向量类。
  3. 性能测试。

// C++17
#include <benchmark/benchmark.h> // Googleo benchmark lib, for benchmark.

#include <new>     // For std::bad_alloc.
#include <memory>  // For std::allocator_traits, std::uninitialized_move.
#include <cstdlib> // For C heap management API.
#include <cstddef> // For std::size_t, std::max_align_t.
#include <cassert> // For assert.
#include <utility> // For std::forward, std::declval,

namespace linux {
#include <sys/mman.h> // For mmap, mremap, munmap.
#include <errno.h>    // For errno.
auto get_errno() noexcept {
    return errno;
}
}

/*
 * Allocators.
 * These allocators will have non-standard compliant behavior if the type T's cp ctor has side effect.
 */

// class mrealloc are usefull for allocating small space for
// std::vector.
//
// Can prevent copy of data and memory fragmentation if there's enough
// continous memory at the original place.
template <class T>
struct mrealloc {
    using pointer = T*;
    using value_type = T;

    auto allocate(std::size_t len) {
        if (auto ret = std::malloc(len))
            return static_cast<pointer>(ret);
        else
            throw std::bad_alloc();
    }
    auto reallocate(pointer old_ptr, std::size_t old_len, std::size_t len) {
        if (auto ret = std::realloc(old_ptr, len))
            return static_cast<pointer>(ret);
        else
            throw std::bad_alloc();
    }
    void deallocate(void *ptr, std::size_t len) noexcept {
        std::free(ptr);
    }
};

// class mmaprealloc is suitable for large memory use.
//
// It will be usefull for situation that std::vector can grow to a huge
// size.
//
// User can call reserve without worrying wasting a lot of memory.
//
// It can prevent data copy and memory fragmentation at any time.
template <class T>
struct mmaprealloc {
    using pointer = T*;
    using value_type = T;

    auto allocate(std::size_t len) const
    {
        return allocate_impl(len, MAP_PRIVATE | MAP_ANONYMOUS);
    }
    auto reallocate(pointer old_ptr, std::size_t old_len, std::size_t len) const
    {
        return reallocate_impl(old_ptr, old_len, len, MREMAP_MAYMOVE);
    }
    void deallocate(pointer ptr, std::size_t len) const noexcept
    {
        assert(linux::munmap(ptr, len) == 0);
    }
  protected:
    auto allocate_impl(std::size_t _len, int flags) const
    {
        if (auto ret = linux::mmap(nullptr, get_proper_size(_len), PROT_READ | PROT_WRITE, flags, -1, 0))
            return static_cast<pointer>(ret);
        else
            fail(EAGAIN | ENOMEM);
    }
    auto reallocate_impl(pointer old_ptr, std::size_t old_len, std::size_t _len, int flags) const
    {
        if (auto ret = linux::mremap(old_ptr, old_len, get_proper_size(_len), flags))
            return static_cast<pointer>(ret);
        else
            fail(EAGAIN | ENOMEM);
    }

    static inline constexpr const std::size_t magic_num = 4096 - 1;
    static inline auto get_proper_size(std::size_t len) noexcept -> std::size_t {
        return round_to_pagesize(len);
    }
    static inline auto round_to_pagesize(std::size_t len) noexcept -> std::size_t {
        return (len + magic_num) & ~magic_num;
    }

    static inline void fail(int assert_val)
    {
        auto _errno = linux::get_errno();
        assert(_errno == assert_val);
        throw std::bad_alloc();
    }
};

template <class T>
struct mmaprealloc_populate_ver: mmaprealloc<T> {
    auto allocate(size_t len) const
    {
        return mmaprealloc<T>::allocate_impl(len, MAP_PRIVATE | MAP_ANONYMOUS | MAP_POPULATE);
    }
};

namespace impl {
struct disambiguation_t2 {};
struct disambiguation_t1 {
    constexpr operator disambiguation_t2() const noexcept { return {}; }
};
template <class Alloc>
static constexpr auto has_reallocate(disambiguation_t1) noexcept -> decltype(&Alloc::reallocate, bool{}) { return true; }
template <class Alloc>
static constexpr bool has_reallocate(disambiguation_t2) noexcept { return false; }
template <class Alloc>
static inline constexpr const bool has_reallocate_v = has_reallocate<Alloc>(disambiguation_t1{});
} /* impl */

template <class Alloc>
struct allocator_traits: public std::allocator_traits<Alloc> {
    using Base = std::allocator_traits<Alloc>;
    using value_type = typename Base::value_type;
    using pointer = typename Base::pointer;
    using size_t = typename Base::size_type;

    static auto reallocate(Alloc &alloc, pointer prev_ptr, size_t prev_len, size_t new_len) {
        if constexpr(impl::has_reallocate_v<Alloc>)
            return alloc.reallocate(prev_ptr, prev_len, new_len);
        else {
            auto new_ptr = Base::allocate(alloc, new_len);

            // Move existing array
            for(auto _prev_ptr = prev_ptr, _new_ptr = new_ptr; _prev_ptr != prev_ptr + prev_len; ++_prev_ptr, ++_new_ptr) {
                new (_new_ptr) value_type(std::move(*_prev_ptr));
                _new_ptr->~value_type();
            }
            Base::deallocate(alloc, prev_ptr, prev_len);

            return new_ptr;
        }
    }
};

template <class T, class Alloc = std::allocator<T>>
struct vector: protected Alloc {
    using alloc_traits = allocator_traits<Alloc>;
    using pointer = typename alloc_traits::pointer;
    using size_t = typename alloc_traits::size_type;
    pointer ptr = nullptr;
    size_t last = 0;
    size_t avail = 0;

    ~vector() noexcept {
        alloc_traits::deallocate(*this, ptr, avail);
    }

    template <class ...Args>
    void emplace_back(Args &&...args) {
        if (last == avail)
            double_the_size();
        alloc_traits::construct(*this, &ptr[last++], std::forward<Args>(args)...);
    }
    void double_the_size() {
        if (__builtin_expect(!!(avail), true)) {
            avail <<= 1;
            ptr = alloc_traits::reallocate(*this, ptr, last, avail);
        } else {
            avail = 1 << 4;
            ptr = alloc_traits::allocate(*this, avail);
        }
    }
};

template <class T>
static void BM_vector(benchmark::State &state) {
    for(auto _: state) {
        T c;
        for(auto i = state.range(0); --i >= 0; )
            c.emplace_back((char)i);
    }
}

static constexpr const auto one_GB = 1 << 30;

BENCHMARK_TEMPLATE(BM_vector, vector<char>)                                ->Range(1 << 3, one_GB);
BENCHMARK_TEMPLATE(BM_vector, vector<char, mrealloc<char>>)                ->Range(1 << 3, one_GB);
BENCHMARK_TEMPLATE(BM_vector, vector<char, mmaprealloc<char>>)             ->Range(1 << 3, one_GB);
BENCHMARK_TEMPLATE(BM_vector, vector<char, mmaprealloc_populate_ver<char>>)->Range(1 << 3, one_GB);
BENCHMARK_MAIN();
  1. Performance test.

All the performance test are done on:

Debian 9.4, Linux version 4.9.0-6-amd64 (debian-kernel@lists.debian.org)(gcc version 6.3.0 20170516 (Debian 6.3.0-18+deb9u1) ) #1 SMP Debian 4.9.82-1+deb9u3 (2018-03-02)

Compiled using clang++ -std=c++17 -lbenchmark -lpthread -Ofast main.cc

The command I used to run this test:

sudo cpupower frequency-set --governor performance
./a.out

Here's the output of google benchmark test:

Run on (8 X 1600 MHz CPU s)
CPU Caches:
  L1 Data 32K (x4)
  L1 Instruction 32K (x4)
  L2 Unified 256K (x4)
  L3 Unified 6144K (x1)
----------------------------------------------------------------------------------------------------------
Benchmark                                                                   Time           CPU Iterations
----------------------------------------------------------------------------------------------------------
BM_vector<vector<char>>/8                                                  58 ns         58 ns   11476934
BM_vector<vector<char>>/64                                                324 ns        324 ns    2225396
BM_vector<vector<char>>/512                                              1527 ns       1527 ns     453629
BM_vector<vector<char>>/4096                                             7196 ns       7196 ns      96695
BM_vector<vector<char>>/32768                                           50145 ns      50140 ns      13655
BM_vector<vector<char>>/262144                                         549821 ns     549825 ns       1245
BM_vector<vector<char>>/2097152                                       5007342 ns    5006393 ns        146
BM_vector<vector<char>>/16777216                                     42873349 ns   42873462 ns         15
BM_vector<vector<char>>/134217728                                   336225619 ns  336097218 ns          2
BM_vector<vector<char>>/1073741824                                 2642934606 ns 2642803281 ns          1
BM_vector<vector<char, mrealloc<char>>>/8                                  55 ns         55 ns   12914365
BM_vector<vector<char, mrealloc<char>>>/64                                266 ns        266 ns    2591225
BM_vector<vector<char, mrealloc<char>>>/512                              1229 ns       1229 ns     567505
BM_vector<vector<char, mrealloc<char>>>/4096                             6903 ns       6903 ns     102752
BM_vector<vector<char, mrealloc<char>>>/32768                           48522 ns      48523 ns      14409
BM_vector<vector<char, mrealloc<char>>>/262144                         399470 ns     399368 ns       1783
BM_vector<vector<char, mrealloc<char>>>/2097152                       3048578 ns    3048619 ns        229
BM_vector<vector<char, mrealloc<char>>>/16777216                     24426934 ns   24421774 ns         29
BM_vector<vector<char, mrealloc<char>>>/134217728                   262355961 ns  262357084 ns          3
BM_vector<vector<char, mrealloc<char>>>/1073741824                 2092577020 ns 2092317044 ns          1
BM_vector<vector<char, mmaprealloc<char>>>/8                             4285 ns       4285 ns     161498
BM_vector<vector<char, mmaprealloc<char>>>/64                            5485 ns       5485 ns     125375
BM_vector<vector<char, mmaprealloc<char>>>/512                           8571 ns       8569 ns      80345
BM_vector<vector<char, mmaprealloc<char>>>/4096                         24248 ns      24248 ns      28655
BM_vector<vector<char, mmaprealloc<char>>>/32768                       165021 ns     165011 ns       4421
BM_vector<vector<char, mmaprealloc<char>>>/262144                     1177041 ns    1177048 ns        557
BM_vector<vector<char, mmaprealloc<char>>>/2097152                    9229860 ns    9230023 ns         74
BM_vector<vector<char, mmaprealloc<char>>>/16777216                  75425704 ns   75426431 ns          9
BM_vector<vector<char, mmaprealloc<char>>>/134217728                607661012 ns  607662273 ns          1
BM_vector<vector<char, mmaprealloc<char>>>/1073741824              4871003928 ns 4870588050 ns          1
BM_vector<vector<char, mmaprealloc_populate_ver<char>>>/8                3956 ns       3956 ns     175037
BM_vector<vector<char, mmaprealloc_populate_ver<char>>>/64               5087 ns       5086 ns     133944
BM_vector<vector<char, mmaprealloc_populate_ver<char>>>/512              8662 ns       8662 ns      80579
BM_vector<vector<char, mmaprealloc_populate_ver<char>>>/4096            23883 ns      23883 ns      29265
BM_vector<vector<char, mmaprealloc_populate_ver<char>>>/32768          158374 ns     158376 ns       4444
BM_vector<vector<char, mmaprealloc_populate_ver<char>>>/262144        1171514 ns    1171522 ns        593
BM_vector<vector<char, mmaprealloc_populate_ver<char>>>/2097152       9297357 ns    9293770 ns         74
BM_vector<vector<char, mmaprealloc_populate_ver<char>>>/16777216     75140789 ns   75141057 ns          9
BM_vector<vector<char, mmaprealloc_populate_ver<char>>>/134217728   636359403 ns  636368640 ns          1
BM_vector<vector<char, mmaprealloc_populate_ver<char>>>/1073741824 4865103542 ns 4864582150 ns          1


sys/mman.h 是 POSIX 标准,而不是 glibc 特定的。您正在使用 posix::mremap,但将头文件放在 namespace glibc {} 中,这是双重错误。除了无法编译之外,mremap(与 mmap / munmap 不同)仅适用于 Linux,因此 glibc::mremap 可能是半合适的,但 glibc 可以移植到其他内核(至少我认为是 HURD,也许是 FreeBSD),因此除非其他内核具有它,否则您应该使用 linux::mremap。此外,您说“可能”需要自定义向量类来利用此功能。您是否检查过任何现有的 std::vector 实现是否直接利用此功能? - Peter Cordes
如果您修复这些错误并在std :: vector <int,mmapalloc>上包含更多描述以了解在Linux上使用glibc时实际获得此功能的方法/方式,我将为此点赞。大概您需要自己修改<vector>头文件? - Peter Cordes
1
我认为对于mmaprealloc而言,对齐并不是一个问题,因为mmap和mremap返回的内存地址总是页面对齐的。 - JiaHao Xu
没错,但是你将它们偏移了一个 sizeof(size_t),这比 alignof(max_align_t) 小。你使用页面的前4或8个字节来存储分配的长度,这意味着你实际返回的指针只有4或8字节对齐。 - Peter Cordes
1
@Peter Cordes 两者都已修复。 - JiaHao Xu
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接