Việc sử dụng 'tự động' của C ++ 11 có thể cải thiện hiệu suất không?


230

Tôi có thể thấy lý do tại sao autoloại trong C ++ 11 cải thiện tính chính xác và khả năng bảo trì. Tôi đã đọc rằng nó cũng có thể cải thiện hiệu suất ( Hầu như luôn luôn tự động bởi Herb Sutter), nhưng tôi bỏ lỡ một lời giải thích tốt.

  • Làm thế nào có thể autocải thiện hiệu suất?
  • Bất cứ ai có thể đưa ra một ví dụ?

5
Xem Herbutter.com/2013/06/13/ từ đó nói về việc tránh các chuyển đổi ngầm vô tình, ví dụ từ tiện ích sang widget. Đây không phải là một vấn đề phổ biến.
Jonathan Wakely

42
Bạn có chấp nhận, làm cho nó ít có khả năng vô tình làm bi quan đến việc cải thiện hiệu suất không?
5gon12eder

1
Chỉ có thể làm sạch mã trong tương lai, có thể
Croll

Chúng tôi cần một câu trả lời ngắn gọn: Không nếu bạn tốt. Nó có thể ngăn ngừa những sai lầm 'noobish'. C ++ có một đường cong học tập giết chết những người không thực hiện được.
Alec Teal

Câu trả lời:


309

autocó thể hỗ trợ hiệu suất bằng cách tránh 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
}

Thấy lỗi không? Chúng tôi đang ở đây, nghĩ rằng chúng tôi thanh lịch lấy mọi vật phẩm trên bản đồ bằng cách tham chiếu 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 sự chúng tôi đang sao chép mọi yếu 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>. Do đó, khi chúng tôi (ngầm) có:

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

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

int const& i = 2.0; // perfectly OK

Chuyển đổi loại là một chuyển đổi ngầm định được phép với cùng lý do bạn có thể chuyển đổi a const Keythành a Key, nhưng chúng tôi phải xây dựng tạm thời loại mới để cho phép điều đó. Do đó, vòng lặp của chúng tôi có hiệu quả:

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, thực tế không có một __tmpđối tượng, nó chỉ ở đó để minh họa, trong thực tế, tạm thời không tên chỉ bị ràng buộc itemtrong suốt cuộc đời của nó).

Chỉ cần thay đổi thành:

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

chỉ lưu cho chúng tôi một tấn bản sao - bây giờ loại được tham chiếu phù hợp với loại trình khởi tạo, do đó không cần tạm thời hoặc chuyển đổi, chúng tôi chỉ có thể thực hiện tham chiếu trực tiếp.


19
@Barry Bạn có thể giải thích lý do tại sao trình biên dịch sẽ vui vẻ tạo các bản sao thay vì phàn nàn về việc cố gắng coi std::pair<const Key, Val> const &như là một std::pair<Key, Val> const &? Mới sử dụng C ++ 11, không chắc chắn phạm vi cho và autochơi trong này.
Agop

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

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

38
Tôi vẫn không nghĩ rằng đây là bằng chứng cho thấy " autocải thiện hiệu suất". Đó chỉ là một ví dụ " autogiúp ngăn ngừa các lỗi lập trình viên phá hủy hiệu năng". Tôi trình bày rằng có một sự khác biệt tinh tế nhưng quan trọng giữa hai. Tuy nhiên, +1.
Các cuộc đua nhẹ nhàng trong quỹ đạo

70

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

Một ví dụ điển hình đến từ (ab) bằng cách 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ể thực hiện cuộc gọi so sánh, trong khi nếu bạn xây dựng một std::functionđối tượng, không chỉ cuộc gọi không thể được nội tuyến, mà bạn còn phải trải qua quá trình tra cứu đa hình trong phần bên trong của hàm bọc.

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

auto && f = MakeAThing();

Đây luôn là một tham chiếu, ràng buộc với giá trị của biểu thức gọi hàm và không bao giờ xây dựng bất kỳ đối tượng bổ sung nào. Nếu bạn không biết loại giá trị được trả về, 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 loại trả về không thể di chuyển và giá trị trả về là một giá trị.)


Vì vậy, đây là lý do "tránh loại bỏ" để sử dụng auto. 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 trang; Tại sao không autocung cấp cho bạn tốc độ chỉ đơn giản là gõ loại ở đó? (Tôi nghĩ câu trả lời là "bạn hiểu sai loại và nó âm thầm chuyển đổi") Điều này làm cho nó trở thành một ví dụ ít được giải thích về câu trả lời của Barry, phải 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 - Adam Nevraumont

2
"không chỉ cuộc gọi không thể được nội tuyến" - tại sao lại như vậy? Bạn có nghĩa là trong một cái gì đó ngăn chặn nguyên tắc cuộc gọi được devirtualized sau khi số liệu phân tích lưu lượng nếu các chuyên ngành liên quan của std::bind, std::functionstd::stable_partitiontất cả đều được inlined? Hoặc chỉ trong thực tế, không có trình biên dịch C ++ nào đủ mạnh để sắp xếp các mớ hỗn độn?
Steve Jessop

@SteveJessop: Chủ yếu là cái sau - sau khi bạn đi qua hàm std::functiontạo, sẽ rất phức tạp để xem qua cuộc gọi thực tế, đặc biệt là với 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ư thể ...
Kerrek SB

41

Có hai loại.

autocó thể tránh được loại tẩy. 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::bindhoặc các mẫu biểu thức khác giống như mọi thứ).

Nếu không auto, cuối cùng bạn phải gõ xóa dữ liệu xuống một cái gì đó như thế nào std::function. Loại tẩy có chi phí.

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

task1có loại tổng phí xóa - phân bổ heap có thể, khó khăn trong việc sắp xếp nó và chi phí gọi bảng chức năng ảo. task2khô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 kiểu; các loại khác có thể phức tạp đến mức họ chỉ cần nó trong thực tế.

Thứ hai, bạn có thể nhận được 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()trả về Bar const&hoặc Barthậm chí Bar&, nơi Foocó thể được xây dựng từ đó Bar. Tạm thời Foosẽ được tạo, sau đó ràng buộc fvà thời gian tồn tại của nó sẽ được kéo dài cho đến khi fbiến mất.

Lập trình viên có thể có ý định Bar const& fvà không có ý định tạo một bản sao ở đó, nhưng một bản sao được tạo ra bất kể.

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

Cả @Barry và @KerrekSB lần đầ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ụ trung tâm.


9

Ba câu trả lời hiện tại đưa ra các ví dụ trong đó việc sử dụng autogiúp Giúp làm cho nó ít có khả năng vô tình làm bi quan đến hiệu quả làm cho nó "cải thiện hiệu suất".

Có một mặt trái của đồng tiền. Sử dụng autovớ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à chạy được). Ví dụ: câu hỏi này hỏi cách sử dụng autocác kết quả khác nhau (không chính xác) bằng thư viện Eigen, tức là các 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 được minh bạch cho 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 sự 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;).


1
Đã thêm câu hỏi ngược lại: stackoverflow.com/questions/38415831/ Kẻ
Leon
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.