2013-04-12 30 views
6

Tôi hiện đang ở giữa một dự án có hiệu suất là rất quan trọng. Sau đây là một số câu hỏi tôi có về vấn đề này.Hình phạt gói con trỏ thông minh. Bản ghi nhớ với std :: map

Question1

dự án của tôi liên quan nhiều boost::shared_ptr .Tôi biết tạo con trỏ được chia sẻ trên chạy bằng boost::make_shared là chậm vì có rất nhiều chi phí như nó cần phải theo dõi references.I muốn biết điều gì nếu các con trỏ chia sẻ tăng đã được tạo sau đó hai câu lệnh này sẽ có hiệu suất giống nhau hoặc sẽ có nhanh hơn cái kia. Nếu con trỏ thông thường nhanh hơn và tôi đã có con trỏ chia sẻ, tôi có những tùy chọn nào để gọi một phương thức mà con trỏ được chia sẻ trỏ tới?

statement1: sharedptr->someMethod(); //here the pointer is a shared ptr created by boost::make_shared 
statement2: regularptr->someMethod(); //here the pointer is a regular one made with new 

Câu hỏi 2

Tôi có một phương pháp dụ (s) đang nhanh chóng được gọi là tạo ra một std::vector<std::string> trên stack mỗi lần. Tôi quyết định lưu trữ con trỏ vector đó trong một std tĩnh :: bản đồ (ví dụ: std::map<std::String,std::vector<std::string>*>. Nếu một vectơ không tồn tại trong bản đồ cho khóa (có thể là tên của phương thức). Địa chỉ vectơ hợp lệ được tạo và thêm vào bản đồ.Vì vậy, câu hỏi của tôi là "có đáng để tìm kiếm bản đồ cho địa chỉ vectơ và trả lại địa chỉ hợp lệ không chỉ tạo một địa chỉ trên ngăn xếp như std::vector<std::string> somevector. Tôi cũng muốn một ý tưởng hiệu suất của std::map tìm

Bất kỳ ý tưởng về những mối quan tâm sẽ được đánh giá

+3

Donald Knuth: "Chúng ta nên quên hiệu quả nhỏ, khoảng 97% thời gian: tối ưu hóa sớm là gốc của tất cả" => hồ sơ xấu đầu tiên, xác định các điểm nóng, tối ưu hóa ** những **. –

Trả lời

13

trả lời đến Q # 1

Nếu các con trỏ thường là nhanh hơn và tôi đã đã chia sẻ con trỏ những tùy chọn nào để tôi có để gọi một phương pháp mà các điểm trỏ được chia sẻ tới?

operator-> trong boost::shared_ptrhas assertion:

typename boost::detail::sp_member_access<T>::type operator->() const 
{ 
    BOOST_ASSERT(px != 0); 
    return px; 
} 

Vì vậy, trước hết, hãy chắc chắn rằng bạn có NDEBUG định (thường là trong phiên bản xây dựng nó được thực hiện tự động):

#define NDEBUG 

Tôi đã thực hiện so sánh lắp ráp giữa hội nghị truyền thông của boost::shared_ptr và con trỏ liệu:

template<int tag,typename T> 
NOINLINE void test(const T &p) 
{ 
    volatile auto anti_opti=0; 
    ASM_MARKER<tag+0>(); 
    anti_opti = p->data; 
    anti_opti = p->data; 
    ASM_MARKER<tag+1>(); 
    (void)anti_opti; 
} 

test<1000>(new Foo); 

ASM mã của test khi TFoo* được (đừng sợ, tôi có diff dưới đây):

_Z4testILi1000EP3FooEvRKT0_: 
.LFB4088: 
.cfi_startproc 
pushq %rbx 
.cfi_def_cfa_offset 16 
.cfi_offset 3, -16 
movq %rdi, %rbx 
subq $16, %rsp 
.cfi_def_cfa_offset 32 
movl $0, 12(%rsp) 
call _Z10ASM_MARKERILi1000EEvv 
movq (%rbx), %rax 
movl (%rax), %eax 
movl %eax, 12(%rsp) 
movl %eax, 12(%rsp) 
call _Z10ASM_MARKERILi1001EEvv 
movl 12(%rsp), %eax 
addq $16, %rsp 
.cfi_def_cfa_offset 16 
popq %rbx 
.cfi_def_cfa_offset 8 
ret 
.cfi_endproc 

test<2000>(boost::make_shared<Foo>()); 

ASM mã của test khi Tboost::shared_ptr<Foo>:

_Z4testILi2000EN5boost10shared_ptrI3FooEEEvRKT0_: 
.LFB4090: 
.cfi_startproc 
pushq %rbx 
.cfi_def_cfa_offset 16 
.cfi_offset 3, -16 
movq %rdi, %rbx 
subq $16, %rsp 
.cfi_def_cfa_offset 32 
movl $0, 12(%rsp) 
call _Z10ASM_MARKERILi2000EEvv 
movq (%rbx), %rax 
movl (%rax), %eax 
movl %eax, 12(%rsp) 
movl %eax, 12(%rsp) 
call _Z10ASM_MARKERILi2001EEvv 
movl 12(%rsp), %eax 
addq $16, %rsp 
.cfi_def_cfa_offset 16 
popq %rbx 
.cfi_def_cfa_offset 8 
ret 
.cfi_endproc 

Dưới đây là đầu ra của diff -U 0 foo_p.asm shared_ptr_foo_p.asm lệnh:

--- foo_p.asm Fri Apr 12 10:38:05 2013 
+++ shared_ptr_foo_p.asm  Fri Apr 12 10:37:52 2013 
@@ -1,2 +1,2 @@ 
-_Z4testILi1000EP3FooEvRKT0_: 
-.LFB4088: 
+_Z4testILi2000EN5boost10shared_ptrI3FooEEEvRKT0_: 
+.LFB4090: 
@@ -11 +11 @@ 
-call _Z10ASM_MARKERILi1000EEvv 
+call _Z10ASM_MARKERILi2000EEvv 
@@ -16 +16 @@ 
-call _Z10ASM_MARKERILi1001EEvv 
+call _Z10ASM_MARKERILi2001EEvv 

Như bạn có thể thấy, sự khác biệt là chỉ trong chữ ký chức năng, và tag phi -giá trị đối số mẫu, mã còn lại là IDENTICAL.


Nói chung - số đếm tham chiếu được đồng bộ hóa giữa các chủ đề (thường thông qua hoạt động nguyên tử). Thay vào đó, nếu bạn sử dụng boost::intrusive_ptr, thì bạn có thể triển khai increment/decrement của riêng mình mà không cần đồng bộ hóa luồng, điều này sẽ tăng tốc độ tính tham chiếu.

Nếu bạn có thể đủ khả năng sử dụng unique_ptr hoặc di chuyển ngữ nghĩa (qua Boost.Move hoặc C++ 11) - thì sẽ không có bất kỳ tính tham chiếu nào - nó sẽ nhanh hơn nữa.

LIVE DEMO WITH ASM OUTPUT

#define NDEBUG 

#include <boost/make_shared.hpp> 
#include <boost/shared_ptr.hpp> 

#define NOINLINE __attribute__ ((noinline)) 

template<int> 
NOINLINE void ASM_MARKER() 
{ 
    volatile auto anti_opti = 11; 
    (void)anti_opti; 
} 

struct Foo 
{ 
    int data; 
}; 

template<int tag,typename T> 
NOINLINE void test(const T &p) 
{ 
    volatile auto anti_opti=0; 
    ASM_MARKER<tag+0>(); 
    anti_opti = p->data; 
    anti_opti = p->data; 
    ASM_MARKER<tag+1>(); 
    (void)anti_opti; 
} 

int main() 
{ 
    { 
     auto p = new Foo; 
     test<1000>(p); 
     delete p; 
    } 
    { 
     test<2000>(boost::make_shared<Foo>()); 
    } 
} 

trả lời đến Q # 2

Tôi có một phương pháp dụ (s) đang nhanh chóng được gọi là tạo ra một std :: vector trên stack mỗi lần.

Nói chung, bạn nên cố gắng sử dụng lại khả năng của vector để tránh phân bổ lại tốn kém.Ví dụ nó là tốt hơn để thay thế:

{ 
    for(/*...*/) 
    { 
     std::vector<value> temp; 
     // do work on temp 
    } 
} 

với:

{ 
    std::vector<value> temp; 
    for(/*...*/) 
    { 
     // do work on temp 
     temp.clear(); 
    } 
} 

Nhưng hình như do gõ std::map<std::string,std::vector<std::string>*> bạn đang cố gắng để phí phạm một số loại memoization.

Như đã gợi ý, thay vì std::map trong đó có O (ln (N)) tra cứu/chèn bạn có thể thử sử dụng boost::unordered_map/std::unordered_map trong đó có O (1) trung bình và O (N) tồi tệ nhất trường hợp phức tạp cho tra cứu/chèn, và địa phương/compactess tốt hơn (bộ nhớ cache thân thiện).

Ngoài ra, cosider thử Boost.Flyweight:

Flyweights là quy mô nhỏ lớp xử lý cấp quyền truy cập thường xuyên để dữ liệu chung chung, do đó cho phép cho việc quản lý một lượng lớn các đơn vị nằm trong giới hạn bộ nhớ hợp lý. Boost.Flyweight giúp dễ dàng sử dụng thành ngữ lập trình phổ biến này bằng cách cung cấp mẫu lớp trọng số, hoạt động như một bản thay thế cho const T.

+0

Công việc thám tử tuyệt vời: D! – Patashu

4

Đối Question1:..

đạt được hiệu suất lớn có thể được achived tại một thiết kế kiến ​​trúc, thuật toán được sử dụng và trong khi mối quan tâm ở mức độ thấp cũng có chỉ quan trọng khi thiết kế highlevel mạnh. Cho phép đến câu hỏi của bạn, Con trỏ thông thường sẽ thực hiện ance cao hơn shared_ptr. Nhưng số tiền trên đầu bạn thấy không sử dụng shared_ptr cũng làm tăng chi phí duy trì mã trong thời gian dài hơn. Cần phải tránh tạo và hủy đối tượng thừa trong các ứng dụng quan trọng về hiệu năng. Trong trường hợp như vậy shared_ptr đóng một vai trò quan trọng mà đóng trong việc chia sẻ các đối tượng phổ biến trên chủ đề bằng cách giảm chi phí phát hành tài nguyên. có con trỏ chia sẻ tiêu tốn nhiều thời gian hơn con trỏ thông thường vì số lần truy cập, phân bổ (đối tượng, truy cập, deleter) v.v. bạn có thể làm cho shared_ptr nhanh hơn bằng cách ngăn không cho bản sao không cần thiết của chúng.use it as ref (shared_ptr const &). Hơn nữa, bạn không cần tài nguyên chia sẻ qua các chủ đề không sử dụng shared_ptr và ptr thông thường sẽ cho hiệu suất tốt hơn trong những trường hợp đó.

Câu hỏi 2

Nếu muốn sử dụng hồ bơi tái sử dụng các đối tượng shared_ptr bạn tốt hơn có thể nhìn vào hồ bơi đối tượng tiếp cận mô hình thiết kế. http://en.wikipedia.org/wiki/Object_pool_pattern

+1

+1 cho "bạn có thể làm cho shared_ptr nhanh hơn bằng cách ngăn chặn bản sao không cần thiết của chúng. Sử dụng nó như ref (shared_ptr const &)." Điều này sẽ tạo nên sự khác biệt lớn! – mark

+0

Cảm ơn câu trả lời, bạn có gợi ý rằng tôi chuyển con trỏ ra làm tham chiếu Để tránh sao chép không? Tôi đang đi ngang qua ở một vài nơi yên tĩnh .. – Rajeshwar

+0

@Rajeshwar có, cũng chỉ sử dụng shared_ptr nếu bạn đang chia sẻ bộ nhớ. Nếu không cần chia sẻ bộ nhớ, bạn có thể sử dụng unique_ptr hoặc bất kỳ cách tiếp cận smartpointer nào khác. Ngoài ra, nếu việc tạo/xóa đối tượng của bạn tiêu tốn nhiều thời gian hơn để tạo các đối tượng trong objectpool để bạn có các đối tượng được tạo trong phần đầu của ứng dụng và bạn có thể tái sử dụng các đối tượng này mà không cần tạo lại chúng. – shivakumar

1

Q1: Chỉ cần nhìn vào việc thực hiện:

T * operator->() const // never throws 
{ 
    BOOST_ASSERT(px != 0); 
    return px; 
} 

Rõ ràng nó trở về một biến thành viên và không tính bất cứ điều gì một cách nhanh chóng, vì vậy hiệu suất sẽ càng nhanh càng dereferencing một con trỏ đồng bằng (tùy thuộc vào quirks bình thường của tối ưu hóa trình biên dịch/hiệu suất của một xây dựng unoptimised luôn luôn có thể được dự kiến ​​sẽ hút - không đáng xem xét).

Q2: ". Là nó có giá trị tìm kiếm một map cho một địa chỉ vector và trở lại một địa chỉ hợp lệ trên chỉ là tạo một trên stack như std::vector<std::string> somevector tôi cũng sẽ giống như một ý tưởng về việc thực hiện của std::map::find"

Cho dù giá trị của nó có phụ thuộc vào lượng dữ liệu phải được sao chép trong vector và ít hơn sẽ mở rộng số lượng nút trong map, thời lượng tiền tố chung trong các khóa được so sánh v.v. luôn luôn, nếu bạn quan tâm, điểm chuẩn. Nói chung, mặc dù, tôi mong đợi câu trả lời là có nếu các vectơ chứa một lượng đáng kể dữ liệu (hoặc dữ liệu chậm để tái tạo). std::map là cây nhị phân cân bằng, vì vậy nói chung bạn mong đợi tra cứu trong O (log2N) trong đó N là số phần tử hiện tại (ví dụ: size()).

Bạn cũng có thể sử dụng bảng băm - cung cấp cho O (1) sẽ nhanh hơn đối với số lượng lớn các phần tử, nhưng không thể nói ngưỡng đó ở đâu. Hiệu suất vẫn phụ thuộc vào tính mở rộng của hàm băm mà bạn sử dụng trên các khóa, chiều dài của chúng (một số triển khai băm giống như std::hash của Microsoft chỉ kết hợp tối đa 10 ký tự cách nhau dọc theo chuỗi bị băm, vì vậy có giới hạn trên cho thời gian thực hiện.), các phương pháp xử lý va chạm bảng băm (ví dụ: danh sách chuyển vị trí để tìm kiếm các nhóm thay thế so với các hàm băm thay thế so với các thùng chứa từ các nhóm) và sự tự thân của va chạm.

2

Câu hỏi 1:

tôi sử dụng con trỏ trong dự án của tôi chia sẻ rộng rãi, nhưng tôi sẽ không muốn sử dụng shared_ptr<T>. Nó đòi hỏi một đối tượng heap được phân bổ riêng biệt với chính bản thân, vì vậy chi phí phân bổ bộ nhớ được tăng gấp đôi và mức sử dụng bộ nhớ tăng lên bởi một số tiền phụ thuộc vào việc thực thi thư viện thời gian chạy của bạn. intrusive_ptr là hiệu quả hơn, nhưng có một vấn đề quan trọng mà irks tôi, và đó là chức năng gọi điện thoại:

void Foo(intrusive_ptr<T> x) {...} 

mỗi khi bạn gọi Foo, số lượng tài liệu tham khảo của tham số x phải được tăng lên với một increment nguyên tử tương đối đắt tiền và sau đó giảm dần trên đường ra. Nhưng điều này là thừa, bởi vì bạn thường có thể giả định rằng người gọi đã có một tham chiếu đến x, và rằng tham chiếu là hợp lệ trong suốt thời gian của cuộc gọi. Có nhiều cách mà người gọi có thể chưa có tham chiếu, nhưng không khó để viết mã của bạn theo cách mà tham chiếu của người gọi luôn hợp lệ.

Vì vậy, tôi thích sử dụng lớp con trỏ thông minh của riêng tôi giống như intrusive_ptr ngoại trừ việc nó chuyển đổi hoàn toàn đến và từ T *. Sau đó, tôi luôn luôn tuyên bố phương pháp của tôi để đưa con trỏ đơn giản, tránh tính tham khảo không cần thiết:

void Foo(T* x) {...} 

Cách tiếp cận này đã được chứng minh để làm việc tốt trong dự án của tôi, nhưng thành thật mà nói tôi không bao giờ thực sự đo sự khác biệt hiệu suất nó làm.

Ngoài ra, thích sử dụng auto_ptr (C++ 03) hoặc unique_ptr (C++ 11) nếu có thể.

Câu hỏi 2:

Tôi không hiểu lý do tại sao bạn đang suy nghĩ về việc sử dụng một std :: bản đồ. Trước hết, hash_map sẽ nhanh hơn (miễn là nó không phải là VC++ Dinkumware thực hiện trong VS2008/2010, details in here somewhere), và thứ hai nếu bạn chỉ cần một vector cho mỗi phương pháp, tại sao không sử dụng biến tĩnh loại std::vector<std::string>?

Nếu bạn phải tra cứu véc tơ trong một hashtable mỗi khi phương pháp được gọi, tôi đoán là bạn sẽ tiết kiệm ít hoặc không có thời gian so với việc tạo ra một véc tơ mới mỗi lần. Nếu bạn tra cứu vectơ trong một std :: map, nó sẽ mất nhiều thời gian hơn.

+2

"Nó đòi hỏi một đối tượng heap được phân bổ riêng biệt với T, vì vậy phí phân bổ bộ nhớ được tăng gấp đôi" - bạn có thể tránh điều này bằng cách sử dụng 'make_shared' –

+0

" 'void Foo (intrusive_ptr x)' mỗi khi bạn gọi Foo, tham chiếu đếm tham số x phải được tăng lên với số tăng nguyên tử tương đối đắt "- khi bạn sử dụng' intrusive_ptr' - bạn có thể thực hiện 'increment' /' decrement' bằng chính bản thân, bạn không phải sử dụng 'increment increments/atomic' nếu bạn không cần syncronization thread. –

+0

@Evgeny Bạn nói đúng, tôi quên mất điều đó. Tuy nhiên, nếu intrusive_ptr_add_ref() cho kiểu T không hỗ trợ tăng nguyên tử, thì đó là điều sẽ xảy ra. Bạn không thể yêu cầu "gia tăng nguyên tử - * trừ * khi intrusive_ptr được chuyển thành tham số cho phương thức". – Qwertie