ناقلات STL مع تخزين غير مهيأ؟

أنا أكتب حلقة داخلية تحتاج إلى وضع struct s في التخزين المتجاور. لا أعرف كم من هذه struct s سيكون هناك وقت مبكر. مشكلتي هي أن vector في STL تقوم بتهيئة قيمها إلى 0 ، لذا بغض النظر عن ما أفعله ، أتحمل تكلفة التهيئة بالإضافة إلى تكلفة إعداد أعضاء struct لقيمهم.

هل هناك أي طريقة لمنع التهيئة ، أو هل توجد حاوية شبيهة بـ STL هناك مع تخزين متجاور قابل للتغيير ، وعناصر غير مهيأة؟

(أنا متأكد من أن هذا الجزء من الشفرة يحتاج إلى تحسين ، وأنا متأكد من أن التهيئة تمثل تكلفة كبيرة.)

أيضًا ، راجع التعليقات أدناه للحصول على توضيح حول وقت حدوث التهيئة.

بعض الأكواد:

void GetsCalledALot(int* data1, int* data2, int count) {
    int mvSize = memberVector.size()
    memberVector.resize(mvSize + count);//causes 0-initialization

    for (int i = 0; i < count; ++i) {
        memberVector[mvSize + i].d1 = data1[i];
        memberVector[mvSize + i].d2 = data2[i];
    }
}
0
وأضاف تحرير
الآراء: 1
توضيح آخر: ليس أن المُنشئ يقوم بتهيئة القيم إلى 0. إنه تغيير حجم إدراج المكالمات ، الذي يفعل ذلك.
وأضاف المؤلف Jim Hunziker, مصدر
ملاحظة - لا يعد استخدام reserve() حلاً ، نظرًا لأنه لا يمكنك الوصول بشكل قانوني إلى البيانات الموجودة في نهاية المواقع() وما فوقها.
وأضاف المؤلف Jim Hunziker, مصدر
ملاحظة: لا يمكنك الوصول إلى البيانات غير المهيأة على أي حال. نفس المشكلة للمتجه الماضي .end() وأعضاء غير مهيأ من T []. ولكن مع وجود متجه ، فإن فرص تصحيح الأخطاء يخبرك بذلك الآن. سوف تفشل رمز الصفيف بصمت على أجهزة الكمبيوتر الشخصية للعملاء.
وأضاف المؤلف MSalters, مصدر
راجع أيضًا stackoverflow.com/q/7218574/1969455 للنهج المخصص. (جيد لأنواع POD)
وأضاف المؤلف Matthäus Brandl, مصدر
هل يمكن أن تعطينا إعلان الهيكل أيضا؟ شكر... :-)
وأضاف المؤلف paercebal, مصدر
هذا سؤال وجيه. بالنسبة لبعض التطبيقات ، من المهم إدراك أن std :: vector يقوم دائمًا بتهيئة عناصره ، حتى إذا كانت بيانات قديمة (POD).
وأضاف المؤلف nobar, مصدر

14 إجابة

إذن ، هذه هي المشكلة ، تغيير الحجم هو استدعاء إدراج ، والذي يقوم بإنشاء نسخة من عنصر تم إنشاؤه افتراضيًا لكل عنصر من العناصر المضافة حديثًا. للحصول على هذه التكلفة 0 تحتاج إلى كتابة المنشئ الافتراضي الخاص بك ومنشئ النسخة الخاصة بك كوظائف فارغة. إن القيام بذلك إلى مُنشئ النسخ الخاص بك هو فكرة سيئة للغاية لأنه سيؤدي إلى كسر خوارزميات إعادة تخصيص داخلي لـ std :: vector.

ملخص: لن تكون قادرًا على القيام بذلك مع std :: vector.

