C++ 使用向量<;char>;作为缓冲区,而不在resize()上初始化它
我想使用C++ 使用向量<;char>;作为缓冲区,而不在resize()上初始化它,c++,boost,c++11,C++,Boost,C++11,我想使用vector作为缓冲区。该接口非常适合我的需要,但是当它的大小超过当前大小时,会有性能损失,因为内存已经初始化。我不需要初始化,因为数据在任何情况下都会被一些第三方C函数覆盖。是否有方法或特定的分配器来避免初始化步骤?请注意,我确实希望使用resize(),而不是像reserve()和capacity()这样的技巧,因为我需要size()始终表示我的“缓冲区”在任何时刻的有效大小,而capacity()可能在resize()之后大于它的大小,所以,同样,我不能依赖capacity()作为
vector
作为缓冲区。该接口非常适合我的需要,但是当它的大小超过当前大小时,会有性能损失,因为内存已经初始化。我不需要初始化,因为数据在任何情况下都会被一些第三方C函数覆盖。是否有方法或特定的分配器来避免初始化步骤?请注意,我确实希望使用resize()
,而不是像reserve()
和capacity()
这样的技巧,因为我需要size()
始终表示我的“缓冲区”在任何时刻的有效大小,而capacity()
可能在resize()
之后大于它的大小,所以,同样,我不能依赖capacity()
作为我的应用程序的重要信息。此外,向量的(新)大小事先是未知的,因此我不能使用std::array
。如果vector不能以这种方式配置,我想知道我可以使用什么样的容器或分配器来代替vector
。唯一的要求是,矢量的替代方案最多只能基于STL或Boost。我可以访问C++11。标准库中没有满足您要求的内容,我对boost中的内容也一无所知
我可以想到三个合理的选择:
- 现在请坚持使用
,在代码中留下注释,如果这会导致应用程序出现瓶颈,请返回注释std::vector
- 使用带有空
构造
/
销毁
方法的自定义分配器-希望您的优化程序足够聪明,能够删除对它们的任何调用
- 围绕动态分配的数组创建包装器,只实现所需的最小功能
- 封装它
将其初始化为最大大小(非保留)
保留对表示实际大小结尾的迭代器的引用,如您所说
对于您的算法,请使用
begin
和real end
,而不是end
。已知的问题是,即使对于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();
}
struct NoInitChar
{
字符值;
NoInitChar()noexcept{
//无所事事
静态_断言(sizeof*this==sizeof值,“无效大小”);
static_assert(uuu alignof*this==uuu alignof value,“无效对齐”);
}
};
int main(){
std::向量v;
v、 调整大小(10);//调用不初始化的NoInitChar()
//听着,妈,不要再解释了!
char*beg=&v.front().value;
char*end=beg+v.size();
}
作为一种与不同荚果类型的载体一起工作的载体:
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);
}
因此,总结一下stackoverflow上的各种解决方案:
缺点:将向量类型更改为
std::vector vec代码>
struct NoInitChar
,从而跳过值初始化()缺点:将向量类型更改为
std::vector vec代码>
向量
临时转换为向量
并调整其大小()缺点:不会更改向量的类型,但您需要调用
您的_resize_函数(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;
}
这是非常罕见的,你会需要这样做;我强烈建议您对您的情况进行基准测试,以绝对确保此黑客是必要的 即使如此,我还是更喜欢NoInitChar解决方案。(见马克西姆的答案) <>但是如果你确信你会从中受益,而NoInitChar不为你工作,你使用CLAN,或者GCC,或者MSVC作为编译器,考虑使用愚蠢的程序来实现这个目的。 看 基本思想是,每个库实现都有一个例程,用于执行未初始化的大小调整;你只要打电话就行了
当你知道脸谱网的C++代码依赖于这个黑客操作时,如果这些库实现的新版本需要它,那么他们会更新它。> <代码>保留<代码> >代码>容量<代码>或
std::array
,或适当的构造函数..不太清楚您想要做什么/避免..哪种性能损失?你是怎么测量的?@UmNyobe不需要测量。很明显,在最坏的情况下,从0调整到10^9会降低性能,即使分配器足够聪明,可以使用memset(这是无法保证的)。@us2012不能保证容量()与大小相同。@us2012:使用向量的保留存储不是一个好主意。如果向量的内部存储大小发生变化,“伪”元素的值将不会被复制,带有选中迭代器/运算符[]
的向量实现将断言您是否使用该函数访问“伪”元素等+1第二项是大希望,但如果优化器相当智能,它可能会看到曙光。第三个显然授予了最多的控制权,但代价是要维护更多的代码(但它真的会比定制的
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*)
#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;
}
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
$(cc) -O3 -flto -std=c++17 sample.cpp