我想使用vector<char>
作为缓冲区。该接口非常适合我的需求,但由于内存已初始化,因此在将其大小调整到超出当前大小时会有性能损失。我不需要初始化,因为在任何情况下,某些第三方C函数都会覆盖数据。有没有办法或特定的分配器来避免初始化步骤?请注意,我确实要使用resize()
,而不是reserve()
和capacity()
等其他技巧,因为我需要size()
始终代表我的“缓冲区”的有意义的大小。在capacity()
之后,虽然resize()
可能大于其大小,但同样,我不能依赖capacity()
作为我的应用程序的重要信息。此外,矢量的(新)大小事先不知道,所以我不能使用std::array
。如果无法以这种方式配置vector,我想知道我可以使用哪种容器或分配器而不是vector<char, std::alloc>
。唯一的要求是vector的替代方案最多必须基于STL或Boost。我可以访问C ++ 11。
答案 0 :(得分:24)
已知问题是,即使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();
}
答案 1 :(得分:18)
标准库中没有任何内容符合您的要求,也没有任何我知道的提升。
我能想到三个合理的选择:
std::vector
,在代码中留下评论,如果这会导致您的应用程序出现瓶颈,请回复它。construct
/ destroy
空方法的自定义分配器 - 并希望您的优化器足够聪明,可以删除对它们的任何调用。答案 2 :(得分:2)
封装它。
将其初始化为最大尺寸(不是保留)。
保持对表示实际大小结尾的迭代器的引用,就像你所说的那样。
为您的算法使用begin
和real end
,而不是end
。
答案 3 :(得分:2)
因此,总结一下stackoverflow上的各种解决方案:
std::vector<char, default_init_allocator<char>> vec;
struct NoInitChar
,因此跳过值初始化(https://stackoverflow.com/a/15220853/1984766)std::vector<NoInitChar> vec;
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, ×pec);
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
答案 4 :(得分:0)
作为alternative solution,可与不同类型的豆荚一起使用:
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,即使它在我更关心性能的情况下对我来说也正常工作。生成的程序集as produced by 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*)
答案 5 :(得分:0)
您很少需要这样做;我强烈建议您对情况进行基准测试,以确保绝对需要此hack。
即使那样,我还是更喜欢NoInitChar解决方案。 (请参见Maxim的答案)
但是,如果您确定会从中受益,并且NoInitChar不适合您,并且您正在使用clang或gcc或MSVC作为编译器,请考虑为此使用folly的例程。
请参见https://github.com/facebook/folly/blob/master/folly/memory/UninitializedMemoryHacks.h
基本思想是,这些库实现中的每个实现都有一个用于执行未初始化的调整大小的例程;您只需要调用它即可。
虽然很黑,但是您至少可以在知道Facebook的C ++代码依赖于这种黑客正常工作的情况下安慰自己,因此,如果这些库实现的新版本需要它,他们将对其进行更新。