0
وأضاف
هذه هي القضية الحقيقية. std :: vector يجب أن يدرك أنه لا يتعين عليه القيام بأي عملية تهيئة إذا كان T لديه مُنشئ افتراضي تافه. شكرًا جزيلاً للإشارة إلى أن مُنشئ النسخة هو ما يفعل العمل غير الضروري هنا.
وأضاف المؤلف Eric Hein, مصدر

لتوضيح الإجابات() الاحتياطية: تحتاج إلى استخدام الاحتياطي() بالتزامن مع push_back (). بهذه الطريقة ، لا يتم استدعاء المُنشئ الافتراضي لكل عنصر ، بل مُنشئ النسخة. لا تزال تتكبد عقوبة إعداد البنية على المكدس ، ثم نسخها إلى المتجه. من ناحية أخرى ، من الممكن أنه إذا كنت تستخدم

vect.push_back(MyStruct(fieldValue1, fieldValue2))

سيقوم المترجم بإنشاء المثيل الجديد مباشرة في الذاكرة التي ينتقل إليها المتجه. يعتمد ذلك على مدى الذكية المحسن. تحتاج إلى التحقق من رمز ولدت لمعرفة ذلك.

0
وأضاف
وتبين أن المحسن لـ gcc ، في المستوى O3 ، ليس ذكيًا بما يكفي لتجنب النسخة.
وأضاف المؤلف Jim Hunziker, مصدر

يضيف C ++ 0x قالب دالة عضو جديد emplace_back إلى vector (والذي يعتمد على النماذج المتغيرة وإعادة التوجيه المثالية) الذي يتخلص من أي مؤقتات بالكامل:

memberVector.emplace_back(data1[i], data2[i]);
0
وأضاف

في C ++ 11 (و boost) يمكنك استخدام إصدار الصفيف unique_ptr لتخصيص صفيف غير مهيأ. هذه ليست تماما حاوية stl ، ولكن لا تزال تدار الذاكرة و C ++ - ish التي ستكون جيدة بما فيه الكفاية للعديد من التطبيقات.

auto my_uninit_array = std::unique_ptr(new mystruct[count]);
0
وأضاف

استخدم الأسلوب std :: vector :: reserve (). لن يقوم بتغيير حجم المتجه ، ولكنه سيخصص المساحة.

0
وأضاف

يخطئ ...

جرب الطريقة:

std::vector::reserve(x)

وسوف تمكنك من حجز ذاكرة كافية للعناصر x بدون تهيئة أي (لا يزال متجه الخاص بك فارغًا). وبالتالي ، لن يكون هناك إعادة تخصيص حتى يمر فوق x.

النقطة الثانية هي أن المتجه لن يقوم بتهيئة القيم إلى الصفر. هل تختبر شفرتك في تصحيح؟

بعد التحقق على g ++ ، الشفرة التالية:

#include 
#include 

struct MyStruct
{
   int m_iValue00 ;
   int m_iValue01 ;
} ;

int main()
{
   MyStruct aaa, bbb, ccc ;

   std::vector aMyStruct ;

   aMyStruct.push_back(aaa) ;
   aMyStruct.push_back(bbb) ;
   aMyStruct.push_back(ccc) ;

   aMyStruct.resize(6) ;//[EDIT] double the size

   for(std::vector::size_type i = 0, iMax = aMyStruct.size(); i < iMax; ++i)
   {
      std::cout << "[" << i << "] : " << aMyStruct[i].m_iValue00 << ", " << aMyStruct[0].m_iValue01 << "\n" ;
   }

   return 0 ;
}

يعطي النتائج التالية:

[0] : 134515780, -16121856
[1] : 134554052, -16121856
[2] : 134544501, -16121856
[3] : 0, -16121856
[4] : 0, -16121856
[5] : 0, -16121856

ربما كانت التهيئة التي رأيتها قطعة أثرية.

