vector<char>
バッファとして使いたいです。インターフェイスは私のニーズには最適ですが、メモリが初期化されるため、現在のサイズを超えてサイズを変更するとパフォーマンスが低下します。データはいずれにせよサードパーティのC関数によって上書きされるため、初期化は必要ありません。初期化ステップを回避する方法または特定のアロケータはありますか?とresize()
のような他のトリックではなく、を使用したいことに注意してください。常に「バッファ」の重要なサイズを表す必要がありますが、後のサイズよりも大きい可能性があるため、ここでも信頼できません。私のアプリケーションにとって重要な情報として。さらに、ベクトルの(新しい)サイズが事前にわからないため、使用できませんreserve()
capacity()
size()
capacity()
resize()
capacity()
std::array
。ベクトルをそのように構成できない場合は、の代わりにどのような種類のコンテナーまたはアロケーターを使用できるかを知りたいですvector<char, std::alloc>
。唯一の要件は、ベクトルの代替はせいぜいSTLまたはBoostに基づいている必要があるということです。C++11にアクセスできます。
6 に答える
の明示的な場合でも初期化をオフにできないことは既知の問題ですstd::vector
。
pod_vector<>
人々は通常、要素の初期化を行わない独自の実装を行います。
もう1つの方法は、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();
}
あなたの要件を満たす標準ライブラリには何もありませんし、私がブーストで知っていることも何もありません。
私が考えることができる3つの合理的なオプションがあります:
- 今のところは固執し
std::vector
、コードにコメントを残して、これがアプリケーションにボトルネックを引き起こす場合は、コードに戻ってください。 construct
空の/メソッドを持つカスタムアロケータを使用しdestroy
ます-そして、オプティマイザーがそれらへの呼び出しを削除するのに十分賢いことを願っています。- 動的に割り当てられた配列のラッパーを作成し、必要な最小限の機能のみを実装します。
さまざまなポッドタイプのベクトルで機能する代替ソリューションとして:
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*)
それをカプセル化します。
最大サイズに初期化します(予約ではありません)。
実際のサイズの終わりを表すイテレータへの参照を保持します。
アルゴリズムには、の代わりにbegin
とを使用します。real end
end
したがって、stackoverflowで見つかったさまざまなソリューションを要約すると、次のようになります。
- 特別なdefault-initアロケータを使用します。(https://stackoverflow.com/a/21028912/1984766)
欠点: vector-typeをに変更しますstd::vector<char, default_init_allocator<char>> vec;
struct NoInitChar
空のコンストラクターを持つcharのラッパー構造体を使用するため、値の初期化をスキップします( https://stackoverflow.com/a/15220853/1984766)
欠点: vector-typeをに変更しますstd::vector<NoInitChar> vec;
- 一時的ににキャストし
vector<char>
てvector<NoInitChar>
サイズを変更します(https://stackoverflow.com/a/57053750/1984766)
欠点:ベクターのタイプは変更されませんが、のyour_resize_function (vec, x)
代わりに呼び出す必要がありますvec.resize (x)
。
この投稿では、プログラムを高速化するために、これらすべてのメソッドをコンパイラーが最適化する必要があることを指摘したいと思います。サイズ変更時の新しいcharの初期化は、テストしたすべてのコンパイラで実際に最適化されていることを確認できます。したがって、すべてが良さそうに見えます...
しかし->方法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
これを行う必要があることは非常にまれです。このハックが必要であることを絶対に確認するために、状況をベンチマークすることを強くお勧めします。
それでも、私はNoInitCharソリューションを好みます。(マキシムの答えを参照してください)
ただし、これが役立つと確信していて、NoInitCharが機能せず、コンパイラとしてclang、gcc、またはMSVCを使用している場合は、この目的でfollyのルーチンを使用することを検討してください。
https://github.com/facebook/folly/blob/master/folly/memory/UninitializedMemoryHacks.hを参照してください
基本的な考え方は、これらのライブラリ実装のそれぞれに、初期化されていないサイズ変更を行うためのルーチンがあるということです。あなたはそれを呼び出す必要があります。
ハッキーですが、FacebookのC ++コードはこのハックが適切に機能することに依存していることを知って、少なくとも自分を慰めることができます。したがって、これらのライブラリ実装の新しいバージョンで必要になった場合は、ハックが更新されます。