std :: vector ช้ากว่าอาเรย์ธรรมดามากหรือไม่?


212

ฉันมักจะคิดว่ามันเป็นภูมิปัญญาทั่วไปที่std::vector"นำไปใช้เป็นอาร์เรย์" blah blah blah วันนี้ฉันลงไปทดสอบและดูเหมือนว่าจะไม่เป็นเช่นนั้น:

นี่คือผลการทดสอบบางส่วน:

UseArray completed in 2.619 seconds
UseVector completed in 9.284 seconds
UseVectorPushBack completed in 14.669 seconds
The whole thing completed in 26.591 seconds

นั่นคือช้าลงประมาณ 3 - 4 เท่า! ไม่ปรับให้เหมาะสมกับความคิดเห็นที่ " vectorอาจช้ากว่าสำหรับ nanosecs สักสองสาม"

และรหัสที่ฉันใช้:

#include <cstdlib>
#include <vector>

#include <iostream>
#include <string>

#include <boost/date_time/posix_time/ptime.hpp>
#include <boost/date_time/microsec_time_clock.hpp>

class TestTimer
{
    public:
        TestTimer(const std::string & name) : name(name),
            start(boost::date_time::microsec_clock<boost::posix_time::ptime>::local_time())
        {
        }

        ~TestTimer()
        {
            using namespace std;
            using namespace boost;

            posix_time::ptime now(date_time::microsec_clock<posix_time::ptime>::local_time());
            posix_time::time_duration d = now - start;

            cout << name << " completed in " << d.total_milliseconds() / 1000.0 <<
                " seconds" << endl;
        }

    private:
        std::string name;
        boost::posix_time::ptime start;
};

struct Pixel
{
    Pixel()
    {
    }

    Pixel(unsigned char r, unsigned char g, unsigned char b) : r(r), g(g), b(b)
    {
    }

    unsigned char r, g, b;
};

void UseVector()
{
    TestTimer t("UseVector");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel> pixels;
        pixels.resize(dimension * dimension);

        for(int i = 0; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }
    }
}

void UseVectorPushBack()
{
    TestTimer t("UseVectorPushBack");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel> pixels;
            pixels.reserve(dimension * dimension);

        for(int i = 0; i < dimension * dimension; ++i)
            pixels.push_back(Pixel(255, 0, 0));
    }
}

void UseArray()
{
    TestTimer t("UseArray");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        Pixel * pixels = (Pixel *)malloc(sizeof(Pixel) * dimension * dimension);

        for(int i = 0 ; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }

        free(pixels);
    }
}

int main()
{
    TestTimer t1("The whole thing");

    UseArray();
    UseVector();
    UseVectorPushBack();

    return 0;
}

ฉันทำผิดหรือเปล่า หรือว่าฉันเพิ่งถูกจับตำนานการแสดงนี้?

ฉันใช้โหมดที่วางจำหน่ายในVisual Studio 2005


ในVisual C ++ , #define _SECURE_SCL 0ลดUseVectorลงครึ่งหนึ่ง (นำมันลงไป 4 วินาที) นี่คือขนาดใหญ่จริงๆ IMO


23
เวกเตอร์บางเวอร์ชันเมื่อคุณอยู่ในโหมดดีบั๊กเพิ่มคำแนะนำพิเศษเพื่อตรวจสอบว่าคุณไม่สามารถเข้าถึงได้เกินกว่าอาเรย์ปลายและสิ่งต่างๆเช่นนั้น ในการรับเวลาจริงคุณต้องสร้างในโหมดเผยแพร่และเปิดการปรับให้เหมาะสม
Martin York

40
เป็นเรื่องดีที่คุณวัดได้แทนที่จะเชื่อว่าคุณอ้างว่าได้ยินทางอินเทอร์เน็ต
P Shved

51
vector ถูกใช้เป็นอาร์เรย์ นั่นไม่ใช่ "ภูมิปัญญาดั้งเดิม" แต่เป็นความจริง คุณได้ค้นพบว่าvectorเป็นอาร์เรย์ที่ปรับขนาดได้ตามวัตถุประสงค์ทั่วไป ขอแสดงความยินดี เช่นเดียวกับเครื่องมือที่ใช้งานทั่วไปทั้งหมดจึงเป็นไปได้ที่จะเกิดสถานการณ์เฉพาะที่เหมาะสมที่สุด ซึ่งเป็นเหตุผลที่ภูมิปัญญาดั้งเดิมคือการเริ่มต้นด้วยvectorและพิจารณาทางเลือกถ้าจำเป็น
Dennis Zickefoose

37
ฮ่า ๆ อะไรคือความแตกต่างของความเร็วในการ "โยนจานสกปรกลงในอ่างล้างจาน" และ "โยนจานสกปรกลงในอ่างล้างจานและตรวจสอบว่าพวกเขาไม่แตก" หรือไม่?
Imre L

9
อย่างน้อยใน VC2010 ดูเหมือนว่าความแตกต่างที่สำคัญคือ malloc () นั้นเร็วกว่าการปรับขนาด () ลบการจัดสรรหน่วยความจำออกจากเวลารวบรวมด้วย _ITERATOR_DEBUG_LEVEL == 0 และผลลัพธ์จะเหมือนกัน
Andreas Magnusson

คำตอบ:


260

ใช้ดังต่อไปนี้:

g ++ -O3 Time.cpp -I <MyBoost>
./a.out
UseArray เสร็จสมบูรณ์ใน 2.196 วินาที
UseVector เสร็จสิ้นใน 4.412 วินาที
UseVectorPushBack เสร็จสมบูรณ์ใน 8.017 วินาที
สิ่งทั้งหมดเสร็จใน 14.626 วินาที

ดังนั้นอาร์เรย์จึงเร็วเป็นสองเท่าของเวกเตอร์

แต่หลังจากดูรายละเอียดของรหัสนี้แล้ว ในขณะที่คุณวิ่งข้ามเวกเตอร์สองครั้งและอาร์เรย์เพียงครั้งเดียว หมายเหตุ: เมื่อคุณresize()เวกเตอร์คุณไม่เพียง แต่จัดสรรหน่วยความจำ แต่ยังทำงานผ่านเวกเตอร์และเรียกตัวสร้างในแต่ละสมาชิก

จัดรหัสใหม่อีกครั้งเพื่อให้เวกเตอร์เริ่มต้นแต่ละวัตถุเพียงครั้งเดียว:

 std::vector<Pixel>  pixels(dimensions * dimensions, Pixel(255,0,0));

ตอนนี้ทำเวลาเดียวกันอีกครั้ง:

g ++ -O3 Time.cpp -I <MyBoost>
./a.out
UseVector เสร็จสมบูรณ์ใน 2.216 วินาที

เวกเตอร์ตอนนี้มีประสิทธิภาพแย่กว่าอาเรย์เพียงเล็กน้อยเท่านั้น IMO ความแตกต่างนี้ไม่มีนัยสำคัญและอาจเกิดจากสิ่งต่าง ๆ ที่ไม่เกี่ยวข้องกับการทดสอบ

