2015-09-10 17 views
210

Tôi có thể thấy lý do tại sao loại auto trong C++ 11 cải thiện tính chính xác và bảo trì. Tôi đã đọc rằng nó cũng có thể cải thiện hiệu suất (Almost Always Auto bởi Herb Sutter), nhưng tôi nhớ một lời giải thích tốt.Việc sử dụng 'tự động' của C++ 11 có cải thiện hiệu suất không?

  • Làm thế nào để auto cải thiện hiệu suất?
  • Có ai cho ví dụ không?
+3

Xem http://herbsutter.com/2013/06/13/gotw-93-solution-auto-variables-part-2/ nói về việc tránh các chuyển đổi ngầm ngẫu nhiên, ví dụ: từ tiện ích đến tiện ích. Nó không phải là một vấn đề phổ biến. –

+41

Bạn có chấp nhận "làm cho nó ít có khả năng vô tình pessimize" như là một cải tiến hiệu suất? – 5gon12eder

+1

Sự hoàn hảo của việc làm sạch mã chỉ trong tương lai, có thể – Croll

Trả lời

281

auto có thể hỗ trợ hiệu suất bằng cách tránh các chuyển đổi ngầm im lặng. Một ví dụ tôi thấy hấp dẫn là như sau.

std::map<Key, Val> m; 
// ... 

for (std::pair<Key, Val> const& item : m) { 
    // do stuff 
} 

Xem lỗi? Ở đây, chúng tôi nghĩ rằng chúng tôi đang lấy mọi mục trong bản đồ bằng tham chiếu const và sử dụng biểu thức phạm vi mới để làm rõ ý định của chúng tôi, nhưng thực ra chúng tôi đang sao chép mỗi phần tử. Điều này là do std::map<Key, Val>::value_typestd::pair<const Key, Val>, không phải std::pair<Key, Val>. Vì vậy, khi chúng tôi (ngầm) có:

std::pair<Key, Val> const& item = *iter; 

Thay vì tham gia một tham chiếu đến một đối tượng hiện có và để nó ở đó, chúng ta phải làm một loại chuyển đổi. Bạn được phép để có một tham chiếu const đến một đối tượng (hoặc tạm thời) của một loại khác nhau miễn là có một chuyển đổi ngầm có sẵn, ví dụ:

int const& i = 2.0; // perfectly OK 

Việc chuyển đổi loại là một chuyển đổi ngầm cho phép đối với lý do tương tự bạn có thể chuyển đổi một số const Key thành Key, nhưng chúng tôi phải tạo tạm thời loại mới để cho phép điều đó.Như vậy, hiệu quả của chúng tôi vòng lặp thực hiện:

std::pair<Key, Val> __tmp = *iter;  // construct a temporary of the correct type 
std::pair<Key, Val> const& item = __tmp; // then, take a reference to it 

(Tất nhiên, có không phải là thực sự là một đối tượng __tmp, nó chỉ có tính minh họa, trong thực tế giấu tên tạm thời chỉ là ràng buộc để item cho tuổi thọ của pin).

Chỉ cần thay đổi để:

for (auto const& item : m) { 
    // do stuff 
} 

chỉ cứu chúng ta một tấn của bản - bây giờ kiểu tham chiếu phù hợp với loại initializer, vì vậy không tạm thời hoặc chuyển đổi là cần thiết, chúng tôi chỉ có thể làm một tài liệu tham khảo trực tiếp.

+16

@Barry Bạn có thể giải thích lý do tại sao trình biên dịch sẽ vui vẻ sao chép thay vì phàn nàn về việc cố gắng xử lý một cặp 'std :: const &' như một 'std :: pair const &'? Mới đối với C++ 11, không chắc chắn cách phạm vi cho và 'tự động' phát vào đây. – Agop

+0

