2008-09-18 46 views
34

Tôi đang viết một vòng lặp bên trong cần đặt struct s trong bộ nhớ liền kề. Tôi không biết có bao nhiêu trong số này struct s sẽ có trước thời hạn. Vấn đề của tôi là STL của vector khởi tạo giá trị của nó là 0, vì vậy bất kể tôi làm gì, tôi phải trả chi phí khởi tạo cộng với chi phí thiết lập các thành viên của struct với giá trị của chúng.vectơ STL với bộ nhớ chưa được khởi tạo?

Có cách nào để ngăn chặn việc khởi tạo hay có một vùng chứa STL giống như vậy với bộ nhớ tiếp giáp có thể thay đổi và các phần tử chưa được khởi tạo không?

(Tôi chắc chắn rằng phần này của mã cần phải được tối ưu hóa, và tôi chắc chắn rằng việc khởi tạo là một chi phí đáng kể.)

Ngoài ra, xem ý kiến ​​của tôi dưới đây để làm rõ về khi khởi tạo xảy ra.

MỘT SỐ Mã sản phẩm:

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]; 
    } 
} 
+1

Lưu ý - sử dụng dự trữ() không phải là một giải pháp, vì bạn không thể truy cập một cách hợp pháp các dữ liệu đó là tại các địa điểm kết thúc() trở lên. –

+1

Làm rõ thêm: nó không phải là các nhà xây dựng khởi tạo các giá trị để 0. Đó là thay đổi kích cỡ các cuộc gọi chèn, mà không. –

+0

Bạn có thể cung cấp cho chúng tôi bản khai cấu trúc không? Cảm ơn ... :-) – paercebal

Trả lời

23

std::vector phải khởi tạo các giá trị trong mảng bằng cách nào đó, có nghĩa là một số hàm tạo (hoặc bản sao-hàm dựng) phải được gọi. Các hành vi của vector (hoặc bất kỳ lớp container) là không xác định nếu bạn đã truy cập vào phần uninitialized của mảng như thể nó đã được khởi tạo.

Cách tốt nhất là sử dụng reserve()push_back(), sao cho hàm tạo bản sao được sử dụng, tránh xây dựng mặc định.

Sử dụng mã ví dụ của bạn:

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

std::vector<YourData> 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])); 
    } 
} 

Vấn đề duy nhất với gọi reserve() (hoặc resize()) như thế này là bạn có thể sẽ viện dẫn sao chép constructor thường xuyên hơn bạn cần. Nếu bạn có thể đưa ra dự đoán tốt về kích thước cuối cùng của mảng, thì tốt hơn là hãy reserve() khoảng trắng một lần ngay từ đầu. Tuy nhiên, nếu bạn không biết kích thước cuối cùng, ít nhất số lượng bản sao sẽ ở mức trung bình tối thiểu.

Trong phiên bản hiện tại của C++, vòng lặp bên trong hơi không hiệu quả vì giá trị tạm thời được tạo trên ngăn xếp, sao chép được xây dựng vào bộ nhớ vectơ và cuối cùng là tạm thời bị hủy. Tuy nhiên phiên bản tiếp theo của C++ có một tính năng gọi là tham chiếu R-Value (T&&) sẽ giúp ích.

Giao diện được cung cấp bởi std::vector không cho phép tùy chọn khác, sử dụng một số lớp giống như nhà máy để tạo giá trị khác với mặc định. Đây là một ví dụ sơ bộ về mô hình này sẽ được thực hiện như thế nào trong C++:

template <typename T> 
class my_vector_replacement { 

    // ... 

    template <typename F> 
    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)); 
    } 
} 

Việc này có nghĩa là bạn phải tạo lớp vectơ của riêng mình. Trong trường hợp này nó cũng phức tạp những gì cần phải có được một ví dụ đơn giản. Nhưng có thể có những lúc sử dụng chức năng của nhà máy như thế này tốt hơn, ví dụ nếu chèn là có điều kiện trên một số giá trị khác, và bạn sẽ phải xây dựng một cách vô điều kiện một số điều vô cùng tốn kém ngay cả khi nó không thực sự cần thiết.

1

