32

vector<char>バッファとして使いたいです。インターフェイスは私のニーズには最適ですが、メモリが初期化されるため、現在のサイズを超えてサイズを変更するとパフォーマンスが低下します。データはいずれにせよサードパーティのC関数によって上書きされるため、初期化は必要ありません。初期化ステップを回避する方法または特定のアロケータはありますか?とresize()のような他のトリックではなく、を使用したいことに注意してください。常に「バッファ」の重要なサイズを表す必要がありますが、後のサイズよりも大きい可能性があるため、ここでも信頼できません。私のアプリケーションにとって重要な情報として。さらに、ベクトルの(新しい)サイズが事前にわからないため、使用できませんreserve()capacity()size()capacity()resize()capacity()std::array。ベクトルをそのように構成できない場合は、の代わりにどのような種類のコンテナーまたはアロケーターを使用できるかを知りたいですvector<char, std::alloc>。唯一の要件は、ベクトルの代替はせいぜいSTLまたはBoostに基づいている必要があるということです。C++11にアクセスできます。

4

6 に答える 6

32

の明示的な場合でも初期化をオフにできないことは既知の問題です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();
}
于 2013-03-05T10:00:27.607 に答える
19

あなたの要件を満たす標準ライブラリには何もありませんし、私がブーストで知っていることも何もありません。

私が考えることができる3つの合理的なオプションがあります:

  • 今のところは固執しstd::vector、コードにコメントを残して、これがアプリケーションにボトルネックを引き起こす場合は、コードに戻ってください。
  • construct空の/メソッドを持つカスタムアロケータを使用しdestroyます-そして、オプティマイザーがそれらへの呼び出しを削除するのに十分賢いことを願っています。
  • 動的に割り当てられた配列のラッパーを作成し、必要な最小限の機能のみを実装します。
于 2013-03-05T09:51:03.820 に答える
4

さまざまなポッドタイプのベクトルで機能する代替ソリューションとして:

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

それをカプセル化します。

最大サイズに初期化します(予約ではありません)。

実際のサイズの終わりを表すイテレータへの参照を保持します。

アルゴリズムには、の代わりにbeginとを使用します。real endend

于 2013-03-05T09:51:25.987 に答える
2

したがって、stackoverflowで見つかったさまざまなソリューションを要約すると、次のようになります。

  1. 特別なdefault-initアロケータを使用します。(https://stackoverflow.com/a/21028912/1984766
    欠点: vector-typeをに変更しますstd::vector<char, default_init_allocator<char>> vec;
  2. struct NoInitChar空のコンストラクターを持つcharのラッパー構造体を使用するため、値の初期化をスキップします( https://stackoverflow.com/a/15220853/1984766
    欠点: vector-typeをに変更しますstd::vector<NoInitChar> vec;
  3. 一時的ににキャストし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, &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

これを行う必要があることは非常にまれです。このハックが必要であることを絶対に確認するために、状況をベンチマークすることを強くお勧めします。

それでも、私はNoInitCharソリューションを好みます。(マキシムの答えを参照してください)

ただし、これが役立つと確信していて、NoInitCharが機能せず、コンパイラとしてclang、gcc、またはMSVCを使用している場合は、この目的でfollyのルーチンを使用することを検討してください。

https://github.com/facebook/folly/blob/master/folly/memory/UninitializedMemoryHacks.hを参照してください

基本的な考え方は、これらのライブラリ実装のそれぞれに、初期化されていないサイズ変更を行うためのルーチンがあるということです。あなたはそれを呼び出す必要があります。

ハッキーですが、FacebookのC ++コードはこのハックが適切に機能することに依存していることを知って、少なくとも自分を慰めることができます。したがって、これらのライブラリ実装の新しいバージョンで必要になった場合は、ハックが更新されます。

于 2020-11-29T08:24:41.900 に答える