ฉันยังคำนึงถึงว่าคุณไม่ได้เริ่มต้นอย่างถูกต้อง / ทำลายวัตถุพิกเซลในUseArrray()วิธีการที่ไม่เรียกว่าคอนสตรัคเตอร์ / destructor (นี้อาจไม่เป็นปัญหาสำหรับชั้นเรียนที่เรียบง่ายนี้ แต่สิ่งที่ซับซ้อนกว่าเล็กน้อย (เช่นพอยน์เตอร์หรือสมาชิก ด้วยตัวชี้) จะทำให้เกิดปัญหา


48
@ kizzx2: คุณจำเป็นต้องใช้แทนreserve() resize()สิ่งนี้จัดสรรพื้นที่สำหรับวัตถุ (นั่นคือมันจะเปลี่ยนความจุของเวกเตอร์) แต่ไม่ได้สร้างวัตถุ (นั่นคือขนาดของเวกเตอร์จะไม่เปลี่ยนแปลง)
James McNellis

25
คุณกำลังเข้าถึงอาร์เรย์ 1,000,000 000 ความแตกต่างของเวลาคือ 0.333 วินาที หรือความแตกต่างของ 0.000000000333 ต่อการเข้าถึงอาร์เรย์ สมมติว่าตัวประมวลผล 2.33 GHz เหมือนของฉันที่ 0.7 ขั้นตอนการส่งคำสั่งต่อการเข้าถึงอาร์เรย์ เวกเตอร์ดูเหมือนว่ามันใช้คำสั่งพิเศษหนึ่งคำสั่งต่อการเข้าถึงหนึ่งครั้ง
Martin York

3
@James McNellis: คุณไม่สามารถแทนที่resize()ด้วยreserve()เพราะสิ่งนี้ไม่ได้ปรับความคิดภายในของเวกเตอร์ที่มีขนาดของมันเองดังนั้นการเขียนองค์ประกอบของมันในภายหลังจึงเป็นเทคนิคในการ "เขียนจนจบ" และจะสร้าง UB แม้ว่าในทางปฏิบัติทุกการใช้งาน STL จะ "ประพฤติตน" ในเรื่องนั้นคุณจะซิงค์ขนาดของเวกเตอร์อย่างไร ถ้าคุณลองโทรresize() หลังจากเติมเวกเตอร์ลงไปมันอาจจะเขียนทับองค์ประกอบเหล่านั้นทั้งหมดด้วยค่าที่สร้างขึ้นเป็นค่าเริ่มต้น!
j_random_hacker

8
@j_random_hacker: นั่นไม่ใช่สิ่งที่ฉันพูดใช่มั้ย ฉันคิดว่าฉันชัดเจนมากที่reserveเปลี่ยนความสามารถของเวกเตอร์ไม่ใช่ขนาดของมัน
James McNellis

7
ตกลงไปคิดกัน มี cruft ที่เกี่ยวข้องกับข้อยกเว้นจำนวนมากในวิธีการของเวกเตอร์ การเพิ่ม/EHscสวิตช์การคอมไพล์เป็นการทำความสะอาดที่ขึ้นและassign()จริง ๆ แล้วเต้นแถวตอนนี้ เย้.
Pavel Minaev

55

เป็นคำถามที่ดีมาก ฉันเข้ามาที่นี่โดยหวังว่าจะพบการแก้ไขง่ายๆที่จะเพิ่มความเร็วในการทดสอบเวคเตอร์ขึ้น นั่นไม่ได้ผลเท่าที่ฉันคาดไว้!

การเพิ่มประสิทธิภาพช่วย แต่ก็ไม่เพียงพอ ด้วยการเพิ่มประสิทธิภาพที่ฉันยังเห็นความแตกต่างของประสิทธิภาพ 2X ระหว่าง UseArray และ UseVector ที่น่าสนใจ UseVector ช้ากว่า UseVectorPushBack มากโดยไม่มีการเพิ่มประสิทธิภาพ

# g++ -Wall -Wextra -pedantic -o vector vector.cpp
# ./vector
UseArray completed in 20.68 seconds
UseVector completed in 120.509 seconds
UseVectorPushBack completed in 37.654 seconds
The whole thing completed in 178.845 seconds
# g++ -Wall -Wextra -pedantic -O3 -o vector vector.cpp
# ./vector
UseArray completed in 3.09 seconds
UseVector completed in 6.09 seconds
UseVectorPushBack completed in 9.847 seconds
The whole thing completed in 19.028 seconds

แนวคิด # 1 - ใช้ใหม่ [] แทน malloc

ฉันลองเปลี่ยนmalloc()เป็นnew[]UseArray เพื่อให้วัตถุถูกสร้างขึ้น และการเปลี่ยนจากการมอบหมายฟิลด์เฉพาะเป็นการกำหนดอินสแตนซ์ Pixel Oh, jและเปลี่ยนชื่อตัวแปรภายในวงไป

void UseArray()
{
    TestTimer t("UseArray");

    for(int i = 0; i < 1000; ++i)
    {   
        int dimension = 999;

        // Same speed as malloc().
        Pixel * pixels = new Pixel[dimension * dimension];

        for(int j = 0 ; j < dimension * dimension; ++j)
            pixels[j] = Pixel(255, 0, 0);

        delete[] pixels;
    }
}

น่าประหลาดใจที่ฉันไม่มีการเปลี่ยนแปลงใด ๆ ที่ทำให้เกิดความแตกต่างใด ๆ ไม่แม้แต่การเปลี่ยนแปลงnew[]ที่จะเริ่มต้นสร้างพิกเซลทั้งหมด มันดูเหมือนว่า GCC สามารถเพิ่มประสิทธิภาพการสายสร้างเริ่มต้นเมื่อใช้แต่ไม่ได้เมื่อใช้new[]vector

ความคิด # 2 - ลบผู้โทรออก [] ซ้ำ ๆ

ฉันยังพยายามที่จะกำจัดของสามการค้นหาและแคชอ้างอิงถึงoperator[] pixels[j]ที่จริงแล้ว UseVector ช้าลง! อุ่ย

for(int j = 0; j < dimension * dimension; ++j)
{
    // Slower than accessing pixels[j] three times.
    Pixel &pixel = pixels[j];
    pixel.r = 255;
    pixel.g = 0;
    pixel.b = 0;
}

# ./vector 
UseArray completed in 3.226 seconds
UseVector completed in 7.54 seconds
UseVectorPushBack completed in 9.859 seconds
The whole thing completed in 20.626 seconds

ความคิด # 3 - ลบตัวสร้าง

แล้วการลบสิ่งก่อสร้างทั้งหมด? จากนั้นบางที gcc สามารถปรับโครงสร้างของวัตถุทั้งหมดให้เหมาะสมเมื่อสร้างเวกเตอร์ จะเกิดอะไรขึ้นถ้าเราเปลี่ยน Pixel เป็น:

struct Pixel
{
    unsigned char r, g, b;
};

ผลลัพธ์: เร็วขึ้นประมาณ 10% ยังช้ากว่าอาเรย์ ฮึ่ม

# ./vector 
UseArray completed in 3.239 seconds
UseVector completed in 5.567 seconds

แนวคิด # 4 - ใช้ตัววนซ้ำแทนดัชนีลูป

วิธีการเกี่ยวกับการใช้vector<Pixel>::iteratorแทนดัชนีห่วง?

for (std::vector<Pixel>::iterator j = pixels.begin(); j != pixels.end(); ++j)
{
    j->r = 255;
    j->g = 0;
    j->b = 0;
}

ผลลัพธ์:

# ./vector 
UseArray completed in 3.264 seconds
UseVector completed in 5.443 seconds

ไม่ไม่แตกต่างกัน อย่างน้อยก็ไม่ช้า ฉันคิดว่าสิ่งนี้จะมีประสิทธิภาพคล้ายกับ # 2 ซึ่งฉันใช้การPixel&อ้างอิง

ข้อสรุป

std::vectorแม้ว่าบางตัวเลขคุกกี้สมาร์ทวิธีที่จะทำให้วงเวกเตอร์ให้เร็วที่สุดเท่าอาร์เรย์หนึ่งนี้ไม่ได้พูดคุยกันของพฤติกรรมเริ่มต้นของ มีมากพอสำหรับคอมไพเลอร์ที่ฉลาดพอที่จะเพิ่มประสิทธิภาพของ C ++ ness ทั้งหมดและทำให้คอนเทนเนอร์ STL เร็วเท่าอาเรย์ดิบ

บรรทัดล่างคือว่าคอมไพเลอร์จะไม่สามารถที่จะเพิ่มประสิทธิภาพออกไปไม่มี-op std::vectorโทรสร้างเริ่มต้นเมื่อใช้ ถ้าคุณใช้ธรรมดาnew[]มันปรับให้ดีขึ้นได้ดี std::vectorแต่ไม่ได้มี แม้ว่าคุณจะสามารถเขียนโค้ดของคุณใหม่เพื่อกำจัดการเรียก Constructor ที่ลอยอยู่ในหน้ามนต์ที่นี่: "คอมไพเลอร์ฉลาดกว่าคุณ STL เร็วพอ ๆ กับ C ไม่ต้องกังวลกับมัน"


2
ขอขอบคุณอีกครั้งที่ใช้รหัส บางครั้งมันง่ายที่จะถูกทุบโดยไม่มีเหตุผลเมื่อมีคนพยายามท้าทายความคิดเห็นยอดนิยม
kizzx2

3
"มีประโยชน์มากสำหรับคอมไพเลอร์ที่ฉลาดพอที่จะปรับ Cess Ness ทั้งหมดให้เหมาะสมและสร้างคอนเทนเนอร์ STL ให้เร็วที่สุดเท่าที่อาร์เรย์ดิบ" ความคิดเห็นที่ดี ฉันมีทฤษฎีที่ว่า "คอมไพเลอร์เป็นสมาร์ท" เป็นเพียงแค่ตำนาน - การแยกวิเคราะห์ C ++ นั้นยากมากและคอมไพเลอร์เป็นเพียงเครื่องจักร
kizzx2

3
ฉันไม่รู้. แน่นอนว่าเขาสามารถชะลอการทดสอบอาเรย์ได้ แต่เขาไม่ได้เพิ่มความเร็วเวคเตอร์ ฉันแก้ไขข้างต้นโดยที่ฉันลบตัวสร้างออกจาก Pixel และทำให้มันเป็นโครงสร้างที่เรียบง่ายและมันก็ยังช้า vector<int>นั่นเป็นข่าวร้ายสำหรับผู้ใช้ประเภทง่ายๆเช่น
John Kugelman

2
ฉันหวังว่าฉันจะสามารถตอบคำถามของคุณได้สองครั้ง ความคิดที่ชาญฉลาดที่จะลอง (แม้ว่าจะไม่มีใครทำได้จริง ๆ ) ที่ฉันไม่สามารถนึกได้!
kizzx2

9
แค่ต้องการให้ทราบว่าความซับซ้อนของการแยก C ++ (ซึ่งซับซ้อนอย่างบ้าคลั่งใช่) ไม่มีส่วนเกี่ยวข้องกับคุณภาพของการเพิ่มประสิทธิภาพ หลังมักจะเกิดขึ้นในขั้นตอนที่ผลการแยกวิเคราะห์แล้วแปลงหลายครั้งเพื่อเป็นตัวแทนระดับต่ำมากขึ้น
Pavel Minaev

44

นี่เป็นคำถามเก่า แต่เป็นที่นิยม

ณ จุดนี้โปรแกรมเมอร์จำนวนมากจะทำงานใน C ++ 11 และใน C ++ 11 รหัส OP ในขณะที่วิ่งเขียนอย่างเท่าเทียมกันได้อย่างรวดเร็วสำหรับหรือUseArrayUseVector

UseVector completed in 3.74482 seconds
UseArray completed in 3.70414 seconds

ปัญหาพื้นฐานคือขณะที่Pixelโครงสร้างของคุณไม่ได้std::vector<T>::resize( size_t, T const&=T() )กำหนดค่าเริ่มต้นให้สร้างค่าเริ่มต้นPixelและคัดลอกมา คอมไพเลอร์ไม่ได้สังเกตว่ากำลังขอให้คัดลอกข้อมูลที่ไม่มีการกำหนดค่าเริ่มต้นดังนั้นจึงทำการคัดลอกจริง

ใน C ++ 11 std::vector<T>::resizeมีสองโอเวอร์โหลด เป็นครั้งแรกที่มีที่std::vector<T>::resize(size_t)อื่น ๆ std::vector<T>::resize(size_t, T const&)ที่เป็น ซึ่งหมายความว่าเมื่อคุณเรียกใช้resizeโดยไม่มีอาร์กิวเมนต์ที่สองมันเป็นเพียงการสร้างเริ่มต้นและคอมไพเลอร์ฉลาดพอที่จะรู้ว่าการสร้างเริ่มต้นไม่ได้ทำอะไรเลยดังนั้นจึงข้ามการส่งผ่านบัฟเฟอร์

(โอเวอร์โหลดสองอันที่เพิ่มเข้ามาเพื่อจัดการกับชนิดที่เคลื่อนย้ายได้, ที่สามารถสร้างได้และไม่สามารถคัดลอกได้ - การปรับปรุงประสิทธิภาพเมื่อทำงานกับข้อมูลที่ไม่ได้กำหนดค่าเริ่มต้นเป็นโบนัส)

push_backวิธีการแก้ปัญหาก็คือการตรวจสอบรั้วที่ช้าลงดังนั้นมันจึงช้ากว่าmallocเวอร์ชั่น

ตัวอย่างสด (ฉันยังแทนที่ตัวจับเวลาด้วยchrono::high_resolution_clock)

โปรดทราบว่าถ้าคุณมีโครงสร้างที่ต้องเริ่มต้น แต่คุณต้องการจัดการมันหลังจากเพิ่มบัฟเฟอร์คุณสามารถทำได้ด้วยstd::vectorตัวจัดสรรที่กำหนดเอง หากคุณต้องการย้ายมันให้เป็นแบบปกติมากขึ้นstd::vectorฉันเชื่อว่าการใช้allocator_traitsและการเอาชนะอย่างระมัดระวัง==อาจดึงสิ่งนั้นออก แต่ไม่แน่ใจ


ยังน่าสนใจที่จะดูว่าemplace_backvs push_backที่นี่
แดเนียล

1
ฉันไม่สามารถทำซ้ำผลลัพธ์ของคุณ รวบรวมรหัสของคุณclang++ -std=c++11 -O3มีและUseArray completed in 2.02e-07 seconds UseVector completed in 1.3026 secondsฉันยังเพิ่มUseVectorEmplaceBackเวอร์ชันที่ประมาณ 2.5x UseVectorPushBackเร็วที่สุดเท่าที่
แดเนียล

1
@daniel odds เป็นเครื่องมือเพิ่มประสิทธิภาพลบทุกอย่างจากรุ่นอาร์เรย์ มีความเสี่ยงเสมอด้วยการวัดขนาดเล็ก
Yakk - Adam Nevraumont

4
ใช่คุณถูกต้องเพียงแค่ดูที่การชุมนุม (หรือขาดมัน) .. น่าจะมีความคิดเกี่ยวกับสิ่งนั้นที่มีความแตกต่าง ~ 6448514x! ฉันสงสัยว่าทำไมเวอร์ชันเวกเตอร์ไม่สามารถปรับให้เหมาะสมแบบเดียวกันได้ .. ถ้าสร้างด้วยขนาดแทนที่จะปรับขนาด
แดเนียล

34

เพื่อความเป็นธรรมคุณไม่สามารถเปรียบเทียบการใช้งาน C ++ กับการใช้งาน C ได้เพราะฉันจะเรียกรุ่น malloc ของคุณ malloc ไม่ได้สร้างวัตถุ - จัดสรรหน่วยความจำดิบเท่านั้น จากนั้นคุณปฏิบัติต่อหน่วยความจำนั้นเป็นวัตถุโดยไม่ต้องเรียกตัวสร้างว่า C ++ ไม่ดี (อาจไม่ถูกต้อง - ฉันจะปล่อยให้มันเป็นทนายความด้านภาษา)

ที่กล่าวว่าเพียงแค่เปลี่ยน malloc เป็นnew Pixel[dimensions*dimensions]และฟรีเพื่อdelete [] pixelsไม่สร้างความแตกต่างมากกับการใช้งานง่าย ๆ ของ Pixel ที่คุณมี นี่คือผลลัพธ์ในกล่องของฉัน (E6600, 64- บิต):

UseArray completed in 0.269 seconds
UseVector completed in 1.665 seconds
UseVectorPushBack completed in 7.309 seconds
The whole thing completed in 9.244 seconds

แต่ด้วยการเปลี่ยนแปลงเล็กน้อยตารางเปิด:

Pixel.h

struct Pixel
{
    Pixel();
    Pixel(unsigned char r, unsigned char g, unsigned char b);

    unsigned char r, g, b;
};

Pixel.cc

#include "Pixel.h"

Pixel::Pixel() {}
Pixel::Pixel(unsigned char r, unsigned char g, unsigned char b) 
  : r(r), g(g), b(b) {}

main.cc

#include "Pixel.h"
[rest of test harness without class Pixel]
[UseArray now uses new/delete not malloc/free]

รวบรวมด้วยวิธีนี้:

$ g++ -O3 -c -o Pixel.o Pixel.cc
$ g++ -O3 -c -o main.o main.cc
$ g++ -o main main.o Pixel.o

เราได้รับผลลัพธ์ที่แตกต่างกันมาก:

UseArray completed in 2.78 seconds
UseVector completed in 1.651 seconds
UseVectorPushBack completed in 7.826 seconds
The whole thing completed in 12.258 seconds

ด้วย Constructor ที่ไม่ถูก Inline สำหรับ Pixel ตอนนี้ std :: vector จะเต้นเป็นอาเรย์ดิบ

ก็ปรากฏว่าความซับซ้อนของการจัดสรรผ่านมาตรฐาน :: เวกเตอร์และมาตรฐาน: new Pixel[n]จัดสรรมากเกินไปที่จะเพิ่มประสิทธิภาพอย่างมีประสิทธิภาพเป็นที่เรียบง่าย อย่างไรก็ตามเราจะเห็นว่าปัญหานั้นเกิดจากการจัดสรรไม่ใช่การเข้าถึงแบบเวกเตอร์โดยการปรับแต่งฟังก์ชั่นการทดสอบเพื่อสร้างเวกเตอร์ / อาเรย์หนึ่งครั้งโดยการย้ายออกนอกวง:

void UseVector()
{
    TestTimer t("UseVector");

    int dimension = 999;
    std::vector<Pixel> pixels;
    pixels.resize(dimension * dimension);

    for(int i = 0; i < 1000; ++i)
    {
        for(int i = 0; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }
    }
}

และ

void UseArray()
{
    TestTimer t("UseArray");

    int dimension = 999;
    Pixel * pixels = new Pixel[dimension * dimension];

    for(int i = 0; i < 1000; ++i)
    {
        for(int i = 0 ; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }
    }
    delete [] pixels;
}

เราได้ผลลัพธ์เหล่านี้แล้ว:

UseArray completed in 0.254 seconds
UseVector completed in 0.249 seconds
UseVectorPushBack completed in 7.298 seconds
The whole thing completed in 7.802 seconds

สิ่งที่เราสามารถเรียนรู้ได้จากสิ่งนี้คือ std :: vector เปรียบได้กับอาเรย์ดิบสำหรับการเข้าถึง แต่ถ้าคุณต้องการสร้างและลบเวกเตอร์ / อาเรย์หลายครั้งการสร้างออบเจ็กต์ที่ซับซ้อนจะใช้เวลามากขึ้น เมื่อคอนสตรัคเตอร์ขององค์ประกอบไม่ได้ inlined ฉันไม่คิดว่ามันน่าแปลกใจมาก


3
คุณยังมีคอนสตรัคเตอร์แบบอินไลน์ - ตัวสร้างสำเนา
Ben Voigt

26

ลองกับสิ่งนี้:

void UseVectorCtor()
{
    TestTimer t("UseConstructor");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel> pixels(dimension * dimension, Pixel(255, 0, 0));
    }
}

