50

大きな配列を返す C コードの C++ ラッパーを作成しているので、vector<unsigned char>.

問題は、データがメガバイトのオーダーであり、そのストレージをvector不必要に初期化することです。これにより、基本的に速度が半分になります。

これを防ぐにはどうすればよいですか?

または、それが不可能な場合、そのような不必要な作業を回避する STL コンテナーは他にありますか? それとも、自分のコンテナを作成する必要がありますか?

(C++11 より前)

ノート:

ベクトルを出力バッファとして渡しています。私は他の場所からデータをコピーしていません。
それは次のようなものです:

vector<unsigned char> buf(size);   // Why initialize??
GetMyDataFromC(&buf[0], buf.size());
4

5 に答える 5

57

明示的に何も初期化しないユーザー指定のデフォルト コンストラクターを使用した構造体のデフォルトおよび値の初期化では、unsigned char メンバーに対して初期化は実行されません。

struct uninitialized_char {
    unsigned char m;
    uninitialized_char() {}
};

// just to be safe
static_assert(1 == sizeof(uninitialized_char), "");

std::vector<uninitialized_char> v(4 * (1<<20));

GetMyDataFromC(reinterpret_cast<unsigned char*>(&v[0]), v.size());

これは厳密なエイリアシング規則の下でも合法だと思います。

v対 aの構築時間を比較すると、vector<unsigned char>約 8 µs 対約 12 ms でした。1000倍以上高速。コンパイラは libc++ とフラグ付きの clang 3.2 でした:-std=c++11 -Os -fcatch-undefined-behavior -ftrapv -pedantic -Weverything -Wno-c++98-compat -Wno-c++98-compat-pedantic -Wno-missing-prototypes

C++11 には、初期化されていないストレージ用のヘルパー std::aligned_storage があります。ただし、コンパイル時のサイズが必要です。


合計使用量 (ナノ秒単位の時間) を比較する追加の例を次に示します。

バージョン=1 ( vector<unsigned char>):

clang++ -std=c++14 -stdlib=libc++ main.cpp -DVERSION=1 -ftrapv -Weverything -Wno-c++98-compat -Wno-sign-conversion -Wno-sign-compare -Os && ./a.out

initialization+first use: 16,425,554
array initialization: 12,228,039
first use: 4,197,515
second use: 4,404,043

バージョン=2 ( vector<uninitialized_char>):

clang++ -std=c++14 -stdlib=libc++ main.cpp -DVERSION=2 -ftrapv -Weverything -Wno-c++98-compat -Wno-sign-conversion -Wno-sign-compare -Os && ./a.out

initialization+first use: 7,523,216
array initialization: 12,782
first use: 7,510,434
second use: 4,155,241


#include <iostream>
#include <chrono>
#include <vector>

struct uninitialized_char {
  unsigned char c;
  uninitialized_char() {}
};

void foo(unsigned char *c, int size) {
  for (int i = 0; i < size; ++i) {
    c[i] = '\0';
  }
}

int main() {
  auto start = std::chrono::steady_clock::now();

#if VERSION==1
  using element_type = unsigned char;
#elif VERSION==2
  using element_type = uninitialized_char;
#endif

  std::vector<element_type> v(4 * (1<<20));

  auto end = std::chrono::steady_clock::now();

  foo(reinterpret_cast<unsigned char*>(v.data()), v.size());

  auto end2 = std::chrono::steady_clock::now();

  foo(reinterpret_cast<unsigned char*>(v.data()), v.size());

  auto end3 = std::chrono::steady_clock::now();

  std::cout.imbue(std::locale(""));
  std::cout << "initialization+first use: " << std::chrono::nanoseconds(end2-start).count() << '\n';
  std::cout << "array initialization: " << std::chrono::nanoseconds(end-start).count() << '\n';
  std::cout << "first use: " << std::chrono::nanoseconds(end2-end).count() << '\n';
  std::cout << "second use: " << std::chrono::nanoseconds(end3-end2).count() << '\n';
}

私はclang svn-3.6.0 r218006を使用しています

于 2012-06-22T04:09:56.027 に答える
9

申し訳ありませんが、回避する方法はありません。

C++11 は、サイズのみを受け取るコンストラクターを追加しますが、それでもデータを値で初期化します。

あなたの最善の策は、ヒープに配列を割り当て、unique_ptr(利用可能な場合) に貼り付け、そこから使用することです。

あなたが言うように、「STL にハッキング」する意思がある場合は、いつでもEASTL のコピーを取得して作業することができます。これは、より制限されたメモリ条件を可能にする特定の STL コンテナーのバリエーションです。あなたがやろうとしていることの適切な実装は、そのコンストラクターに「デフォルトでメンバーを初期化する」ことを意味する特別な値を与えることです。これは、POD 型の場合、メモリを初期化するために何もしないことを意味します。もちろん、これにはテンプレート メタプログラミングを使用して、それが POD タイプであるかどうかを検出する必要があります。

于 2012-06-22T03:12:00.703 に答える
4

最適な解決策は、ゼロ引数に対して何もしないようにアロケータを単純に変更することですconstruct。これは、基になる型が同じであることを意味します。これにより、あらゆる種類の厄介な reinterpret_casting と潜在的なエイリアシング違反を回避し、非侵入的に任意の型を非初期化できます。

template<typename T> struct no_initialize : std::allocator<T> {
    void construct(T* p) {}
    template<typename... Args> void construct(T* p, Args&&... args) {
        new (p) T(std::forward<Args>(args)...);
    }
};
于 2014-09-18T21:39:19.867 に答える
3

1あなたの状況では、使用std::vectorする必要はなく、賢明でもないようです。いくつかのオブジェクトに生のメモリを管理してもらいたいだけです。これは、次の方法で簡単に達成できます。

std::unique_ptr<void, void(*)(void*)> p(std::malloc(n), std::free);

2本当に使いたい場合は、ここでstd::vector<>説明するトリックを使用できます。

于 2013-08-22T11:51:07.923 に答える