46 votes

Un vecteur C ++ qui n'initialise pas ses membres?

Je suis en train de faire un wrapper C++ pour un morceau de code C qui retourne un grand tableau, et j'ai donc essayé de retourner les données en vector<unsigned char>.

Maintenant, le problème est, les données sont de l'ordre de méga-octets, et vector inutilement initialise son stockage, ce qui s'avère essentiel à réduis ma vitesse de moitié.

Comment puis-je éviter cela?

Ou, s'il n'est pas possible, est-il un autre conteneur STL qui permettrait d'éviter une telle travail inutile? Ou doit-je faire ma propre conteneur?

(Avant C++11)

Note:

Je suis de passage le vecteur que mon tampon de sortie. Je suis pas de copier les données à partir d'ailleurs.
C'est quelque chose comme:

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

55voto

bames53 Points 38303

Pour le défaut et la valeur d'initialisation des structures de l'utilisateur par défaut fourni par les constructeurs qui ne sont pas explicitement initialiser rien, pas d'initialisation est effectuée sur unsigned char membres:

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());

Je pense que c'est encore légal en vertu de la stricte aliasing règles.

Quand j'ai comparé les temps de construction pour l' v contre une vector<unsigned char> j'ai eu ~8 µs vs ~12 mme. Plus de 1000x plus rapide. Compilateur a été clang 3.2 avec la libc++ et indicateurs: -std=c++11 -Os -fcatch-undefined-behavior -ftrapv -pedantic -Weverything -Wno-c++98-compat -Wno-c++98-compat-pedantic -Wno-missing-prototypes

C++11 a une aide pour les non initialisé de stockage, std::aligned_storage. Bien qu'il nécessite un temps de compilation de taille.


Voici un exemple supplémentaire, afin de comparer l'utilisation totale (fois en nanosecondes):

VERSION=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

VERSION=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';
}

Je suis l'aide de clang svn-3.6.0 r218006

8voto

Nicol Bolas Points 133791

Désolé, il n'y a aucun moyen de l'éviter.

C++11 ajoute un constructeur qui prend seulement en taille, mais même que la valeur-initialiser les données.

Votre meilleur pari est de les allouer un tableau sur le tas, le coller dans un unique_ptr (si disponible), et de l'utiliser à partir de là.

Si vous êtes prêts à vous, comme vous le dites, "le piratage en STL", vous pouvez toujours prendre une copie de EASTL de travailler à partir. C'est une variation de certains conteneurs STL qui permet plus de mémoire restreinte conditions. Une mise en œuvre correcte de ce que vous essayez de le faire serait de donner son constructeur une valeur spéciale qui signifie "par défaut initialiser les membres," qui, pour POD types de moyens de ne rien faire pour initialiser la mémoire. Cela nécessite l'utilisation de certains modèles de la métaprogrammation à détecter si c'est un type POD, bien sûr.

3voto

Walter Points 7554

1 Il semble que l'utilisation de std::vector n'est ni nécessaire ni sensée dans votre situation. Vous voulez seulement que certains objets gèrent de la mémoire brute pour vous. Ceci peut être facilement réalisé par

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

2 Si vous voulez vraiment utiliser std::vector<> vous pouvez utiliser le truc décrit ici .

-2voto

Marmel Points 1

Pourquoi ne pas utiliser vector.reserve () pour allouer uniquement le stockage sans l'initialiser?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X