ฉันได้รับประสิทธิภาพเกือบจะเหมือนกับอาร์เรย์

สิ่งที่เกี่ยวกับvectorคือมันเป็นเครื่องมือทั่วไปมากกว่าอาร์เรย์ และนั่นหมายความว่าคุณต้องพิจารณาว่าคุณใช้มันอย่างไร มันสามารถใช้งานได้หลายวิธีโดยมีฟังก์ชั่นการทำงานที่อาร์เรย์ไม่ได้มี และถ้าคุณใช้มัน "ผิด" สำหรับวัตถุประสงค์ของคุณคุณจะได้รับค่าใช้จ่ายจำนวนมาก แต่ถ้าคุณใช้อย่างถูกต้องก็มักจะเป็นโครงสร้างข้อมูลศูนย์ค่าใช้จ่าย ในกรณีนี้ปัญหาคือคุณเริ่มต้นเวกเตอร์แยกต่างหาก (ทำให้องค์ประกอบทั้งหมดมี ctor เริ่มต้นเรียกว่า) จากนั้นเขียนทับแต่ละองค์ประกอบด้วยค่าที่ถูกต้อง นั่นเป็นเรื่องยากสำหรับคอมไพเลอร์ในการปรับให้เหมาะสมกว่าเมื่อคุณทำสิ่งเดียวกันกับอาเรย์ นี่คือเหตุผลที่เวกเตอร์มีตัวสร้างซึ่งช่วยให้คุณทำสิ่งนั้นได้อย่างแม่นยำ:NX.

และเมื่อคุณใช้มันเวกเตอร์นั้นเร็วเท่ากับอาเรย์

ดังนั้นไม่คุณไม่ได้จับตำนานการแสดง แต่คุณได้แสดงให้เห็นแล้วว่ามันเป็นเรื่องจริงถ้าคุณใช้เวกเตอร์อย่างดีที่สุดซึ่งเป็นจุดที่ดีเช่นกัน :)

ในแง่ที่สดใสแล้วมันเป็นการใช้งานที่ง่ายที่สุดที่เร็วที่สุด หากคุณเปรียบเทียบโค้ดของฉัน (บรรทัดเดียว) กับคำตอบของ John Kugelman ที่มี heaps และ heaps ของ tweaks และการปรับแต่งซึ่งยังไม่กำจัดความแตกต่างด้านประสิทธิภาพค่อนข้างชัดเจนที่vectorออกแบบมาอย่างชาญฉลาด คุณไม่ต้องกระโดดผ่านห่วงเพื่อให้ได้ความเร็วเท่ากับอาเรย์ ในทางตรงกันข้ามคุณต้องใช้วิธีที่ง่ายที่สุดที่เป็นไปได้


1
ฉันยังคงถามว่านี่เป็นการเปรียบเทียบที่ยุติธรรมหรือไม่ หากคุณกำลังกำจัดวงด้านในจากนั้นอาร์เรย์ที่เทียบเท่ากันก็คือการสร้างวัตถุพิกเซลเดียวแล้วลบส่วนนั้นออกไปทั่วทั้งอาร์เรย์
John Kugelman

1
ใช้new[]ดำเนินการก่อสร้างเริ่มต้นเดียวกันกับที่vector.resize()ทำ แต่มันเร็วกว่ามาก new[]+ วงในควรมีความเร็วเท่ากับvector.resize()+ วงด้านใน แต่ไม่ใช่มันเร็วเกือบสองเท่า
John Kugelman

@John: มันเป็นการเปรียบเทียบที่ยุติธรรม ในรหัสต้นฉบับอาร์เรย์จะถูกจัดสรรด้วยmallocซึ่งไม่ได้เริ่มต้นหรือสร้างอะไรเลยดังนั้นจึงเป็นอัลกอริทึมแบบ pass เดียวเช่นเดียวกับvectorตัวอย่างของฉัน และสำหรับnew[]คำตอบนั้นเห็นได้ชัดว่าทั้งสองต้องผ่านสองรอบ แต่ในnew[]กรณีนี้คอมไพเลอร์สามารถปรับค่าใช้จ่ายเพิ่มเติมให้เหนือกว่าซึ่งไม่ได้ทำในvectorกรณีนี้ แต่ฉันไม่เห็นว่าทำไมมันจึงน่าสนใจว่าเกิดอะไรขึ้นในกรณีที่ไม่ดี หากคุณใส่ใจกับประสิทธิภาพคุณไม่ต้องเขียนโค้ดแบบนั้น
jalf

@John: ความคิดเห็นที่น่าสนใจ ถ้าฉันอยากจะทำลายทั้งอาเรย์ฉันคิดว่าอาเรย์เป็นทางเลือกที่ดีที่สุดอีกครั้ง - เพราะฉันไม่สามารถบอกได้ว่าvector::resize()จะให้ความทรงจำอันน่าสะพรึงแก่ฉันโดยไม่ต้องเสียเวลาเรียกช่างก่อสร้างที่ไร้ประโยชน์
kizzx2