@Barry Cảm ơn bạn đã giải thích. Đó là phần tôi đã mất tích - vì lý do nào đó, tôi nghĩ rằng bạn không thể có một tham chiếu liên tục đến một tạm thời. Nhưng tất nhiên bạn có thể - nó sẽ chấm dứt tồn tại ở cuối phạm vi của nó. – Agop

+0

@barry Tôi nhận được bạn, nhưng vấn đề là sau đó không có một câu trả lời bao gồm tất cả các lý do để sử dụng 'auto' làm tăng hiệu suất. Vì vậy, tôi sẽ viết nó theo những từ của riêng tôi dưới đây. – Yakk

67

Bởi vì auto suy ra loại biểu thức khởi tạo, không có chuyển đổi loại nào có liên quan. Kết hợp với thuật toán templated, điều này có nghĩa rằng bạn có thể nhận được một tính toán trực tiếp hơn nếu bạn đã tạo ra một loại mình – đặc biệt là khi bạn đang đối phó với các biểu thức có loại bạn không thể đặt tên!

Một ví dụ điển hình đến từ (ab) sử dụng std::function:

std::function<bool(T, T)> cmp1 = std::bind(f, _2, 10, _1); // bad 
auto cmp2 = std::bind(f, _2, 10, _1);      // good 
auto cmp3 = [](T a, T b){ return f(b, 10, a); };   // also good 

std::stable_partition(begin(x), end(x), cmp?); 

Với cmp2cmp3, toàn bộ thuật toán có thể nội tuyến các cuộc gọi so sánh, trong khi đó nếu bạn xây dựng một đối tượng std::function, không chỉ có thể gọi không được inlined, nhưng bạn cũng phải đi qua tra cứu đa hình trong nội thất loại-erased của wrapper hàm.

Một biến thể về chủ đề này là bạn có thể nói:

auto && f = MakeAThing(); 

Đây luôn là một tài liệu tham khảo, liên kết với các giá trị của các biểu hiện chức năng cuộc gọi, và không bao giờ xây dựng bất kỳ đối tượng bổ sung. Nếu bạn không biết loại giá trị trả lại, bạn có thể bị buộc phải xây dựng một đối tượng mới (có thể là tạm thời) thông qua một cái gì đó như T && f = MakeAThing(). (Hơn nữa, auto && thậm chí hoạt động khi kiểu trả về là không thể di chuyển và giá trị trả về là một prvalue.)

+0

Vì vậy, đây là lý do "tránh loại xóa" lý do để sử dụng 'tự động'. Biến thể khác của bạn là "tránh các bản sao tình cờ", nhưng cần chỉnh sửa; tại sao 'tự động 'cung cấp cho bạn tốc độ trên chỉ đơn giản là gõ kiểu đó? (Tôi nghĩ câu trả lời là "bạn nhận được loại sai, và nó âm thầm chuyển đổi") Mà làm cho nó một ví dụ ít giải thích về câu trả lời của Barry, không? Tức là, có hai trường hợp cơ bản: tự động để tránh xóa kiểu và tự động tránh các lỗi loại im lặng vô tình chuyển đổi, cả hai đều có chi phí thời gian chạy. – Yakk

+2

"không chỉ có thể gọi điện thoại không được gạch chân" - tại sao vậy? Bạn có nghĩa là về nguyên tắc một cái gì đó ngăn chặn các cuộc gọi được devirtualized sau khi phân tích lưu lượng dữ liệu nếu các chuyên ngành có liên quan của 'std :: bind',' std :: function' và 'std :: stable_partition' có tất cả được inlined? Hoặc chỉ là trong thực tế không có trình biên dịch C++ sẽ inline đủ mạnh để sắp xếp ra mess? –

+0

@SteveJessop: Chủ yếu là sau - sau khi bạn đi qua hàm tạo 'std :: function', nó sẽ rất phức tạp để xem qua cuộc gọi thực tế, đặc biệt với các tối ưu hóa chức năng nhỏ (vì vậy bạn không thực sự muốn ảo hóa) . Tất nhiên * về nguyên tắc * mọi thứ đều như - nếu ... –

