いくつかの画像に対して何らかの処理を実行するアプリケーションがあります。
私は幅/高さ/形式などを知っているので(私は知っています)、ピクセルデータを保存するバッファを定義することだけを考えています:
それから、delete []
でnew
とunsigned char*
を使用し、バッファサイズを個別に記録するのではなく、std::vector
を使用して単純化することを考えています。
したがって、クラスを次のように宣言します。
#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. ...
};
次に、新しい画像(可変サイズですが、ここではそれらの詳細について心配する必要はありません)を受け取ったときに、ベクトルのサイズを変更して(必要な場合)、ピクセルをコピーします。
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. ...
}
これは私には問題ないと思われ、メモリ管理について心配する必要がないという事実が好きですが、いくつかの疑問が生じます。
std::vector
の有効なアプリケーションですか、それともより適切なコンテナがありますか?reserve
andresize
を呼び出して、パフォーマンス面で正しいことをしていますか?memcpy_s
を使用できますか?追加のコメント、批判、アドバイスは大歓迎です。
float
など)を使用できます。ちなみに、memcpy_s
は、ここでの慣用的なアプローチではありません。使用する std::copy
代わりに。ポインターはイテレーターであることに注意してください。
C++ 17以降、std::byte
は、ここで使用しているような不透明に型指定されたストレージの慣用的な単位です。 char
はもちろん動作しますが、char
ではできない安全でない使用(byte
!として)を許可します。
他の回答が言及していることに加えて、std::vector::assign
およびmemcpy
ではなくstd::vector::resize
を使用することをお勧めします。
void MyClass::OnImageReceived(unsigned char *pPixels, unsigned int uPixelCount)
{
m_pImageBuffer.assign(pPixels, pPixels + uPixelCount);
}
必要に応じてサイズが変更され、0
によって引き起こされるバッファの不必要なstd::vector::resize
初期化を回避できます。
この場合、vector
を使用しても問題ありません。 C++では、ストレージは連続していることが保証されています。
resize
とreserve
の両方も、データをコピーするためにmemcpy
もしません。代わりに、確認するためにreserve
するだけです。何回も再割り当てする必要はありません。vector
を使用してclear
をクリアします。 resize
を使用すると、すべての要素の値がデフォルトに設定されます。とにかく上書きするため、ここでは不要です。
データをコピーする準備ができたら、memcpy
を使用しないでください。 copy
をback_inserter
とともに使用して、空のvector
に入れます。
std::copy (pPixels, pPixels + uPixelCount, std::back_inserter(m_pImageBuffer));
このイディオムは、使用しているmemcpy
メソッドよりもはるかに標準に近いと考えます。より高速またはより効率的な方法があるかもしれませんが、これがコードのボトルネックであることを証明できない限り(おそらくそうではありません。他の場所で揚げる魚がはるかに多いでしょう)、私は慣用的な方法に固執し、去ります他の誰かへの時期尚早なマイクロ最適化。
Std :: vectorはバッファとして使用すると非常に遅いため、非構造化バッファを格納するためのコンテナとしてstd :: vectorを避ける
この例を考えてみましょう:
#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;
}
出力:
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
書き込みや再割り当てが行われていなくても、std :: vectorは、unique_ptrを指定して新しいものを使用する場合よりもほぼ100,000倍遅くなります。何が起きてる?
@MartinSchlottが指摘しているように、このタスク用には設計されていません。ベクトルは、オブジェクトのセットを保持するためのものであり、非構造化(配列の観点から)バッファではありません。オブジェクトにはデストラクタとコンストラクタがあります。ベクターが破棄されると、ベクター内の各要素のデストラクターが呼び出されます。ベクターでさえ、ベクター内の各文字のデストラクターが呼び出されます。
この例では、このベクトル内の符号なし文字を「破壊」するのにどれだけ時間がかかるかを見ることができます。
#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;
}
出力:
leaking vectors: = 2058.2 ms.
destroying vectors = 3473.72 ms.
real 0m5.579s
user 0m5.427s
sys 0m0.135s
ベクターの破壊を除去したとしても、これらのものを100個作成するのにまだ2秒かかります。
動的なサイズ変更、またはバッファを構成する要素の構築と破棄が必要ない場合は、std :: vectorを使用しないでください。
std :: vectorは、このような場合に使用するために作成されました。あ、はい。
はい、そうです。
reserve
はあなたの場合は不要です。
はい、そうします。
さらに-割り当てられたメモリの最小値を確保するには:
void MyClass::OnImageReceived(unsigned char *pPixels, unsigned int uPixelCount)
{
m_pImageBuffer.swap(std::vector<unsigned char>(
pPixels, pPixels + uPixelCount));
// ... process image etc. ...
}
vector :: assignは、容量が必要な量よりも大きい場合、割り当てられたメモリの量を変更しません。
効果:erase(begin()、end()); insert(begin()、first、last);
これを考慮してください:
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. ...
}
私のポイントは、大きな写真があり、大きなゴミが必要な場合、予約時に古い写真がコピーされ、新しい割り当てられたメモリにサイズ変更され、過剰なメモリが初期化され、新しい写真で書き換えられることです。直接評価しますが、新しいサイズに関する情報を使用して再割り当てを回避することはできません(この単純なケースでは、assignの実装はすでに最適化されているのでしょうか????)。
場合によります。反復子と[]演算子のみを使用してデータにアクセスする場合は、ベクトルを使用しても問題ありません。
たとえば、バッファを期待する関数へのポインタを与える必要がある場合バイト。私の意見ではありません。この場合、次のようなものを使用する必要があります
unique_ptr<unsigned char[]> buf(new unsigned char[size])
それはベクトルとして保存されますが、ベクトルの代わりにバッファを最大限に制御できます。ベクターはバッファーを再割り当てするか、メソッド/関数の呼び出し中に意図せずにベクター全体のコピーを作成する場合があります。間違いを犯しやすい。
ルールは(私にとって)です。ベクターがある場合は、ベクターのように使用します。メモリバッファが必要な場合は、メモリバッファを使用します。
指摘されたコメントのように、ベクターにはデータメソッドがあります。これはC++です。ベクトルを生のバッファとして使用する自由は、ベクトルを生のバッファとして使用する必要があることを意味しません。私の謙虚な意見では、ベクトルの意図は、タイプ保存アクセスシステムを備えたタイプ保存バッファを持つことでした。互換性のために、呼び出しに内部バッファーを使用できます。意図は、ベクターをスマートポインターバッファーコンテナーとして使用することではありませんでした。そのために、ポインターテンプレートを使用して、このバッファーを生の方法で使用することをコードの他のユーザーに通知します。ベクトルを使用する場合、提供される可能性のある方法ではなく、意図した方法で使用します。
ここで自分の意見(推奨ではない)で非難されたので、私はopが説明した実際の問題にいくつかの言葉を加えたい。
彼は常に同じ画像サイズを期待している場合、私の意見では、unique_ptrを使用する必要があります。を使用して
m_pImageBuffer.resize(uPixelCount, 0);
pPixelをコピーする前に最初にバッファをゼロにします。これは不必要な時間のペナルティです。
写真のサイズが異なる場合は、次の理由でベクトルを使用しないでください。特に彼のコードでは:
// called when a new image is available
if (m_pImageBuffer.size() != uPixelCount)
{
// resize image buffer
m_pImageBuffer.reserve(uPixelCount);
m_pImageBuffer.resize(uPixelCount, 0);
}
彼はベクトルのサイズを変更します。これは実際にはmallocであり、画像が大きくなる限りコピーします。私の経験では、reallocは常にmallocとcopyにつながります。
それが私が、特にこの状況で、ベクターではなくunique_ptrの使用を推奨する理由です。