@ kizzx2: ใช่และไม่ใช่ อาเรย์จะเริ่มต้นได้ตามปกติเช่นกันใน C ++ ใน C คุณจะใช้mallocซึ่งไม่ได้เริ่มต้น แต่จะไม่ทำงานใน C ++ ที่ไม่ใช่ POD ดังนั้นในกรณีทั่วไปอาร์เรย์ C ++ น่าจะแย่ บางทีคำถามก็คือถ้าคุณจะทำอย่างนี้บ่อยครั้งคุณจะไม่ใช้อาเรย์ / เวกเตอร์เดียวกันซ้ำอีกหรือเปล่า? และถ้าคุณทำเช่นนั้นคุณต้องจ่ายค่าใช้จ่ายของ "ผู้สร้างที่ไร้ประโยชน์" เพียงครั้งเดียวเมื่อเริ่มต้น การระเบิดที่เกิดขึ้นจริงนั้นรวดเร็วมาก
jalf

22

มันเป็นการเปรียบเทียบที่ไม่ยุติธรรมเลยเมื่อฉันดูโค้ดของคุณครั้งแรก ฉันคิดว่าคุณไม่ได้เปรียบเทียบแอปเปิ้ลกับแอปเปิ้ล ดังนั้นฉันคิดว่าเรามาสร้างคอนสตรัคเตอร์และ destructors ในการทดสอบทั้งหมด แล้วเปรียบเทียบ

const size_t dimension = 1000;

void UseArray() {
    TestTimer t("UseArray");
    for(size_t j = 0; j < dimension; ++j) {
        Pixel* pixels = new Pixel[dimension * dimension];
        for(size_t i = 0 ; i < dimension * dimension; ++i) {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = (unsigned char) (i % 255);
        }
        delete[] pixels;
    }
}

void UseVector() {
    TestTimer t("UseVector");
    for(size_t j = 0; j < dimension; ++j) {
        std::vector<Pixel> pixels(dimension * dimension);
        for(size_t i = 0; i < dimension * dimension; ++i) {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = (unsigned char) (i % 255);
        }
    }
}

int main() {
    TestTimer t1("The whole thing");

    UseArray();
    UseVector();

    return 0;
}

ความคิดของฉันคือว่าด้วยการตั้งค่านี้พวกเขาควรจะเหมือนกันทุกประการ ปรากฎว่าฉันผิด

UseArray completed in 3.06 seconds
UseVector completed in 4.087 seconds
The whole thing completed in 10.14 seconds

เหตุใดการสูญเสียประสิทธิภาพถึง 30% จึงเกิดขึ้น STL มีทุกอย่างในส่วนหัวดังนั้นจึงเป็นไปได้ที่คอมไพเลอร์จะเข้าใจทุกอย่างที่จำเป็น

ความคิดของฉันคือว่ามันเป็นอย่างไรในการวนรอบเริ่มต้นค่าทั้งหมดให้กับตัวสร้างเริ่มต้น ดังนั้นฉันจึงทำการทดสอบ:

class Tester {
public:
    static int count;
    static int count2;
    Tester() { count++; }
    Tester(const Tester&) { count2++; }
};
int Tester::count = 0;
int Tester::count2 = 0;

int main() {
    std::vector<Tester> myvec(300);
    printf("Default Constructed: %i\nCopy Constructed: %i\n", Tester::count, Tester::count2);

    return 0;
}

ผลลัพธ์เป็นตามที่ฉันสงสัย:

Default Constructed: 1
Copy Constructed: 300

นี่คือที่มาของการชะลอตัวอย่างชัดเจนความจริงที่ว่าเวกเตอร์ใช้ตัวสร้างการคัดลอกเพื่อเริ่มต้นองค์ประกอบจากวัตถุที่สร้างเริ่มต้น

นี่หมายความว่าจะมีคำสั่งหลอกดำเนินการต่อไปนี้ระหว่างการสร้างเวกเตอร์:

Pixel pixel;
for (auto i = 0; i < N; ++i) vector[i] = pixel;

ซึ่งเนื่องจากตัวสร้างสำเนาโดยนัยที่คอมไพเลอร์สร้างขึ้นจะขยายไปยังสิ่งต่อไปนี้:

Pixel pixel;
for (auto i = 0; i < N; ++i) {
    vector[i].r = pixel.r;
    vector[i].g = pixel.g;
    vector[i].b = pixel.b;
}

ดังนั้นเริ่มต้นPixelยังคงยกเลิก initialised ในขณะที่ส่วนที่เหลือจะเริ่มต้นใช้กับการเริ่มต้นPixelของการยกเลิกการ initialisedค่า

เปรียบเทียบกับสถานการณ์ทางเลือกด้วยNew[]/ Delete[]:

int main() {
    Tester* myvec = new Tester[300];

    printf("Default Constructed: %i\nCopy Constructed:%i\n", Tester::count, Tester::count2);

    delete[] myvec;

    return 0;
}

Default Constructed: 300
Copy Constructed: 0

พวกเขาทั้งหมดจะถูกทิ้งให้อยู่ในค่าที่ยังไม่เริ่มต้นและไม่มีการวนซ้ำซ้ำในลำดับ

ด้วยข้อมูลนี้เราจะทดสอบได้อย่างไร ลองเขียนตัวสร้างสำเนาโดยนัย

Pixel(const Pixel&) {}

และผลลัพธ์หรือไม่

UseArray completed in 2.617 seconds
UseVector completed in 2.682 seconds
The whole thing completed in 5.301 seconds

ดังนั้นโดยสรุปหากคุณทำพาหะนับร้อยบ่อย: คิดขั้นตอนวิธีของคุณใหม่

ไม่ว่าในกรณีใดการใช้งานSTLไม่ช้าลงด้วยเหตุผลที่ไม่ทราบสาเหตุ แต่ก็ทำในสิ่งที่คุณต้องการอย่างแน่นอน หวังว่าคุณจะรู้ดีกว่า


3
ตัดสินจากความสนุกที่เรา (คุณและฉันและคนฉลาดอื่น ๆ ที่นี่) มีการดำเนินการ "ความหวัง" ของ STL นั้นเป็นสิ่งที่ค่อนข้างท้าทาย: โดยพื้นฐานแล้วเราสามารถพูดเกินจริงและสรุปได้ว่าหวังว่าฉันจะอ่านและวิเคราะห์แหล่งที่มาทั้งหมด รหัส. ต่อไป: P
kizzx2

1
Awsome! ใน VS 2013 สิ่งนี้ทำให้เวกเตอร์เร็วกว่าอาร์เรย์ แม้ว่าจะดูเหมือนว่าสำหรับระบบที่มีความสำคัญต่อประสิทธิภาพคุณต้องทดสอบ STL บ่อยครั้งเพื่อให้สามารถใช้งานได้อย่างมีประสิทธิภาพ
rozina

7

ลองปิดการใช้งานตัวทำซ้ำที่ผ่านการตรวจสอบและสร้างในโหมดการเปิดตัว คุณไม่ควรเห็นความแตกต่างด้านประสิทธิภาพมากนัก


1
#define _SECURE_SCL 0พยายาม นั่นทำให้UseVectorบางที่ประมาณ 4 วินาที (คล้ายกับgccด้านล่าง) แต่ก็ยังช้าเป็นสองเท่า
kizzx2

นี่คือสาเหตุเกือบแน่นอน Microsoft มีการดีบัก iterator โดยอัตโนมัติตามค่าเริ่มต้นสำหรับการดีบักและรีลีส เราพบว่านี่เป็นสาเหตุของการชะลอตัวครั้งใหญ่หลังจากอัพเกรดจากปี 2003 เป็นปี 2008 แน่นอนว่าเป็นหนึ่งใน gotchas ที่อันตรายที่สุดของ visual studio
Doug T.

2
@ kizzx2 มีอีกแมโครที่ต้องปิดการใช้งาน: HAS_ITERATOR_DEBUGGING หรือบางส่วน
Doug T.

ในฐานะที่เป็น @ Martin และคำตอบของฉันแสดง GCC -O3แสดงให้เห็นรูปแบบเดียวกันแม้จะมีการเพิ่มประสิทธิภาพใน
John Kugelman

1
@Doug: ดูเอกสารฉันคิดว่า_HAS_ITERATOR_DEBUGGINGถูกปิดการใช้งานในรุ่น build: msdn.microsoft.com/en-us/library/aa985939(VS.80).aspx
kizzx2

4

STL ของ GNU (และอื่น ๆ ) ที่ได้รับvector<T>(n)เริ่มต้นสร้างวัตถุต้นแบบT()- คอมไพเลอร์จะปรับการสร้างว่างเปล่า - แต่แล้วสำเนาของขยะใด ๆ ที่เกิดขึ้นจะอยู่ในที่อยู่หน่วยความจำตอนนี้สงวนไว้สำหรับวัตถุที่ถูกนำโดย STL ของ__uninitialized_fill_n_auxซึ่ง วนซ้ำสำเนาของวัตถุนั้นเป็นค่าเริ่มต้นในเวกเตอร์ ดังนั้น "my" STL จึงไม่วนการสร้าง แต่เป็นการสร้างลูป / การคัดลอก มันเป็นเรื่องที่เข้าใจง่าย แต่ฉันควรจำได้เมื่อฉันแสดงความคิดเห็นกับคำถาม stackoverflow ล่าสุดเกี่ยวกับประเด็นนี้: การสร้าง / การคัดลอกอาจมีประสิทธิภาพมากขึ้นสำหรับการอ้างอิงวัตถุที่นับเป็นต้น

ดังนั้น:

vector<T> x(n);

หรือ

vector<T> x;
x.resize(n);

คือ - ในการใช้งาน STL มากมาย - เช่น:

T temp;
for (int i = 0; i < n; ++i)
    x[i] = temp;

ปัญหาที่ว่าตัวสร้างคอมไพเลอร์ตัวปรับรุ่นปัจจุบันดูเหมือนจะไม่ทำงานจากข้อมูลเชิงลึกที่ temp เป็นขยะที่ไม่ได้กำหนดค่าเริ่มต้นและไม่สามารถปรับให้เหมาะสมกับลูปและการเรียกใช้ตัวสร้างการคัดลอกค่าเริ่มต้น คุณสามารถยืนยันได้อย่างน่าเชื่อถือว่าคอมไพเลอร์ไม่ควรปรับค่านี้ให้เหมาะสมเนื่องจากโปรแกรมเมอร์ที่เขียนด้านบนมีความคาดหวังที่สมเหตุสมผลว่าวัตถุทั้งหมดจะเหมือนกันหลังจากวนรอบแม้ว่าขยะ (คำเตือนทั่วไปเกี่ยวกับ memcmp / โอเปอเรเตอร์ = ใช้กับ etc) คอมไพเลอร์ไม่สามารถคาดการณ์ได้ว่าจะมีข้อมูลเชิงลึกเพิ่มเติมในบริบทที่มีขนาดใหญ่กว่าของ std :: vector <> หรือการใช้ข้อมูลในภายหลังซึ่งจะแนะนำการเพิ่มประสิทธิภาพนี้อย่างปลอดภัย