35

Có hai loại.

auto có thể tránh xóa kiểu. Có các loại không thể đặt tên (như lambdas) và các loại gần như không thể đặt tên (như kết quả của std::bind hoặc các mẫu biểu thức khác giống như mọi thứ).

Nếu không có auto, bạn sẽ phải xóa dữ liệu xuống dưới dạng số std::function. Loại tẩy xóa có chi phí.

std::function<void()> task1 = []{std::cout << "hello";}; 
auto task2 = []{std::cout << " world\n";}; 

task1 có kiểu tẩy xoá overhead - một phân bổ đống có thể, khó khăn nội tuyến nó, và ảo bảng chức năng gọi overhead. task2 không có. Lambdas cần tự động hoặc các hình thức khấu trừ loại khác để lưu trữ mà không xóa bỏ loại; các loại khác có thể phức tạp đến nỗi chúng chỉ cần nó trong thực tế.

Thứ hai, bạn có thể gặp phải các loại sai. Trong một số trường hợp, loại sai sẽ hoạt động có vẻ hoàn hảo, nhưng sẽ gây ra một bản sao.

Foo const& f = expression(); 

sẽ biên dịch nếu expression() lợi nhuận Bar const& hoặc Bar hoặc thậm chí Bar&, nơi Foo thể được xây dựng từ Bar. Tạm thời Foo sẽ được tạo, sau đó được liên kết với f và thời gian tồn tại của nó sẽ được kéo dài đến khi f biến mất.

Lập trình viên có thể có nghĩa là Bar const& f và không có ý định sao chép ở đó, nhưng bản sao được tạo bất kể.

Ví dụ phổ biến nhất là loại *std::map<A,B>::const_iterator, là std::pair<A const, B> const& không std::pair<A,B> const&, nhưng lỗi là một loại lỗi làm giảm hiệu suất âm thầm. Bạn có thể tạo một std::pair<A, B> từ một số std::pair<const A, B>. (Khóa trên bản đồ là const, vì chỉnh sửa nó là ý tưởng tồi)

Cả hai @Barry và @KerrekSB đầu tiên minh họa hai nguyên tắc này trong câu trả lời của họ. Đây chỉ đơn giản là một nỗ lực để làm nổi bật hai vấn đề trong một câu trả lời, với từ ngữ nhằm vào vấn đề chứ không phải là ví dụ làm trung tâm.

7

Ba câu trả lời hiện có cung cấp các ví dụ khi sử dụng auto giúp “makes it less likely to unintentionally pessimize” làm cho hiệu quả "cải thiện hiệu suất".

Có mặt trái của đồng xu. Sử dụng auto với các đối tượng có toán tử không trả về đối tượng cơ bản có thể dẫn đến mã không chính xác (vẫn có thể biên dịch và có thể chạy).Ví dụ, this question hỏi cách sử dụng auto đã cung cấp (không chính xác) kết quả khác nhau bằng cách sử dụng thư viện Eigen, ví dụ những dòng sau

const auto resAuto = Ha + Vector3(0.,0.,j * 2.567); 
const Vector3 resVector3 = Ha + Vector3(0.,0.,j * 2.567); 

std::cout << "resAuto = " << resAuto <<std::endl; 
std::cout << "resVector3 = " << resVector3 <<std::endl; 

dẫn đến đầu ra khác nhau. Phải thừa nhận rằng, điều này chủ yếu là do đánh giá lười biếng của Eigens, nhưng mã đó là/nên trong suốt đối với người dùng (thư viện).

Mặc dù hiệu suất không bị ảnh hưởng nhiều ở đây, sử dụng auto để tránh bi quan không chủ ý có thể được phân loại là tối ưu hóa sớm hoặc ít nhất là sai;).

+0

Đã thêm câu hỏi ngược lại: http://stackoverflow.com/questions/38415831/can-the-use-of-c11s-auto-dioriorate-performance-or-even-break-the-code – Leon

Các vấn đề liên quan