Sử dụng phương pháp std :: vector :: dự trữ(). Nó sẽ không thay đổi kích thước của vector, nhưng nó sẽ phân bổ không gian.

3

Ơ ...

thử phương pháp:

std::vector<T>::reserve(x) 

Nó sẽ cho phép bạn dự trữ đủ bộ nhớ cho mặt hàng x mà không cần khởi tạo bất kỳ (vector của bạn vẫn còn trống). Vì vậy, sẽ không có sự phân bổ lại cho đến khi đi qua x.

Điểm thứ hai là vector sẽ không khởi tạo các giá trị bằng 0. Bạn đang thử nghiệm mã của bạn trong gỡ lỗi?

Sau khi xác minh trên g ++, đoạn code sau:

#include <iostream> 
#include <vector> 

struct MyStruct 
{ 
    int m_iValue00 ; 
    int m_iValue01 ; 
} ; 

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

    std::vector<MyStruct> aMyStruct ; 

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

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

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

    return 0 ; 
} 

cho kết quả như sau:

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

Việc khởi bạn thấy có lẽ là một artifact.

[EDIT] Sau khi nhận xét về thay đổi kích thước, tôi đã sửa đổi mã để thêm dòng thay đổi kích thước. Việc thay đổi kích thước có hiệu quả gọi hàm khởi tạo mặc định của đối tượng bên trong vectơ, nhưng nếu hàm tạo mặc định không làm gì cả, thì không có gì được khởi tạo ... Tôi vẫn tin đó là một tạo tác (tôi đã quản lý lần đầu tiên có toàn bộ véc-tơ zerooed với đoạn mã sau:

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

Vậy ... : -./

[EDIT 2] giống như đã được cung cấp bởi Arkadiy, giải pháp là sử dụng một constructor inline lấy thông số mong muốn cái gì đó giống như

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

Điều này có thể sẽ được inlined trong mã của bạn.

Tuy nhiên, bạn vẫn nên nghiên cứu mã của mình bằng một trình hồ sơ để đảm bảo đoạn mã này là nút cổ chai của ứng dụng của bạn.

+0

Tôi đã viết một lưu ý ở trên. Nó không phải là hàm tạo của vectơ khởi tạo 0. Nó thay đổi kích thước(). –

+0

Trong trường hợp này MyStruct có một hàm tạo tầm thường nên không có gì được khởi tạo. Điều này có thể khác với tình hình của OP. –

+0

Tôi nghĩ bạn đang đi đúng hướng. Tôi không có hàm tạo nào được định nghĩa trong cấu trúc, vì vậy hàm tạo mặc định của nó (tôi tin) không khởi tạo. Tôi sẽ kiểm tra nếu thêm một constructor mặc định mà không có gì giải quyết vấn đề. –

0

Bản thân các cấu trúc có cần phải ở trong bộ nhớ liền kề không, hoặc bạn có thể lấy đi với việc có vectơ cấu trúc * không?

Vectơ tạo bản sao của bất kỳ thứ gì bạn thêm vào chúng, do đó, sử dụng vectơ của con trỏ thay vì đối tượng là một cách để cải thiện hiệu suất.

+0

Họ phải tiếp giáp. Họ đang ở trong một bộ đệm sắp được gửi qua mạng như một phần lớn. –

0

Tôi không nghĩ STL là câu trả lời của bạn. Bạn sẽ cần phải cuộn loại giải pháp của riêng bạn bằng cách sử dụng realloc(). Bạn sẽ phải lưu trữ một con trỏ và kích thước, hoặc số lượng các phần tử và sử dụng nó để tìm nơi bắt đầu thêm các phần tử sau một 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; 
} 
4

Vì vậy, đây là vấn đề, thay đổi kích thước đang gọi chèn, đang thực hiện xây dựng bản sao từ phần tử được tạo mặc định cho từng phần tử mới được thêm vào. Để có được điều này đến 0 chi phí, bạn cần phải viết hàm tạo mặc định của riêng bạn và hàm tạo bản sao của riêng bạn làm hàm rỗng. Làm điều này với hàm tạo bản sao của bạn là ý tưởng rất xấu vì nó sẽ phá vỡ tiêu chuẩn :: thuật toán phân bổ lại nội bộ của vectơ.