สิ่งนี้สามารถเปรียบเทียบได้กับการใช้งานโดยตรงที่ชัดเจนยิ่งขึ้น:

for (int i = 0; i < n; ++i)
    x[i] = T();

ซึ่งเราสามารถคาดหวังคอมไพเลอร์เพื่อเพิ่มประสิทธิภาพ

เพื่อให้ชัดเจนยิ่งขึ้นเกี่ยวกับการจัดชิดขอบสำหรับพฤติกรรมของเวกเตอร์ลักษณะนี้ให้พิจารณา:

std::vector<big_reference_counted_object> x(10000);

เห็นได้ชัดว่าเป็นความแตกต่างที่สำคัญหากเราสร้างวัตถุอิสระ 10,000 ชิ้นเทียบกับข้อมูลอ้างอิง 10,000 รายการ มีข้อโต้แย้งที่สมเหตุสมผลว่าข้อดีของการปกป้องผู้ใช้งาน C ++ ที่ไม่เป็นทางการจากการทำสิ่งที่มีค่าใช้จ่ายโดยไม่ได้ตั้งใจนั้นมีค่าสูงกว่าต้นทุนการสร้างสำเนาที่ยากต่อการเพิ่มประสิทธิภาพ

คำตอบเดิม (สำหรับการอ้างอิง / ทำความเข้าใจความคิดเห็น): ไม่มีโอกาส vector นั้นเร็วเท่ากับอาเรย์อย่างน้อยถ้าคุณจองพื้นที่อย่างสมเหตุสมผล ...


6
ฉันไม่สามารถพิสูจน์ได้ว่าคำตอบนี้เป็นประโยชน์กับทุกคนเล็กน้อย ฉันหวังว่าฉันจะลงคะแนนได้สองครั้ง
kizzx2

-1, มีการสนับสนุนของฉันใน kizzx2 เวกเตอร์จะไม่เร็วเท่าอาเรย์เนื่องจากมีคุณสมบัติเพิ่มเติมให้กฎของจักรวาลทุกอย่างมีราคา!
YeenFei

คุณพลาดโทนี่…มันเป็นตัวอย่างของมาตรฐานการประดิษฐ์ แต่มันพิสูจน์ได้ว่าสิ่งที่มันอ้างถึง
Potatoswatter

ดอกกุหลาบเป็นสีเขียวสีม่วงมีสีส้ม, downvotes มีรสขม แต่คำตอบขอให้พวกเขา
Pavel Minaev

3

คำตอบของมาร์ตินยอร์กทำให้ฉันรำคาญเพราะมันดูเหมือนจะพยายามปัดปัญหาการเริ่มต้นใต้พรม แต่เขามีสิทธิ์ที่จะระบุโครงสร้างเริ่มต้นที่ซ้ำซ้อนเป็นสาเหตุของปัญหาประสิทธิภาพ

[แก้ไข: คำตอบของมาร์ตินไม่แนะนำให้เปลี่ยนตัวสร้างเริ่มต้นอีกต่อไป]

สำหรับปัญหาที่เกิดขึ้นทันทีคุณสามารถเรียกvector<Pixel>ctor เวอร์ชัน 2 พารามิเตอร์แทน:

std::vector<Pixel> pixels(dimension * dimension, Pixel(255, 0, 0));

ใช้งานได้หากคุณต้องการกำหนดค่าเริ่มต้นด้วยค่าคงที่ซึ่งเป็นกรณีทั่วไป แต่ปัญหาที่พบโดยทั่วไปก็คือ: คุณจะเริ่มต้นสิ่งที่ซับซ้อนได้อย่างมีประสิทธิภาพมากกว่าค่าคงที่ได้อย่างไร

สำหรับสิ่งนี้คุณสามารถใช้ a back_insert_iteratorซึ่งเป็นอะแดปเตอร์ตัววนซ้ำ นี่คือตัวอย่างที่มีเวกเตอร์ของints ถึงแม้ว่าแนวคิดทั่วไปจะใช้งานได้ดีเช่นกันสำหรับPixels:

#include <iterator>
// Simple functor return a list of squares: 1, 4, 9, 16...
struct squares {
    squares() { i = 0; }
    int operator()() const { ++i; return i * i; }

private:
    int i;
};

...

std::vector<int> v;
v.reserve(someSize);     // To make insertions efficient
std::generate_n(std::back_inserter(v), someSize, squares());

หรือคุณสามารถใช้copy()หรือแทนtransform()generate_n()

ข้อเสียคือตรรกะในการสร้างค่าเริ่มต้นจะต้องย้ายไปอยู่ในคลาสที่แยกต่างหากซึ่งสะดวกกว่าการใช้แบบแทนที่ (แม้ว่า lambdas ใน C ++ 1x ทำสิ่งนี้ได้ดีกว่า) นอกจากนี้ฉันคาดว่าสิ่งนี้จะยังไม่เร็วเท่ารุ่นที่malloc()ไม่ใช่ STL แต่ฉันคาดว่ามันจะปิดเพราะมันจะสร้างเพียงหนึ่งองค์ประกอบสำหรับแต่ละองค์ประกอบ


2

คนเวกเตอร์กำลังเรียกตัวสร้างพิกเซลเพิ่มเติม

แต่ละอันก่อให้เกิด ctor เกือบล้านตัววิ่งตามเวลาที่คุณกำหนด

แก้ไข: ถ้างั้นก็มีด้านนอก 1 ... 1,000 ลูปดังนั้นเรียกมันว่าพันล้านสาย ctor!

แก้ไข 2: มันน่าสนใจที่จะเห็นการถอดแยกชิ้นส่วนสำหรับกรณี UseArray เครื่องมือเพิ่มประสิทธิภาพสามารถปรับสิ่งทั้งหมดให้เหมาะสมเนื่องจากไม่มีผลอื่นใดนอกจากการเบิร์น CPU


คุณพูดถูก แต่คำถามคือ: การเรียก ctor ที่ไม่มีจุดหมายเหล่านี้จะถูกปิดได้อย่างไร? มันง่ายสำหรับวิธีที่ไม่ใช่ STL แต่ยาก / น่าเกลียดสำหรับวิธี STL
j_random_hacker

1

นี่คือวิธีpush_backการทำงานของเวกเตอร์:

  1. เวกเตอร์จัดสรรพื้นที่จำนวน X เมื่อเริ่มต้น
  2. ตามที่ระบุไว้ด้านล่างจะตรวจสอบว่ามีห้องพักในอาร์เรย์ที่ขีดเส้นใต้ปัจจุบันสำหรับรายการ
  3. มันทำสำเนาของรายการในการโทร push_back

หลังจากเรียกpush_backรายการ X:

  1. เวกเตอร์จัดสรรพื้นที่จำนวน kX ให้เป็นอาร์เรย์ที่ 2
  2. มันคัดลอกรายการของอาร์เรย์แรกลงในวินาที
  3. ทิ้งอาร์เรย์แรก
  4. ตอนนี้ใช้อาร์เรย์ที่สองเป็นที่เก็บข้อมูลจนกว่าจะถึงรายการ kX

ทำซ้ำ ถ้าคุณไม่มีที่reservingว่างมันจะช้าลงอย่างแน่นอน ยิ่งไปกว่านั้นหากมีราคาแพงในการคัดลอกรายการดังนั้น 'push_back' เช่นนั้นจะทำให้คุณมีชีวิตอยู่

สำหรับเรื่องของvectorอาเรย์กับฉันจะต้องเห็นด้วยกับคนอื่น ทำงานในรีลีสเปิดใช้งานการปรับให้เหมาะสมและใส่แฟลกเพิ่มเติมอีกสองสามรายการเพื่อให้ผู้คนที่เป็นมิตรใน Microsoft ไม่ได้ # @% $ ^ มันสำหรับ ya

อีกอย่างหนึ่งถ้าคุณไม่ต้องการปรับขนาดใช้ Boost.Array


ฉันเข้าใจว่าคนไม่ชอบอ่านโค้ดจำนวนมากเมื่อโพสต์คำต่อคำ แต่ฉันใช้reserveอย่างที่ควร
kizzx2

ขอโทษฉันคิดถึงมัน ไม่มีอะไรที่ฉันวางไว้มีประโยชน์เลย?
ข้าวสาลี

push_backได้ตัดจำหน่ายเวลาคงที่ ดูเหมือนว่าคุณกำลังอธิบายกระบวนการ O (N) (ขั้นตอนที่ 1 และ 3 ดูไม่สมบูรณ์) สิ่งที่ทำให้push_backช้าสำหรับ OP คือการตรวจสอบช่วงเพื่อดูว่าจำเป็นต้องทำการจัดสรรใหม่หรือไม่การอัปเดตพอยน์เตอร์การตรวจสอบกับ NULL ภายในตำแหน่งnewและสิ่งเล็ก ๆ อื่น ๆ งานจริงของโปรแกรม
Potatoswatter

มันจะช้าลงแม้ว่าreserveจะยังต้องทำการตรวจสอบนั้น (ไม่ว่าจะต้องทำการจัดสรรใหม่) ทุกครั้งpush_backก็ตาม
Pavel Minaev

ทุกจุดที่ดี สิ่งที่ฉันอธิบายฟังดูเหมือนกระบวนการ O (N) แต่มันก็ไม่ดี คนส่วนใหญ่ที่ฉันรู้ไม่เข้าใจว่าvectorประสิทธิภาพการทำงานของมันคือการปรับขนาดการทำงานมันเป็นเพียงแค่ ที่นี่ขอผมอธิบายอีกหน่อย
ข้าวสาลี

1

ข้อมูล profiler บางส่วน (พิกเซลจัดชิดกับ 32 บิต):

g++ -msse3 -O3 -ftree-vectorize -g test.cpp -DNDEBUG && ./a.out
UseVector completed in 3.123 seconds
UseArray completed in 1.847 seconds
UseVectorPushBack completed in 9.186 seconds
The whole thing completed in 14.159 seconds

blah

