32

我想vector<char>用作缓冲区。该界面非常适合我的需求,但是在将其调整为超出当前大小时会降低性能,因为内存已初始化。我不需要初始化,因为在任何情况下数据都会被一些第三方 C 函数覆盖。有没有办法或特定的分配器来避免初始化步骤?请注意,我确实想使用,而不是像and之类的resize()其他技巧,因为我需要在任何时候始终表示我的“缓冲区”的显着大小,而在 a 之后可能大于它的大小,所以,我不能再依赖作为我申请的重要信息。此外,向量的(新)大小永远不会提前知道,所以我不能使用reserve()capacity()size()capacity()resize()capacity()std::array. 如果无法以这种方式配置 vector,我想知道我可以使用哪种容器或分配器来代替vector<char, std::alloc>. 唯一的要求是vector的替代品最多必须基于STL或Boost。我可以访问 C++11。

4

6 回答 6

32

这是一个已知问题,即使是显式地也无法关闭初始化std::vector

人们通常实现自己的pod_vector<>,不做任何元素的初始化。

另一种方法是创建一个与 char 布局兼容的类型,其构造函数不执行任何操作:

struct NoInitChar
{
    char value;
    NoInitChar() noexcept {
        // do nothing
        static_assert(sizeof *this == sizeof value, "invalid size");
        static_assert(__alignof *this == __alignof value, "invalid alignment");
    }
};

int main() {
    std::vector<NoInitChar> v;
    v.resize(10); // calls NoInitChar() which does not initialize

    // Look ma, no reinterpret_cast<>!
    char* beg = &v.front().value;
    char* end = beg + v.size();
}
于 2013-03-05T10:00:27.607 回答
19

标准库中没有任何内容可以满足您的要求,我在 boost 中也一无所知。

我能想到三个合理的选择:

  • 暂时坚持std::vector下去,在代码中留下评论,如果这会导致您的应用程序出现瓶颈,请返回它。
  • 使用带有空construct/destroy方法的自定义分配器 - 并希望您的优化器足够聪明,可以删除对它们的任何调用。
  • 围绕一个动态分配的数组创建一个包装器,仅实现您需要的最小功能。
于 2013-03-05T09:51:03.820 回答
4

作为与不同 pod 类型的向量一起使用的替代解决方案:

template<typename V>
void resize(V& v, size_t newSize)
{
    struct vt { typename V::value_type v; vt() {}};
    static_assert(sizeof(vt[10]) == sizeof(typename V::value_type[10]), "alignment error");
    typedef std::vector<vt, typename std::allocator_traits<typename V::allocator_type>::template rebind_alloc<vt>> V2;
    reinterpret_cast<V2&>(v).resize(newSize);
}

然后你可以:

std::vector<char> v;
resize(v, 1000); // instead of v.resize(1000);

这很可能是UB,即使它适用于我更关心性能的情况。clang生成的程序集的差异:

test():
        push    rbx
        mov     edi, 1000
        call    operator new(unsigned long)
        mov     rbx, rax
        mov     edx, 1000
        mov     rdi, rax
        xor     esi, esi
        call    memset
        mov     rdi, rbx
        pop     rbx
        jmp     operator delete(void*)

test_noinit():
        push    rax
        mov     edi, 1000
        call    operator new(unsigned long)
        mov     rdi, rax
        pop     rax
        jmp     operator delete(void*)
于 2019-07-16T09:06:11.107 回答
2

封装它。

将其初始化为最大大小(非保留)。

如您所说,保留对表示实际大小结束的迭代器的引用。

使用beginandreal end代替end, 用于您的算法。

于 2013-03-05T09:51:25.987 回答
2

