J'ai une application qui effectue un traitement sur certaines images.
Étant donné que je connais la largeur/hauteur/format, etc. (je le sais), et que je pense à définir un tampon pour stocker les données de pixels:
Ensuite, plutôt que d'utiliser new
et delete []
sur un unsigned char*
et en gardant une note séparée de la taille du tampon, je pense à simplifier les choses en utilisant un std::vector
.
Je déclarerais donc ma classe quelque chose comme ceci:
#include <vector>
class MyClass
{
// ... etc. ...
public:
virtual void OnImageReceived(unsigned char *pPixels,
unsigned int uPixelCount);
private:
std::vector<unsigned char> m_pImageBuffer; // buffer for 8-bit pixels
// ... etc. ...
};
Ensuite, lorsque j'ai reçu une nouvelle image (de taille variable - mais ne vous inquiétez pas de ces détails ici), je peux simplement redimensionner le vecteur (si nécessaire) et copier les pixels:
void MyClass::OnImageReceived(unsigned char *pPixels, unsigned int uPixelCount)
{
// called when a new image is available
if (m_pImageBuffer.size() != uPixelCount)
{
// resize image buffer
m_pImageBuffer.reserve(uPixelCount);
m_pImageBuffer.resize(uPixelCount, 0);
}
// copy frame to local buffer
memcpy_s(&m_pImageBuffer[0], m_pImageBuffer.size(), pPixels, uPixelCount);
// ... process image etc. ...
}
Cela me semble bien, et j'aime le fait que je n'ai pas à me soucier de la gestion de la mémoire, mais cela soulève quelques questions:
std::vector
ou existe-t-il un conteneur plus adapté?reserve
etresize
?memcpy_s
comme montré?Tout commentaire, critique ou conseil supplémentaire serait le bienvenu.
float
).Soit dit en passant, memcpy_s
n'est pas l'approche idiomatique ici. Utilisation std::copy
au lieu. Gardez à l'esprit qu'un pointeur est un itérateur.
À partir de C++ 17, std::byte
est l'unité idiomatique de stockage typé de manière opaque comme celle que vous utilisez ici. char
fonctionnera toujours, bien sûr, mais permet des utilisations dangereuses (comme char
!) que byte
ne fait pas.
Outre les autres réponses mentionnées, je vous recommande d'utiliser std::vector::assign
plutôt que std::vector::resize
et memcpy
:
void MyClass::OnImageReceived(unsigned char *pPixels, unsigned int uPixelCount)
{
m_pImageBuffer.assign(pPixels, pPixels + uPixelCount);
}
Cela se redimensionnera si nécessaire, et vous éviterez les inutiles 0
initialisation du tampon provoquée par std::vector::resize
.
L'utilisation d'un vector
dans ce cas est très bien. En C++, le stockage est garanti contigieux.
Je ne voudrais pas à la fois resize
et reserve
, ni memcpy
pour copier les données. À la place, tout ce que vous avez à faire est reserve
pour vous assurer vous n'avez pas besoin de réaffecter plusieurs fois, puis effacez vector
en utilisant clear
. Si vous resize
, il passera par et définira les valeurs de chaque élément à leurs valeurs par défaut - ce n'est pas une évidence ici car vous allez juste le remplacer de toute façon.
Lorsque vous êtes prêt à copier les données, n'utilisez pas memcpy
. Utilisez copy
conjointement avec back_inserter
dans un vector
vide:
std::copy (pPixels, pPixels + uPixelCount, std::back_inserter(m_pImageBuffer));
Je considérerais cet idiome comme étant beaucoup plus proche de la canonique que la méthode memcpy
que vous utilisez. Il peut y avoir des méthodes plus rapides ou plus efficaces, mais à moins que vous ne puissiez prouver qu'il s'agit d'un goulot d'étranglement dans votre code (ce qui ne sera probablement pas le cas, vous aurez des poissons beaucoup plus gros à faire frire ailleurs), je resterais avec des méthodes idiomatiques et je laisserais les micro-optimisations prématurées à quelqu'un d'autre.
J'éviterais std :: vector comme conteneur pour stocker un tampon non structuré, car std :: vector est profondément lent lorsqu'il est utilisé comme tampon
Considérez cet exemple:
#include <chrono>
#include <ctime>
#include <iostream>
#include <memory>
#include <vector>
namespace {
std::unique_ptr<unsigned char[]> allocateWithPtr() {
return std::unique_ptr<unsigned char[]>(new unsigned char[4000000]);
}
std::vector<unsigned char> allocateWithVector() {
return std::vector<unsigned char>(4000000); }
}
int main() {
auto start = std::chrono::system_clock::now();
for (long i = 0; i < 1000; i++) {
auto myBuff = allocateWithPtr();
}
auto ptr_end = std::chrono::system_clock::now();
for (long i = 0; i < 1000; i++) {
auto myBuff = allocateWithVector();
}
auto vector_end = std::chrono::system_clock::now();
std::cout << "std::unique_ptr = "
<< (ptr_end - start).count() / 1000.0 << " ms." << std::endl;
std::cout << "std::vector = "
<< (vector_end - ptr_end).count() / 1000.0 << " ms." << std::endl;
}
Production:
bash-3.2$ time myTest
std::unique_ptr = 0.396 ms.
std::vector = 35341.1 ms.
real 0m35.361s
user 0m34.932s
sys 0m0.092s
Même sans écritures ni réallocations, std :: vector est presque 100 000 fois plus lent que d'utiliser simplement un nouveau avec un unique_ptr. Que se passe t-il ici?
Comme le souligne @MartinSchlott, il n'est pas conçu pour cette tâche. Un vecteur sert à contenir un ensemble d'objets, pas un tampon non structuré (du point de vue d'un tableau). Les objets ont des destructeurs et des constructeurs. Lorsque le vecteur est détruit, il appelle le destructeur pour chaque élément qu'il contient, même le vecteur appellera un destructeur pour chaque caractère de votre vecteur.
Vous pouvez voir combien de temps il faut juste pour "détruire" les caractères non signés dans ce vecteur avec cet exemple:
#include <chrono>
#include <ctime>
#include <iostream>
#include <memory>
#include <vector>
std::vector<unsigned char> allocateWithVector() {
return std::vector<unsigned char>(4000000); }
}
int main() {
auto start = std::chrono::system_clock::now();
for (long i = 0; i < 100; i++) {
auto leakThis = new std::vector<unsigned char>(allocateWithVector());
}
auto leak_end = std::chrono::system_clock::now();
for (long i = 0; i < 100; i++) {
auto myBuff = allocateWithVector();
}
auto vector_end = std::chrono::system_clock::now();
std::cout << "leaking vectors: = "
<< (leak_end - start).count() / 1000.0 << " ms." << std::endl;
std::cout << "destroying vectors = "
<< (vector_end - leak_end).count() / 1000.0 << " ms." << std::endl;
}
Production:
leaking vectors: = 2058.2 ms.
destroying vectors = 3473.72 ms.
real 0m5.579s
user 0m5.427s
sys 0m0.135s
Même en supprimant la destruction du vecteur, il faut toujours 2 secondes pour construire 100 de ces choses.
Si vous n'avez pas besoin de redimensionnement dynamique, ni de construction et de destruction des éléments composant votre tampon, n'utilisez pas std :: vector.
std :: vector a été FAIT pour être utilisé dans de tels cas. Donc oui.
Oui, ça l'est.
reserve
n'est pas nécessaire dans votre cas.
Oui, il sera.
De plus - pour assurer un minimum de mémoire allouée:
void MyClass::OnImageReceived(unsigned char *pPixels, unsigned int uPixelCount)
{
m_pImageBuffer.swap(std::vector<unsigned char>(
pPixels, pPixels + uPixelCount));
// ... process image etc. ...
}
vector :: assign ne change pas la quantité de mémoire allouée, si la capacité est supérieure à la quantité nécessaire:
Effets: effacer (début (), fin ()); insert (begin (), first, last);
Veuillez considérer ceci:
void MyClass::OnImageReceived(unsigned char *pPixels, unsigned int uPixelCount)
{
// called when a new image is available
if (m_pImageBuffer.size() != uPixelCount) // maybe just < ??
{
std::vector<unsigned char> temp;
temp.reserve(uPixelCount); // no initialize
m_pImageBuffer.swap(temp) ; // no copy old data
}
m_pImageBuffer.assign(pPixels, pPixels + uPixelCount); // no reallocate
// ... process image etc. ...
}
Mon point est que si vous avez une grande image et avez besoin d'une image plus grande, votre ancienne image sera copiée pendant la réservation et/ou redimensionnée dans la nouvelle mémoire allouée, l'excès de mémoire initialisé, puis réécrit avec la nouvelle image. Vous coludez directement, mais vous ne pourrez plus utiliser les informations dont vous disposez sur la nouvelle taille pour éviter des réallocations possibles (peut-être que l'implémentation de assign est déjà optimisée pour ce cas simple ????).
Ça dépend. Si vous accédez aux données uniquement par le biais d'itérateurs et de l'opérateur [], vous pouvez utiliser un vecteur.
Si vous devez donner un pointeur sur des fonctions qui attendent un tampon de par ex. octets. Ce n'est pas à mon avis. Dans ce cas, vous devez utiliser quelque chose comme
unique_ptr<unsigned char[]> buf(new unsigned char[size])
est-il aussi sauvegardé qu'un vecteur, mais au lieu d'un vecteur, vous avez un contrôle maximum du tampon. Un vecteur peut réallouer un tampon ou pendant un appel de méthode/fonction, vous pouvez involontairement faire une copie de votre vecteur entier. Une erreur facilement commise.
La règle (pour moi) est. Si vous avez un vecteur, utilisez-le comme un vecteur. Si vous avez besoin d'un tampon mémoire, utilisez un tampon mémoire.
Comme indiqué dans un commentaire, le vecteur a une méthode de données. C'est C++. La liberté d'utiliser un vecteur comme tampon brut ne signifie pas que vous devez l'utiliser comme tampon brut. À mon humble avis, l'intention d'un vecteur était d'avoir un tampon de sauvegarde de type avec un système d'accès de sauvegarde de type. Pour des raisons de compatibilité, vous pouvez utiliser le tampon interne pour les appels. L'intention n'était pas d'utiliser le vecteur comme conteneur tampon de pointeur intelligent. Pour cela, j'utilise les modèles de pointeurs, signalant à un autre utilisateur de mon code que j'utilise ce tampon de manière brute. Si j'utilise des vecteurs, je les utilise de la façon dont ils sont destinés, et non des façons possibles qu'ils offrent.
COMME j'ai été blâmé ici pour mon opinion (pas une recommandation), je veux ajouter quelques mots au problème réel décrit par l'op.
S'il s'attend toujours à la même taille d'image, il devrait, à mon avis, utiliser un unique_ptr, car c'est ce qu'il en fait à mon avis. En utilisant
m_pImageBuffer.resize(uPixelCount, 0);
remet à zéro le tampon avant de lui copier le pPixel, une pénalité de temps inutile.
Si les images qu'il attend de taille différente, il ne devrait pas, à mon avis, utiliser un vecteur pendant la raison suivante. Surtout dans son code:
// called when a new image is available
if (m_pImageBuffer.size() != uPixelCount)
{
// resize image buffer
m_pImageBuffer.reserve(uPixelCount);
m_pImageBuffer.resize(uPixelCount, 0);
}
il redimensionnera le vecteur, qui est en fait un malloc et le copiera tant que les images grossissent. Une réallocation de mon expérience conduit toujours à malloc et copie.
C'est la raison pour laquelle je recommande, en particulier dans cette situation, l'utilisation d'un unique_ptr au lieu d'un vecteur.