andrey@nv:~$ opannotate --source libcchem/src/a.out  | grep "Total samples for file" -A3
Overflow stats not available
 * Total samples for file : "/usr/include/c++/4.4/ext/new_allocator.h"
 *
 * 141008 52.5367
 */
--
 * Total samples for file : "/home/andrey/libcchem/src/test.cpp"
 *
 *  61556 22.9345
 */
--
 * Total samples for file : "/usr/include/c++/4.4/bits/stl_vector.h"
 *
 *  41956 15.6320
 */
--
 * Total samples for file : "/usr/include/c++/4.4/bits/stl_uninitialized.h"
 *
 *  20956  7.8078
 */
--
 * Total samples for file : "/usr/include/c++/4.4/bits/stl_construct.h"
 *
 *   2923  1.0891
 */

ในallocator:

               :      // _GLIBCXX_RESOLVE_LIB_DEFECTS
               :      // 402. wrong new expression in [some_] allocator::construct
               :      void
               :      construct(pointer __p, const _Tp& __val)
141008 52.5367 :      { ::new((void *)__p) _Tp(__val); }

vector:

               :void UseVector()
               :{ /* UseVector() total:  60121 22.3999 */
...
               :
               :
 10790  4.0201 :        for (int i = 0; i < dimension * dimension; ++i) {
               :
   495  0.1844 :            pixels[i].r = 255;
               :
 12618  4.7012 :            pixels[i].g = 0;
               :
  2253  0.8394 :            pixels[i].b = 0;
               :
               :        }

แถว

               :void UseArray()
               :{ /* UseArray() total:  35191 13.1114 */
               :
...
               :
   136  0.0507 :        for (int i = 0; i < dimension * dimension; ++i) {
               :
  9897  3.6874 :            pixels[i].r = 255;
               :
  3511  1.3081 :            pixels[i].g = 0;
               :
 21647  8.0652 :            pixels[i].b = 0;

ค่าโสหุ้ยส่วนใหญ่อยู่ในตัวสร้างการคัดลอก ตัวอย่างเช่น,

    std::vector < Pixel > pixels;//(dimension * dimension, Pixel());

    pixels.reserve(dimension * dimension);

    for (int i = 0; i < dimension * dimension; ++i) {

        pixels[i].r = 255;

        pixels[i].g = 0;

        pixels[i].b = 0;
    }

มันมีประสิทธิภาพเช่นเดียวกับอาร์เรย์


2
น่าเสียดายหลังจาก "แก้ปัญหา" ที่คุณให้ไว้pixels.size()จะถูกทำลาย
kizzx2

1
นี่เป็นสิ่งที่ผิดคุณไม่สามารถโทรสำรองแล้วใช้องค์ประกอบคุณยังคงต้องใช้ push_back เพื่อเพิ่มรายการ
paulm

1

แล็ปท็อปของฉันคือ Lenova G770 (RAM 4 GB)

ระบบปฏิบัติการคือ Windows 7 64- บิต (อันที่มีแล็ปท็อป)

คอมไพเลอร์คือMinGW 4.6.1

IDE ที่เป็นรหัส :: บล็อก

ฉันทดสอบซอร์สโค้ดของโพสต์แรก

ผลที่ได้

การเพิ่มประสิทธิภาพ O2

UseArray เสร็จสมบูรณ์ใน 2.841 วินาที

UseVector เสร็จสมบูรณ์ใน 2.548 วินาที

UseVectorPushBack เสร็จสมบูรณ์ใน 11.95 วินาที

ทุกอย่างเสร็จสมบูรณ์ใน 17.342 วินาที

ระบบหยุดชั่วคราว

การเพิ่มประสิทธิภาพ O3

UseArray เสร็จสิ้นใน 1.452 วินาที

UseVector เสร็จสมบูรณ์ใน 2.514 วินาที

UseVectorPushBack เสร็จสมบูรณ์ใน 12.967 วินาที

ทุกอย่างเสร็จสมบูรณ์ภายใน 16.937 วินาที

ดูเหมือนว่าประสิทธิภาพของเวกเตอร์จะแย่ลงภายใต้การปรับให้เหมาะสม O3

หากคุณเปลี่ยนลูปเป็น

    pixels[i].r = i;
    pixels[i].g = i;
    pixels[i].b = i;

ความเร็วของอาเรย์และเวกเตอร์ภายใต้ O2 และ O3 นั้นใกล้เคียงกัน


แม้ว่าฉันจะเปลี่ยน malloc เป็นใหม่ในกรณีทดสอบครั้งแรกภายใต้ O3 ประสิทธิภาพของเวกเตอร์ยังช้ากว่าอาร์เรย์ แต่เมื่อคุณเปลี่ยนค่าการมอบหมายจาก (255, 0, 0) เป็น (i, i, i) ประสิทธิภาพของ เวกเตอร์และอาเรย์เกือบจะเหมือนกันภายใต้ O2 และ O3 มันค่อนข้างประหลาด
StereoMatching

ขออภัยฉันลืมเปลี่ยนฟรีเพื่อลบหลังจากเปลี่ยนฟรีเพื่อลบประสิทธิภาพการทำงานภายใต้ O3 ของเวกเตอร์และอาร์เรย์เหมือนกันตอนนี้ดูเหมือนว่าตัวจัดสรรคือเหตุผลหลักหรือไม่
StereoMatching

1

เกณฑ์มาตรฐานที่ดีกว่า (ฉันคิดว่า ... ) คอมไพเลอร์เนื่องจากการปรับให้เหมาะสมสามารถเปลี่ยนรหัสได้เนื่องจากผลลัพธ์ของเวกเตอร์ / อาร์เรย์ที่จัดสรรไม่ได้ถูกใช้ในที่ใด ๆ ผล:

$ g++ test.cpp -o test -O3 -march=native
$ ./test 
UseArray inner completed in 0.652 seconds
UseArray completed in 0.773 seconds
UseVector inner completed in 0.638 seconds
UseVector completed in 0.757 seconds
UseVectorPushBack inner completed in 6.732 seconds
UseVectorPush completed in 6.856 seconds
The whole thing completed in 8.387 seconds

คอมไพเลอร์:

gcc version 6.2.0 20161019 (Debian 6.2.0-9)

CPU:

model name  : Intel(R) Core(TM) i7-3630QM CPU @ 2.40GHz

และรหัส:

#include <cstdlib>
#include <vector>

#include <iostream>
#include <string>

#include <boost/date_time/posix_time/ptime.hpp>
#include <boost/date_time/microsec_time_clock.hpp>

class TestTimer
{
    public:
        TestTimer(const std::string & name) : name(name),
            start(boost::date_time::microsec_clock<boost::posix_time::ptime>::local_time())
        {
        }

        ~TestTimer()
        {
            using namespace std;
            using namespace boost;

            posix_time::ptime now(date_time::microsec_clock<posix_time::ptime>::local_time());
            posix_time::time_duration d = now - start;

            cout << name << " completed in " << d.total_milliseconds() / 1000.0 <<
                " seconds" << endl;
        }

    private:
        std::string name;
        boost::posix_time::ptime start;
};

struct Pixel
{
    Pixel()
    {
    }

    Pixel(unsigned char r, unsigned char g, unsigned char b) : r(r), g(g), b(b)
    {
    }

    unsigned char r, g, b;
};

void UseVector(std::vector<std::vector<Pixel> >& results)
{
    TestTimer t("UseVector inner");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel>& pixels = results.at(i);
        pixels.resize(dimension * dimension);

        for(int i = 0; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }
    }
}

void UseVectorPushBack(std::vector<std::vector<Pixel> >& results)
{
    TestTimer t("UseVectorPushBack inner");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel>& pixels = results.at(i);
            pixels.reserve(dimension * dimension);

        for(int i = 0; i < dimension * dimension; ++i)
            pixels.push_back(Pixel(255, 0, 0));
    }
}

void UseArray(Pixel** results)
{
    TestTimer t("UseArray inner");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        Pixel * pixels = (Pixel *)malloc(sizeof(Pixel) * dimension * dimension);

        results[i] = pixels;

        for(int i = 0 ; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }

        // free(pixels);
    }
}

void UseArray()
{
    TestTimer t("UseArray");
    Pixel** array = (Pixel**)malloc(sizeof(Pixel*)* 1000);
    UseArray(array);
    for(int i=0;i<1000;++i)
        free(array[i]);
    free(array);
}

void UseVector()
{
    TestTimer t("UseVector");
    {
        std::vector<std::vector<Pixel> > vector(1000, std::vector<Pixel>());
        UseVector(vector);
    }
}

void UseVectorPushBack()
{
    TestTimer t("UseVectorPush");
    {
        std::vector<std::vector<Pixel> > vector(1000, std::vector<Pixel>());
        UseVectorPushBack(vector);
    }
}


int main()
{
    TestTimer t1("The whole thing");

    UseArray();
    UseVector();
    UseVectorPushBack();

    return 0;
}

1

ฉันได้ทำการทดสอบอย่างกว้างขวางที่ฉันต้องการในขณะนี้ อาจแบ่งปันสิ่งนี้เช่นกัน

นี่คือเครื่องบูตคู่ของฉัน i7-3770, 16GB Ram, x86_64, บน Windows 8.1 และบน Ubuntu 16.04 ข้อมูลเพิ่มเติมและข้อสรุปข้อสังเกตด้านล่าง ทดสอบทั้ง MSVS 2017 และ g ++ (ทั้งบน Windows และบน Linux)

โปรแกรมทดสอบ

#include <iostream>
#include <chrono>
//#include <algorithm>
#include <array>
#include <locale>
#include <vector>
#include <queue>
#include <deque>

// Note: total size of array must not exceed 0x7fffffff B = 2,147,483,647B
//  which means that largest int array size is 536,870,911
// Also image size cannot be larger than 80,000,000B
constexpr int long g_size = 100000;
int g_A[g_size];