所以总结一下stackoverflow上的各种解决方案:

  1. 使用特殊的默认初始化分配器。(https://stackoverflow.com/a/21028912/1984766
    缺点:将矢量类型更改为std::vector<char, default_init_allocator<char>> vec;
  2. struct NoInitChar在具有空构造函数的 char 周围使用包装结构,因此跳过值初始化( https://stackoverflow.com/a/15220853/1984766
    缺点:将向量类型更改为std::vector<NoInitChar> vec;
  3. 暂时将其vector<char>转换为vector<NoInitChar>并调整其大小(https://stackoverflow.com/a/57053750/1984766
    缺点:不会更改向量的类型,但您需要调用your_resize_function (vec, x)而不是vec.resize (x).

在这篇文章中,我想指出所有这些方法都需要由编译器进行优化,以加快您的程序。我可以确认调整大小时新字符的初始化确实被我测试的每个编译器优化掉了。所以一切看起来都不错......
但是--> 由于方法 1 和 2 改变了向量的类型,当您在更“复杂”的情况下使用这些向量时会发生什么。
考虑这个例子:

#include <time.h>
#include <vector>
#include <string_view>
#include <iostream>

//high precision-timer
double get_time () {
    struct timespec timespec;
    ::clock_gettime (CLOCK_MONOTONIC_RAW, &timespec);
    return timespec.tv_sec + timespec.tv_nsec / (1000.0 * 1000.0 * 1000.0);
}

//method 1 --> special allocator
//reformated to make it readable
template <typename T, typename A = std::allocator<T>>
class default_init_allocator : public A {
private:
    typedef std::allocator_traits<A> a_t;
public:
    template<typename U>
    struct rebind {
        using other = default_init_allocator<U, typename a_t::template rebind_alloc<U>>;
    };
    using A::A;

    template <typename U>
    void construct (U* ptr) noexcept (std::is_nothrow_default_constructible<U>::value) {
        ::new (static_cast<void*>(ptr)) U;
    }
    template <typename U, typename...Args>
    void construct (U* ptr, Args&&... args) {
        a_t::construct (static_cast<A&>(*this), ptr, std::forward<Args>(args)...);
    }
};

//method 2 --> wrapper struct
struct NoInitChar {
public:
    NoInitChar () noexcept { }
    NoInitChar (char c) noexcept : value (c) { }
public:
    char value;
};

//some work to waste time
template<typename T>
void do_something (T& vec, std::string_view str) {
    vec.push_back ('"');
    vec.insert (vec.end (), str.begin (), str.end ());
    vec.push_back ('"');
    vec.push_back (',');
}

int main (int argc, char** argv) {
    double timeBegin = get_time ();

    std::vector<char> vec;                                 //normal case
    //std::vector<char, default_init_allocator<char>> vec; //method 1
    //std::vector<NoInitChar> vec;                         //method 2
    vec.reserve (256 * 1024 * 1024);
    for (int i = 0; i < 1024 * 1024; ++i) {
        do_something (vec, "foobar1");
        do_something (vec, "foobar2");
        do_something (vec, "foobar3");
        do_something (vec, "foobar4");
        do_something (vec, "foobar5");
        do_something (vec, "foobar6");
        do_something (vec, "foobar7");
        do_something (vec, "foobar8");
        vec.resize (vec.size () + 64);
    }

    double timeEnd = get_time ();
    std::cout << (timeEnd - timeBegin) * 1000 << "ms" << std::endl;
    return 0;
}

您会期望方法 1 和 2 在每个“最近的”编译器中都优于法线向量,因为调整大小是免费的并且其他操作是相同的。再想想:

                g++ 7.5.0   g++ 8.4.0   g++ 9.3.0   clang++ 9.0.0
vector<char>         95ms       134ms       133ms            97ms
method 1            130ms       159ms       166ms            91ms
method 2            166ms       160ms       159ms            89ms

所有测试应用程序都像这样编译并执行 50 次以获取最低测量值:

$(cc) -O3 -flto -std=c++17 sample.cpp
于 2020-04-21T14:59:48.940 回答
1

您很少需要这样做;我强烈建议您对自己的情况进行基准测试,以绝对确定需要此 hack。

即便如此,我还是更喜欢 NoInitChar 解决方案。(见马克西姆的回答)

但是,如果您确定您会从中受益,并且 NoInitChar 对您不起作用,并且您使用 clang、gcc 或 MSVC 作为编译器,请考虑为此目的使用 folly 的例程。

https://github.com/facebook/folly/blob/master/folly/memory/UninitializedMemoryHacks.h

基本思想是这些库实现中的每一个都有一个用于进行未初始化调整大小的例程;你只需要调用它。

虽然 hacky,你至少可以安慰自己知道 facebook 的 C++ 代码依赖于这个 hack 正常工作,所以如果这些库实现的新版本需要它,他们会更新它。

于 2020-11-29T08:24:41.900 回答