[EDIT] بعد التعليق على تغيير الحجم ، عدلت الشفرة لإضافة خط تغيير الحجم. إن عملية تغيير الحجم تستدعي على نحو فعال المُنشئ الافتراضي للكائن داخل المتجه ، ولكن إذا كان المُنشئ الافتراضي لا يفعل شيئًا ، فلن تتم تهيئة أي شيء ... ما زلت أعتقد أنه كان قطعة أثرية (تمكنت من أول مرة تم تجريد المتجه بأكمله من التعليمة البرمجية التالية:

aMyStruct.push_back(MyStruct()) ;
aMyStruct.push_back(MyStruct()) ;
aMyStruct.push_back(MyStruct()) ;

وبالتالي... : - /

[EDIT 2] كما هو الحال مع Arkadiy ، فإن الحل هو استخدام منشئ مضمّن يأخذ المعلمات المطلوبة. شيء مثل

struct MyStruct
{
   MyStruct(int p_d1, int p_d2) : d1(p_d1), d2(p_d2) {}
   int d1, d2 ;
} ;

من المحتمل أن يكون هذا مضمنًا في شفرتك.

ولكن يجب عليك على أي حال دراسة التعليمات البرمجية الخاصة بك مع أداة تعريف الصفحات للتأكد من هذه القطعة من التعليمات البرمجية هي عنق الزجاجة التطبيق الخاص بك.

0
وأضاف
كتبت ملاحظة أعلاه. إنه ليس مُنشئ المتجه الذي يبدأ بـ 0. إنه تغيير الحجم() الذي يفعل ذلك.
وأضاف المؤلف Jim Hunziker, مصدر
أعتقد أنك على الطريق الصحيح. ليس لدي مُنشئ مُعرَّف في البنية ، لذا فإن مُنشئه الافتراضي (على ما أظن) يعمل على تهيئة الصفر. سوف تحقق ما إذا كان إضافة مُنشئ افتراضي لا يفعل شيئًا يحل المشكلة.
وأضاف المؤلف Jim Hunziker, مصدر
جريج روجرز على حق. تخميني هو أن الذاكرة كانت "صفر" بسبب بعض عملية التهيئة مستقلة عن الكود الذي قمت بكتابته. في C ++ ، أنت لا تدفع مقابل شيء لا تستخدمه. لذا إذا كنت تكتب رمزًا C-like ، فلا يجب أن يكون لديك حمل. وناقلات جيدة جدا في ذلك.
وأضاف المؤلف paercebal, مصدر
nobar: ذلك يعتمد على منشئ MyStruct. إذا كانت فارغة ، ومضمونة ، وأعضاء MyStruct لديهم معدّلات تكلفة صفر ، فسيعمل مترجم C ++ على تحسينها إلى لا شيء. ثم ، لن ندفع ثمنها. فقط من أجل تغيير الحجم.
وأضاف المؤلف paercebal, مصدر
يبدو أن المتجه قد تركنا في هذه الحالة. نحن ندفع مقابل التهيئة ، حتى لو لم نكن بحاجة إليها أو نريدها. هذا مضمون من قبل دلالات من insert() الذي يسمى بواسطة تغيير الحجم (). تستند القيمة المستخدمة للتهيئة على أي شيء يحدث في MyStruct تم تمريره لتغيير حجم (). نظرًا لأنك لم تحدد أي شيء عند استدعاءك لتغيير الحجم() ، فقد تم استخدام المُنشئ الافتراضي. نظرًا لأن المُنشئ الافتراضي لا يفعل شيئًا في هذه الحالة ، فقد تحصل على أصفار أو قد تحصل على شيء آخر. في كلتا الحالتين ، تدفع عن التهيئة التي نفذتها بتغيير ().
وأضاف المؤلف nobar, مصدر
إذا لم يكن لديك مُنشئ مُعرَّف وكانت جميع عناصره أنواع POD ، فإن المُنشئ لا يفعل شيئًا. إذا كانت العناصر ليست POD ، فستتصل ببساطة بالمصنّعين الافتراضيين.
وأضاف المؤلف Greg Rogers, مصدر
في هذه الحالة ، يحتوي MyStruct على منشئ بسيط حتى لا يتم تهيئة أي شيء. قد يكون هذا مختلفًا عن وضع OP.
وأضاف المؤلف Greg Rogers, مصدر

هل تحتاج البنى نفسها إلى أن تكون في ذاكرة متجاورة ، أم يمكنك أن تفلت من وجود متجه للبنية *؟

Vectors make a copy of whatever you add to them, so using vectors of pointers rather than objects is one way to improve performance.

0
وأضاف
يجب أن تكون متجاورة. إنهم في مخزن مؤقت على وشك إرساله عبر الشبكة كقطعة ضخمة.
وأضاف المؤلف Jim Hunziker, مصدر

لا أعتقد أن المحكمة الخاصة بلبنان هي إجابتك. ستحتاج إلى لفك نوع الحل الخاص بك باستخدام realloc (). سيكون عليك تخزين مؤشر وإما الحجم ، أو عدد العناصر ، واستخدام ذلك للعثور على مكان بدء إضافة عناصر بعد realloc ().

int *memberArray;
int arrayCount;
void GetsCalledALot(int* data1, int* data2, int count) {
    memberArray = realloc(memberArray, sizeof(int) * (arrayCount + count);
    for (int i = 0; i < count; ++i) {
        memberArray[arrayCount + i].d1 = data1[i];
        memberArray[arrayCount + i].d2 = data2[i];
    }
    arrayCount += count;
}
0
وأضاف

من تعليقاتك إلى ملصقات أخرى ، يبدو أنك تركت مع malloc() والأصدقاء. لن يسمح لك Vector بوجود عناصر غير منشورة.

0
وأضاف

من الرمز الخاص بك ، يبدو أن لديك متجهًا للبنيات التي يتكون كل منها من 2 نواة. هل يمكنك بدلاً من ذلك استخدام متجهين من النتوءات؟ ثم

copy(data1, data1 + count, back_inserter(v1));
copy(data2, data2 + count, back_inserter(v2));

الآن لا تدفع مقابل نسخ البنية في كل مرة.

0
وأضاف
مثير للإعجاب. قد يعمل هذا فقط - يبدو أنه سيتجنب إنشاء كائن وسيط.
وأضاف المؤلف nobar, مصدر

كنت سأفعل شيئًا مثل:

void GetsCalledALot(int* data1, int* data2, int count)
{
  const size_t mvSize = memberVector.size();
  memberVector.reserve(mvSize + count);

  for (int i = 0; i < count; ++i) {
    memberVector.push_back(MyType(data1[i], data2[i]));
  }
}

تحتاج إلى تحديد ctor للنوع الذي تم تخزينه في memberVector ، ولكن هذه تكلفة صغيرة لأنها سوف تعطيك أفضل ما في العالمين. لا يتم إجراء التهيئة غير الضرورية ولن يحدث أي إعادة تخصيص خلال الحلقة.

0
وأضاف
يبدو أن هذا لا يحل المشكلة لأنه يستخدم MyType() مؤقت ونسخ ذلك في المتجه. لا يزال هناك تهيئة مزدوجة.
وأضاف المؤلف nobar, مصدر

std::vector must initialize the values in the array somehow, which means some constructor (or copy-constructor) must be called. The behavior of vector (or any container class) is undefined if you were to access the uninitialized section of the array as if it were initialized.

أفضل طريقة هي استخدام Reserve() و push_back() ، بحيث يتم استخدام منشئ النسخ ، وتجنب الإنشاء الافتراضي.

باستخدام رمز المثال الخاص بك:

struct YourData {
    int d1;
    int d2;
    YourData(int v1, int v2) : d1(v1), d2(v2) {}
};

std::vector memberVector;

void GetsCalledALot(int* data1, int* data2, int count) {
    int mvSize = memberVector.size();

   //Does not initialize the extra elements
    memberVector.reserve(mvSize + count);

   //Note: consider using std::generate_n or std::copy instead of this loop.
    for (int i = 0; i < count; ++i) {
       //Copy construct using a temporary.
        memberVector.push_back(YourData(data1[i], data2[i]));
    }
}

المشكلة الوحيدة مع استدعاء احتياطي() (أو تغيير الحجم() ) مثل هذا هو أنه قد ينتهي بك الأمر استدعاء منشئ النسخ في أكثر الأحيان مما تحتاج إليه. إذا تمكنت من توقع جيد بالنسبة للحجم النهائي للمصفوفة ، فمن الأفضل حجز() المساحة مرة واحدة في البداية. إذا كنت لا تعرف الحجم النهائي ، فسيكون عدد النسخ على الأقل في المتوسط.

In the current version of C++, the inner loop is a bit inefficient as a temporary value is constructed on the stack, copy-constructed to the vectors memory, and finally the temporary is destroyed. However the next version of C++ has a feature called R-Value references (T&&) which will help.

لا تسمح الواجهة المتوفرة من std :: vector بخيار آخر ، وهو استخدام فئة تشبه المصنّع لإنشاء قيم أخرى بخلاف القيمة الافتراضية. في ما يلي مثال تقريبي لما سيبدو عليه هذا النموذج في C ++:

template 
class my_vector_replacement {

   //...

    template 
    my_vector::push_back_using_factory(F factory) {
       //... check size of array, and resize if needed.

       //Copy construct using placement new,
        new(arrayData+end) T(factory())
        end += sizeof(T);
    }

    char* arrayData;
    size_t end;//Of initialized data in arrayData
};

// One of many possible implementations
struct MyFactory {
    MyFactory(int* p1, int* p2) : d1(p1), d2(p2) {}
    YourData operator()() const {
        return YourData(*d1,*d2);
    }
    int* d1;
    int* d2;
};

void GetsCalledALot(int* data1, int* data2, int count) {
   //... Still will need the same call to a reserve() type function.

   //Note: consider using std::generate_n or std::copy instead of this loop.
    for (int i = 0; i < count; ++i) {
       //Copy construct using a factory
        memberVector.push_back_using_factory(MyFactory(data1+i, data2+i));
    }
}

يعني ذلك أن عليك إنشاء فئة المتجه الخاصة بك. في هذه الحالة ، يعقّد أيضًا ما كان يجب أن يكون مثالًا بسيطًا. ولكن قد تكون هناك أوقات يكون فيها استخدام وظيفة مصنع مثل هذا أفضل ، على سبيل المثال إذا كانت الإضافة مشروطة ببعض القيم الأخرى ، وكان عليك أن تقوم بشكل غير مشروط ببناء بعض التكاليف المؤقتة حتى إذا لم تكن هناك حاجة فعلية لذلك.

0
وأضاف

إذا كنت تصر حقاً على وجود عناصر غير محسّنة وتضحي ببعض الطرق مثل front() ، back() ، push_back() ، استخدم الموجه المعزز من الأرقام. يسمح لك حتى لا للحفاظ على العناصر الموجودة عند استدعاء تغيير حجم() ...

0
وأضاف

يمكنك استخدام نوع مجمّع حول نوع العنصر ، باستخدام مُنشئ افتراضي لا يفعل شيئًا. على سبيل المثال:

template 
struct no_init
{
    T value;

    no_init() { static_assert(std::is_standard_layout>::value && sizeof(T) == sizeof(no_init), "T does not have standard layout"); }

    no_init(T& v) { value = v; }
    T& operator=(T& v) { value = v; return value; }

    no_init(no_init& n) { value = n.value; }
    no_init(no_init&& n) { value = std::move(n.value); }
    T& operator=(no_init& n) { value = n.value; return this; }
    T& operator=(no_init&& n) { value = std::move(n.value); return this; }

    T* operator&() { return &value; }//So you can use &(vec[0]) etc.
};

ليستخدم:

std::vector> vec;
vec.resize(2ul * 1024ul * 1024ul * 1024ul);
0
وأضاف