int main()
{
    std::locale loc("");
    std::cout.imbue(loc);
    constexpr int long size = 100000;  // largest array stack size

    // stack allocated c array
    std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
    int A[size];
    for (int i = 0; i < size; i++)
        A[i] = i;

    auto duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "c-style stack array duration=" << duration / 1000.0 << "ms\n";
    std::cout << "c-style stack array size=" << sizeof(A) << "B\n\n";

    // global stack c array
    start = std::chrono::steady_clock::now();
    for (int i = 0; i < g_size; i++)
        g_A[i] = i;

    duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "global c-style stack array duration=" << duration / 1000.0 << "ms\n";
    std::cout << "global c-style stack array size=" << sizeof(g_A) << "B\n\n";

    // raw c array heap array
    start = std::chrono::steady_clock::now();
    int* AA = new int[size];    // bad_alloc() if it goes higher than 1,000,000,000
    for (int i = 0; i < size; i++)
        AA[i] = i;

    duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "c-style heap array duration=" << duration / 1000.0 << "ms\n";
    std::cout << "c-style heap array size=" << sizeof(AA) << "B\n\n";
    delete[] AA;

    // std::array<>
    start = std::chrono::steady_clock::now();
    std::array<int, size> AAA;
    for (int i = 0; i < size; i++)
        AAA[i] = i;
    //std::sort(AAA.begin(), AAA.end());

    duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "std::array duration=" << duration / 1000.0 << "ms\n";
    std::cout << "std::array size=" << sizeof(AAA) << "B\n\n";

    // std::vector<>
    start = std::chrono::steady_clock::now();
    std::vector<int> v;
    for (int i = 0; i < size; i++)
        v.push_back(i);
    //std::sort(v.begin(), v.end());

    duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "std::vector duration=" << duration / 1000.0 << "ms\n";
    std::cout << "std::vector size=" << v.size() * sizeof(v.back()) << "B\n\n";

    // std::deque<>
    start = std::chrono::steady_clock::now();
    std::deque<int> dq;
    for (int i = 0; i < size; i++)
        dq.push_back(i);
    //std::sort(dq.begin(), dq.end());

    duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "std::deque duration=" << duration / 1000.0 << "ms\n";
    std::cout << "std::deque size=" << dq.size() * sizeof(dq.back()) << "B\n\n";

    // std::queue<>
    start = std::chrono::steady_clock::now();
    std::queue<int> q;
    for (int i = 0; i < size; i++)
        q.push(i);

    duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "std::queue duration=" << duration / 1000.0 << "ms\n";
    std::cout << "std::queue size=" << q.size() * sizeof(q.front()) << "B\n\n";
}

ผล

//////////////////////////////////////////////////////////////////////////////////////////
// with MSVS 2017:
// >> cl /std:c++14 /Wall -O2 array_bench.cpp
//
// c-style stack array duration=0.15ms
// c-style stack array size=400,000B
//
// global c-style stack array duration=0.130ms
// global c-style stack array size=400,000B
//
// c-style heap array duration=0.90ms
// c-style heap array size=4B
//
// std::array duration=0.20ms
// std::array size=400,000B
//
// std::vector duration=0.544ms
// std::vector size=400,000B
//
// std::deque duration=1.375ms
// std::deque size=400,000B
//
// std::queue duration=1.491ms
// std::queue size=400,000B
//
//////////////////////////////////////////////////////////////////////////////////////////
//
// with g++ version:
//      - (tdm64-1) 5.1.0 on Windows
//      - (Ubuntu 5.4.0-6ubuntu1~16.04.10) 5.4.0 20160609 on Ubuntu 16.04
// >> g++ -std=c++14 -Wall -march=native -O2 array_bench.cpp -o array_bench
//
// c-style stack array duration=0ms
// c-style stack array size=400,000B
//
// global c-style stack array duration=0.124ms
// global c-style stack array size=400,000B
//
// c-style heap array duration=0.648ms
// c-style heap array size=8B
//
// std::array duration=1ms
// std::array size=400,000B
//
// std::vector duration=0.402ms
// std::vector size=400,000B
//
// std::deque duration=0.234ms
// std::deque size=400,000B
//
// std::queue duration=0.304ms
// std::queue size=400,000
//
//////////////////////////////////////////////////////////////////////////////////////////

หมายเหตุ

  • ประกอบโดยเฉลี่ย 10 การวิ่ง
  • ฉันเริ่มทำการทดสอบด้วยstd::sort()เช่นกัน (คุณสามารถเห็นมันใส่ความคิดเห็น) แต่ลบออกในภายหลังเพราะไม่มีความแตกต่างที่สัมพันธ์กันอย่างมีนัยสำคัญ

ข้อสรุปและข้อสังเกตของฉัน

  • สังเกตุว่าอาเรย์ c-style อาเรย์ใช้เวลานานเท่าอาเรย์ c-style อาเรย์
  • จากการทดสอบทั้งหมดฉันสังเกตเห็นความเสถียรที่น่าทึ่ง std::arrayแปรผันของเวลาระหว่างการวิ่งติดต่อกันในขณะที่การทดสอบอื่น ๆ โดยเฉพาะ std :: data structs แตกต่างกันอย่างมากในการเปรียบเทียบ
  • การปรับให้เหมาะสมของ O3 ไม่ได้แสดงความแตกต่างของเวลาที่น่าสังเกต
  • การลบการปรับให้เหมาะสมบน Windows cl (no -O2) และ g ++ (Win / Linux no -O2, no -march = native) จะเพิ่มขึ้นอย่างมากครั้ง โดยเฉพาะอย่างยิ่งสำหรับ std :: data structs โดยรวมแล้วเวลาบน MSVS สูงกว่า g ++ แต่std::arrayและอาร์เรย์แบบ c-style เร็วขึ้นบน Windows โดยไม่ต้องปรับให้เหมาะสม
  • g ++ สร้างโค้ดที่เร็วกว่าคอมไพเลอร์ของ Microsoft (เห็นได้ชัดว่ามันทำงานได้เร็วกว่าแม้ใน Windows)

คำตัดสิน

แน่นอนว่านี่คือรหัสสำหรับการสร้างที่ปรับให้เหมาะสม และเนื่องจากคำถามเกี่ยวกับstd::vectorใช่แล้วมันเป็น! มาก! ช้ากว่าอาร์เรย์ธรรมดา (ปรับให้เหมาะสม / ไม่เพิ่มประสิทธิภาพ) แต่เมื่อคุณทำเกณฑ์มาตรฐานคุณต้องการสร้างรหัสที่เหมาะสมที่สุด

แม้ดาวแห่งการแสดงสำหรับฉันจะเป็นแบบstd::arrayนั้นก็ตาม


0

ด้วยตัวเลือกที่เหมาะสมเวกเตอร์และอาร์เรย์สามารถสร้าง asm ที่เหมือนกันได้ ในกรณีเหล่านี้แน่นอนว่ามีความเร็วเท่ากันเนื่องจากคุณได้รับไฟล์ปฏิบัติการที่เหมือนกัน


1
ในกรณีนี้พวกเขาดูเหมือนจะไม่สร้างชุดประกอบเดียวกัน โดยเฉพาะอย่างยิ่งดูเหมือนว่าจะไม่มีวิธีใดที่จะระงับการเรียกร้องให้ผู้รับเหมาก่อสร้างใช้เวกเตอร์ได้ คุณสามารถอ้างถึงคำตอบที่นี่สำหรับปัญหานั้น (เป็นการอ่านที่ยาวนาน แต่อธิบายได้ว่าทำไมถึงมีความแตกต่างด้านประสิทธิภาพในกรณีอื่นนอกเหนือจากกรณีทดสอบ simples ในลิงก์ที่คุณพิสูจน์แล้ว) (จริง ๆ แล้วดูเหมือนจะเป็นวิธี - - เขียนตัวจัดสรร STL ที่กำหนดเองตามที่แนะนำโดยส่วนตัวแล้วฉันคิดว่ามันใช้งานได้มากกว่าการใช้ malloc โดยไม่จำเป็น)
kizzx2

1
@ kizzx2: การที่คุณต้องใช้ความยาวเพื่อใช้วัตถุที่ไม่มีโครงสร้างนั้นเป็นสิ่งที่ดีเพราะนั่นเป็นข้อผิดพลาด 99% (ฉันอาจดูถูกดูแคลน) ฉันได้อ่านคำตอบอื่น ๆ แล้วและฉันรู้ว่าฉันไม่ได้ระบุสถานการณ์เฉพาะของคุณ (ไม่ต้องการคำตอบอื่น ๆ นั้นถูกต้อง) แต่ฉันก็ยังต้องการที่จะให้คุณตัวอย่างของเวกเตอร์และอาร์เรย์ที่สามารถทำงานเหมือนกันได้

@Roger: เยี่ยมมาก! ขอบคุณสำหรับลิงค์
kizzx2

0

โดยวิธีการชะลอการมองเห็นในชั้นเรียนโดยใช้เวกเตอร์ยังเกิดขึ้นกับประเภทมาตรฐานเช่น int นี่คือรหัสแบบมัลติเธรด:

#include <iostream>
#include <cstdio>
#include <map>
#include <string>
#include <typeinfo>
#include <vector>
#include <pthread.h>
#include <sstream>
#include <fstream>
using namespace std;

//pthread_mutex_t map_mutex=PTHREAD_MUTEX_INITIALIZER;

long long num=500000000;
int procs=1;

struct iterate
{
    int id;
    int num;
    void * member;
    iterate(int a, int b, void *c) : id(a), num(b), member(c) {}
};

//fill out viterate and piterate
void * viterate(void * input)
{
    printf("am in viterate\n");
    iterate * info=static_cast<iterate *> (input);
    // reproduce member type
    vector<int> test= *static_cast<vector<int>*> (info->member);
    for (int i=info->id; i<test.size(); i+=info->num)
    {
        //printf("am in viterate loop\n");
        test[i];
    }
    pthread_exit(NULL);
}

void * piterate(void * input)
{
    printf("am in piterate\n");
    iterate * info=static_cast<iterate *> (input);;
    int * test=static_cast<int *> (info->member);
    for (int i=info->id; i<num; i+=info->num) {
        //printf("am in piterate loop\n");
        test[i];
    }
    pthread_exit(NULL);
}

int main()
{
    cout<<"producing vector of size "<<num<<endl;
    vector<int> vtest(num);
    cout<<"produced  a vector of size "<<vtest.size()<<endl;
    pthread_t thread[procs];

    iterate** it=new iterate*[procs];
    int ans;
    void *status;

    cout<<"begining to thread through the vector\n";
    for (int i=0; i<procs; i++) {
        it[i]=new iterate(i, procs, (void *) &vtest);
    //  ans=pthread_create(&thread[i],NULL,viterate, (void *) it[i]);
    }
    for (int i=0; i<procs; i++) {
        pthread_join(thread[i], &status);
    }
    cout<<"end of threading through the vector";
    //reuse the iterate structures

    cout<<"producing a pointer with size "<<num<<endl;
    int * pint=new int[num];
    cout<<"produced a pointer with size "<<num<<endl;

    cout<<"begining to thread through the pointer\n";
    for (int i=0; i<procs; i++) {
        it[i]->member=&pint;
        ans=pthread_create(&thread[i], NULL, piterate, (void*) it[i]);
    }
    for (int i=0; i<procs; i++) {
        pthread_join(thread[i], &status);
    }
    cout<<"end of threading through the pointer\n";

    //delete structure array for iterate
    for (int i=0; i<procs; i++) {
        delete it[i];
    }
    delete [] it;

    //delete pointer
    delete [] pint;

    cout<<"end of the program"<<endl;
    return 0;
}