Tóm tắt: Bạn sẽ không thể thực hiện việc này với std :: vector.

+0

Đây là vấn đề thực sự. std :: vector nên nhận ra nó không phải làm bất kỳ khởi tạo nếu T có một constructor mặc định tầm thường. Cảm ơn bạn đã chỉ ra rằng các nhà xây dựng bản sao là những gì đang làm công việc không cần thiết ở đây. –

8

Để làm rõ về câu trả lời dự trữ(): bạn cần sử dụng dự trữ() cùng với push_back(). Bằng cách này, hàm tạo mặc định không được gọi cho mỗi phần tử, mà đúng hơn là hàm tạo bản sao. Bạn vẫn phải chịu hình phạt của việc thiết lập cấu trúc của bạn trên ngăn xếp, và sau đó sao chép nó vào vectơ. Mặt khác, có thể là nếu bạn sử dụng

vect.push_back(MyStruct(fieldValue1, fieldValue2)) 

trình biên dịch sẽ tạo cá thể mới trực tiếp trong bộ nhớ liên quan đến vectơ. Nó phụ thuộc vào cách trình tối ưu hóa thông minh. Bạn cần kiểm tra mã được tạo để tìm hiểu.

+2

Hóa ra là trình tối ưu hóa cho gcc, ở mức O3, không đủ thông minh để tránh sao chép. –

1

Từ nhận xét của bạn đến các áp phích khác, có vẻ như bạn đang bị bỏ lại với malloc() và bạn bè. Vector sẽ không cho phép bạn có các yếu tố không bị cản trở.

1

Từ mã của bạn, có vẻ như bạn có vectơ cấu trúc trong đó mỗi cấu trúc bao gồm 2 int. Thay vào đó, bạn có thể sử dụng 2 vectơ ints không? Sau đó,

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

Bây giờ bạn không phải trả tiền để sao chép cấu trúc mỗi lần.

+0

Thú vị. Điều này có thể chỉ hoạt động - có vẻ như nó sẽ tránh việc xây dựng một đối tượng trung gian. – nobar

0

tôi sẽ làm một cái gì đó như:

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])); 
    } 
} 

Bạn cần phải xác định một ctor cho các loại hình được lưu trữ trong các memberVector, nhưng đó là một chi phí nhỏ vì nó sẽ cung cấp cho bạn tốt nhất của cả hai thế giới; không có khởi tạo không cần thiết được thực hiện và không có sự phân bổ lại sẽ xảy ra trong vòng lặp.

+0

Điều này dường như không giải quyết được vấn đề vì nó sử dụng MyType tạm thời() và sao chép vào vectơ. Vẫn còn một khởi tạo kép. – nobar

10

C++ 0x bổ sung thêm một chức năng thành viên mới mẫu emplace_back-vector (mà dựa trên các mẫu variadic và chuyển tiếp hoàn hảo) mà không làm bất kỳ temporaries hoàn toàn:

memberVector.emplace_back(data1[i], data2[i]); 
1

Nếu bạn thực sự nhấn mạnh vào việc có các yếu tố uninitialized và hy sinh một số phương thức như front(), back(), push_back(), sử dụng vector tăng từ số. Nó cho phép bạn thậm chí không bảo toàn các phần tử hiện có khi gọi lại kích thước() ...

6

Trong C++ 11 (và tăng), bạn có thể sử dụng phiên bản mảng unique_ptr để phân bổ mảng chưa được khởi tạo. Đây không phải là một stl container, nhưng vẫn là bộ nhớ được quản lý và C++ - ish sẽ đủ tốt cho nhiều ứng dụng.

auto my_uninit_array = std::unique_ptr<mystruct[]>(new mystruct[count]); 
1

Bạn có thể sử dụng loại trình bao bọc quanh loại phần tử, với hàm tạo mặc định không có gì. Ví dụ .:

template <typename T> 
struct no_init 
{ 
    T value; 

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

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

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

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

Cách sử dụng:

std::vector<no_init<char>> vec; 
vec.resize(2ul * 1024ul * 1024ul * 1024ul); 
Các vấn đề liên quan