พฤติกรรมจากรหัสแสดงให้เห็นถึงการสร้างอินสแตนซ์ของเวกเตอร์เป็นส่วนที่ยาวที่สุดของรหัส เมื่อคุณผ่านคอขวดนั้น ส่วนที่เหลือของรหัสทำงานอย่างรวดเร็วมาก สิ่งนี้เป็นจริงไม่ว่าคุณจะใช้งานเธรดจำนวนเท่าใด

โดยไม่สนใจจำนวนบ้ารวมอย่างแน่นอน ฉันใช้รหัสนี้เพื่อทดสอบสิ่งต่าง ๆ สำหรับโครงการเพื่อให้จำนวนรวมเพิ่มขึ้นเรื่อย ๆ


0

ฉันแค่อยากจะพูดถึงเวกเตอร์นั้น (และ smart_ptr) เป็นเพียงชั้นบาง ๆ ที่เพิ่มเข้ามาด้านบนของอาร์เรย์ดิบ (และตัวชี้ดิบ) และที่จริงเวลาเข้าถึงเวกเตอร์ในหน่วยความจำต่อเนื่องเร็วกว่าอาเรย์ รหัสต่อไปนี้แสดงผลลัพธ์ของการเริ่มต้นและการเข้าถึงเวกเตอร์และอาร์เรย์

#include <boost/date_time/posix_time/posix_time.hpp>
#include <iostream>
#include <vector>
#define SIZE 20000
int main() {
    srand (time(NULL));
    vector<vector<int>> vector2d;
    vector2d.reserve(SIZE);
    int index(0);
    boost::posix_time::ptime start_total = boost::posix_time::microsec_clock::local_time();
    //  timer start - build + access
    for (int i = 0; i < SIZE; i++) {
        vector2d.push_back(vector<int>(SIZE));
    }
    boost::posix_time::ptime start_access = boost::posix_time::microsec_clock::local_time();
    //  timer start - access
    for (int i = 0; i < SIZE; i++) {
        index = rand()%SIZE;
        for (int j = 0; j < SIZE; j++) {

            vector2d[index][index]++;
        }
    }
    boost::posix_time::ptime end = boost::posix_time::microsec_clock::local_time();
    boost::posix_time::time_duration msdiff = end - start_total;
    cout << "Vector total time: " << msdiff.total_milliseconds() << "milliseconds.\n";
    msdiff = end - start_acess;
    cout << "Vector access time: " << msdiff.total_milliseconds() << "milliseconds.\n"; 


    int index(0);
    int** raw2d = nullptr;
    raw2d = new int*[SIZE];
    start_total = boost::posix_time::microsec_clock::local_time();
    //  timer start - build + access
    for (int i = 0; i < SIZE; i++) {
        raw2d[i] = new int[SIZE];
    }
    start_access = boost::posix_time::microsec_clock::local_time();
    //  timer start - access
    for (int i = 0; i < SIZE; i++) {
        index = rand()%SIZE;
        for (int j = 0; j < SIZE; j++) {

            raw2d[index][index]++;
        }
    }
    end = boost::posix_time::microsec_clock::local_time();
    msdiff = end - start_total;
    cout << "Array total time: " << msdiff.total_milliseconds() << "milliseconds.\n";
    msdiff = end - start_acess;
    cout << "Array access time: " << msdiff.total_milliseconds() << "milliseconds.\n"; 
    for (int i = 0; i < SIZE; i++) {
        delete [] raw2d[i];
    }
    return 0;
}

ผลลัพธ์คือ:

    Vector total time: 925milliseconds.
    Vector access time: 4milliseconds.
    Array total time: 30milliseconds.
    Array access time: 21milliseconds.

ดังนั้นความเร็วจะใกล้เคียงกันถ้าคุณใช้อย่างถูกต้อง (ตามที่คนอื่นพูดถึงโดยใช้ Reserve () หรือปรับขนาด ())


0

ก็เพราะว่า vector :: resize () ทำการประมวลผลมากกว่าการจัดสรรหน่วยความจำธรรมดา (โดย malloc)

ลองใส่เบรกพอยต์ในตัวสร้างสำเนาของคุณ (กำหนดเพื่อให้คุณสามารถเบรกพอยต์!) และมีเวลาดำเนินการเพิ่มเติม


0

ฉันต้องบอกว่าฉันไม่ใช่ผู้เชี่ยวชาญในภาษา C ++ แต่หากต้องการเพิ่มผลลัพธ์การทดสอบ:

คอมไพล์: gcc-6.2.0 / bin / g ++ -O3 -std = c ++ 14 vector.cpp

เครื่อง:

Intel(R) Xeon(R) CPU E5-2690 v2 @ 3.00GHz 

OS:

2.6.32-642.13.1.el6.x86_64

เอาท์พุท:

UseArray completed in 0.167821 seconds
UseVector completed in 0.134402 seconds
UseConstructor completed in 0.134806 seconds
UseFillConstructor completed in 1.00279 seconds
UseVectorPushBack completed in 6.6887 seconds
The whole thing completed in 8.12888 seconds

ที่นี่สิ่งเดียวที่ฉันรู้สึกแปลกคือประสิทธิภาพ "UseFillConstructor" เทียบกับ "UseConstructor"

รหัส:

void UseConstructor()
{
    TestTimer t("UseConstructor");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel> pixels(dimension*dimension);
        for(int i = 0; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }
    }
}


void UseFillConstructor()
{
    TestTimer t("UseFillConstructor");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel> pixels(dimension*dimension, Pixel(255,0,0));
    }
}

ดังนั้น "ค่า" เพิ่มเติมให้ประสิทธิภาพการทำงานช้าลงค่อนข้างมากซึ่งฉันคิดว่าเป็นเพราะการเรียกหลายครั้งเพื่อคัดลอกตัวสร้าง แต่...

รวบรวม:

gcc-6.2.0/bin/g++ -std=c++14 -O vector.cpp

เอาท์พุท:

UseArray completed in 1.02464 seconds
UseVector completed in 1.31056 seconds
UseConstructor completed in 1.47413 seconds
UseFillConstructor completed in 1.01555 seconds
UseVectorPushBack completed in 6.9597 seconds
The whole thing completed in 11.7851 seconds

ดังนั้นในกรณีนี้การเพิ่มประสิทธิภาพ gcc จึงเป็นสิ่งสำคัญมาก แต่ก็ไม่สามารถช่วยคุณได้มากนักเมื่อมีการระบุค่าเป็นค่าเริ่มต้น สิ่งนี้ขัดต่อการสอนของฉันจริง ๆ หวังว่ามันจะช่วยโปรแกรมเมอร์ใหม่เมื่อเลือกรูปแบบการเริ่มต้นเวกเตอร์


0

ดูเหมือนว่าจะขึ้นอยู่กับธงคอมไพเลอร์ นี่คือรหัสมาตรฐาน:

#include <chrono>
#include <cmath>
#include <ctime>
#include <iostream>
#include <vector>


int main(){

    int size = 1000000; // reduce this number in case your program crashes
    int L = 10;

    std::cout << "size=" << size << " L=" << L << std::endl;
    {
        srand( time(0) );
        double * data = new double[size];
        double result = 0.;
        std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
        for( int l = 0; l < L; l++ ) {
            for( int i = 0; i < size; i++ ) data[i] = rand() % 100;
            for( int i = 0; i < size; i++ ) result += data[i] * data[i];
        }
        std::chrono::steady_clock::time_point end   = std::chrono::steady_clock::now();
        auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
        std::cout << "Calculation result is " << sqrt(result) << "\n";
        std::cout << "Duration of C style heap array:    " << duration << "ms\n";
        delete data;
    }

    {
        srand( 1 + time(0) );
        double data[size]; // technically, non-compliant with C++ standard.
        double result = 0.;
        std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
        for( int l = 0; l < L; l++ ) {
            for( int i = 0; i < size; i++ ) data[i] = rand() % 100;
            for( int i = 0; i < size; i++ ) result += data[i] * data[i];
        }
        std::chrono::steady_clock::time_point end   = std::chrono::steady_clock::now();
        auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
        std::cout << "Calculation result is " << sqrt(result) << "\n";
        std::cout << "Duration of C99 style stack array: " << duration << "ms\n";
    }

    {
        srand( 2 + time(0) );
        std::vector<double> data( size );
        double result = 0.;
        std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
        for( int l = 0; l < L; l++ ) {
            for( int i = 0; i < size; i++ ) data[i] = rand() % 100;
            for( int i = 0; i < size; i++ ) result += data[i] * data[i];
        }
        std::chrono::steady_clock::time_point end   = std::chrono::steady_clock::now();
        auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
        std::cout << "Calculation result is " << sqrt(result) << "\n";
        std::cout << "Duration of std::vector array:     " << duration << "ms\n";
    }

    return 0;
}

แฟล็กการปรับให้เหมาะสมที่สุดต่างกันจะให้คำตอบที่ต่างกัน:

$ g++ -O0 benchmark.cpp 
$ ./a.out 
size=1000000 L=10
Calculation result is 181182
Duration of C style heap array:    118441ms
Calculation result is 181240
Duration of C99 style stack array: 104920ms
Calculation result is 181210
Duration of std::vector array:     124477ms
$g++ -O3 benchmark.cpp
$ ./a.out 
size=1000000 L=10
Calculation result is 181213
Duration of C style heap array:    107803ms
Calculation result is 181198
Duration of C99 style stack array: 87247ms
Calculation result is 181204
Duration of std::vector array:     89083ms
$ g++ -Ofast benchmark.cpp 
$ ./a.out 
size=1000000 L=10
Calculation result is 181164
Duration of C style heap array:    93530ms
Calculation result is 181179
Duration of C99 style stack array: 80620ms
Calculation result is 181191
Duration of std::vector array:     78830ms

ผลลัพธ์ที่แน่นอนของคุณจะแตกต่างกันไป แต่นี่เป็นเรื่องปกติในเครื่องของฉัน


0

จากประสบการณ์ของผมบางครั้งเพียงแค่บางครั้งอาจจะหลายครั้งช้ากว่าvector<int> int[]สิ่งหนึ่งที่ต้องระวังคือเวกเตอร์ของเวกเตอร์นั้นแตกต่างint[][]กันมาก เนื่องจากองค์ประกอบอาจไม่ต่อเนื่องกันในหน่วยความจำ ซึ่งหมายความว่าคุณสามารถปรับขนาดเวกเตอร์ที่แตกต่างกันภายในหนึ่งหลัก แต่ CPU int[][]อาจจะไม่สามารถที่จะองค์ประกอบแคชเช่นเดียวกับในกรณีของ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.