Có sự khác biệt về hiệu năng giữa i ++ và ++ i trong C ++ không?


352

Chúng tôi có câu hỏi là có một sự khác biệt hiệu suất giữa i++++i trong C ?

Câu trả lời cho C ++ là gì?


Tôi đã thử lại vì hai thẻ đó là cách dễ nhất để tìm câu hỏi về bản chất này. Tôi cũng đã đi qua những người khác không có thẻ gắn kết và đưa cho họ thẻ gắn kết.
George Stocker

104
Có sự khác biệt về hiệu năng giữa việc sử dụng C ++ và ++ C không?
new123456

2
Bài viết: Có hợp lý không khi sử dụng toán tử gia tăng tiền tố ++ nó thay vì toán tử postfix nó ++ cho các trình vòng lặp? - viva64.com/vi/b/0093

Câu trả lời:


426

[Tóm tắt điều hành: Sử dụng ++inếu bạn không có lý do cụ thể để sử dụng i++.]

Đối với C ++, câu trả lời phức tạp hơn một chút.

Nếu ilà một loại đơn giản (không phải là một thể hiện của lớp C ++), thì câu trả lời được đưa ra cho C ("Không có sự khác biệt về hiệu năng") , vì trình biên dịch đang tạo mã.

Tuy nhiên, nếu ilà một thể hiện của lớp C ++, thì i++++iđang thực hiện các cuộc gọi đến một trong các operator++hàm. Đây là một cặp tiêu chuẩn của các chức năng này:

Foo& Foo::operator++()   // called for ++i
{
    this->data += 1;
    return *this;
}

Foo Foo::operator++(int ignored_dummy_value)   // called for i++
{
    Foo tmp(*this);   // variable "tmp" cannot be optimized away by the compiler
    ++(*this);
    return tmp;
}

Vì trình biên dịch không tạo mã, mà chỉ gọi một operator++hàm, không có cách nào để tối ưu hóa tmpbiến và hàm tạo sao chép liên quan của nó. Nếu hàm tạo sao chép đắt tiền, thì điều này có thể có tác động hiệu suất đáng kể.


3
Những gì trình biên dịch có thể tránh là bản sao thứ hai để trả về tmp, bằng cách phân bổ tmp trong trình gọi, thông qua NRVO, như được đề cập bởi một bình luận khác.
Blaisorblade

7
Trình biên dịch có thể tránh điều này hoàn toàn nếu toán tử ++ được nội tuyến không?
Eduard - Gabriel Munteanu

16
Có nếu toán tử ++ được nội tuyến và tmp không bao giờ được sử dụng, nó có thể được gỡ bỏ trừ khi hàm tạo hoặc hàm hủy của đối tượng tmp có tác dụng phụ.
Zan Lynx

5
@kriss: sự khác biệt giữa C và C ++ là trong C bạn có một đảm bảo rằng toán tử sẽ được nội tuyến và tại thời điểm đó, trình tối ưu hóa tốt sẽ có thể loại bỏ sự khác biệt; thay vào đó trong C ++, bạn không thể giả sử nội tuyến - không phải lúc nào cũng vậy.
Blaisorblade

3
Tôi sẽ +1 NẾU câu trả lời đã đề cập một vài điều về các lớp chứa con trỏ (cho dù là tự động, thông minh hoặc nguyên thủy) cho bộ nhớ được phân bổ động (heap), trong đó hàm tạo sao chép nhất thiết phải thực hiện các bản sao sâu. Trong những trường hợp như vậy, không có đối số, ++ i có lẽ là một thứ tự cường độ hiệu quả hơn i ++. Chìa khóa của họ là tập thói quen sử dụng tiền tăng thêm bất cứ khi nào ngữ nghĩa tăng sau không thực sự được yêu cầu bởi thuật toán của bạn, và sau đó bạn sẽ có thói quen viết mã mà bản chất cho vay hiệu quả cao hơn, bất kể như thế nào cũng trình biên dịch của bạn có thể tối ưu hóa.
phonetagger

64

Đúng. Có.

Toán tử ++ có thể hoặc không thể được định nghĩa là một hàm. Đối với các kiểu nguyên thủy (int, double, ...) các toán tử được tích hợp sẵn, do đó trình biên dịch có thể sẽ tối ưu hóa mã của bạn. Nhưng trong trường hợp một đối tượng xác định toán tử ++ thì mọi thứ lại khác.

Hàm toán tử ++ (int) phải tạo một bản sao. Đó là bởi vì postfix ++ dự kiến ​​sẽ trả về một giá trị khác với giá trị mà nó giữ: nó phải giữ giá trị của nó trong một biến tạm thời, tăng giá trị của nó và trả về temp. Trong trường hợp toán tử ++ (), tiền tố ++, không cần tạo bản sao: đối tượng có thể tự tăng và sau đó chỉ cần trả về chính nó.

Dưới đây là một minh họa về điểm:

struct C
{
    C& operator++();      // prefix
    C  operator++(int);   // postfix

private:

    int i_;
};

C& C::operator++()
{
    ++i_;
    return *this;   // self, no copy created
}

C C::operator++(int ignored_dummy_value)
{
    C t(*this);
    ++(*this);
    return t;   // return a copy
}

Mỗi khi bạn gọi toán tử ++ (int), bạn phải tạo một bản sao và trình biên dịch không thể làm bất cứ điều gì về nó. Khi được lựa chọn, sử dụng toán tử ++ (); Bằng cách này, bạn không lưu một bản sao. Nó có thể có ý nghĩa trong trường hợp có nhiều gia số (vòng lặp lớn?) Và / hoặc các đối tượng lớn.


2
"Toán tử gia tăng trước giới thiệu một phụ thuộc dữ liệu trong mã: CPU phải chờ cho hoạt động gia tăng được hoàn thành trước khi giá trị của nó có thể được sử dụng trong biểu thức. Trên CPU có đường ống sâu, điều này giới thiệu một gian hàng. Không có phụ thuộc dữ liệu cho toán tử gia tăng bài. " ( Kiến trúc công cụ trò chơi (phiên bản 2) ) Vì vậy, nếu bản sao của bài tăng không được tính toán chuyên sâu, nó vẫn có thể đánh bại mức tăng trước.
Matthias

Trong mã postfix, cách thức hoạt động C t(*this); ++(*this); return t;của dòng này Trong dòng thứ hai, bạn đang tăng đúng con trỏ này, vậy làm thế nào để tđược cập nhật nếu bạn tăng số này. Không phải các giá trị này đã được sao chép vào t?
rasen58

The operator++(int) function must create a copy.không có nó không phải là. Không có nhiều bản sao hơnoperator++()
Severin Pappadeux

47

Đây là một điểm chuẩn cho trường hợp khi các toán tử gia tăng ở các đơn vị dịch khác nhau. Trình biên dịch với g ++ 4.5.

Bỏ qua các vấn đề phong cách cho bây giờ

// a.cc
#include <ctime>
#include <array>
class Something {
public:
    Something& operator++();
    Something operator++(int);
private:
    std::array<int,PACKET_SIZE> data;
};

int main () {
    Something s;

    for (int i=0; i<1024*1024*30; ++i) ++s; // warm up
    std::clock_t a = clock();
    for (int i=0; i<1024*1024*30; ++i) ++s;
    a = clock() - a;

    for (int i=0; i<1024*1024*30; ++i) s++; // warm up
    std::clock_t b = clock();
    for (int i=0; i<1024*1024*30; ++i) s++;
    b = clock() - b;

    std::cout << "a=" << (a/double(CLOCKS_PER_SEC))
              << ", b=" << (b/double(CLOCKS_PER_SEC)) << '\n';
    return 0;
}

Gia số O (n)

Kiểm tra

// b.cc
#include <array>
class Something {
public:
    Something& operator++();
    Something operator++(int);
private:
    std::array<int,PACKET_SIZE> data;
};


Something& Something::operator++()
{
    for (auto it=data.begin(), end=data.end(); it!=end; ++it)
        ++*it;
    return *this;
}

Something Something::operator++(int)
{
    Something ret = *this;
    ++*this;
    return ret;
}

Các kết quả

Kết quả (thời gian tính bằng giây) với g ++ 4.5 trên máy ảo:

Flags (--std=c++0x)       ++i   i++
-DPACKET_SIZE=50 -O1      1.70  2.39
-DPACKET_SIZE=50 -O3      0.59  1.00
-DPACKET_SIZE=500 -O1    10.51 13.28
-DPACKET_SIZE=500 -O3     4.28  6.82

Gia số O (1)

Kiểm tra

Bây giờ chúng ta hãy lấy tập tin sau:

// c.cc
#include <array>
class Something {
public:
    Something& operator++();
    Something operator++(int);
private:
    std::array<int,PACKET_SIZE> data;
};


Something& Something::operator++()
{
    return *this;
}

Something Something::operator++(int)
{
    Something ret = *this;
    ++*this;
    return ret;
}

Nó không làm gì trong sự gia tăng. Điều này mô phỏng trường hợp khi tăng có độ phức tạp không đổi.

Các kết quả

Kết quả bây giờ rất khác nhau:

Flags (--std=c++0x)       ++i   i++
-DPACKET_SIZE=50 -O1      0.05   0.74
-DPACKET_SIZE=50 -O3      0.08   0.97
-DPACKET_SIZE=500 -O1     0.05   2.79
-DPACKET_SIZE=500 -O3     0.08   2.18
-DPACKET_SIZE=5000 -O3    0.07  21.90

Phần kết luận

Hiệu suât khôn ngoan

Nếu bạn không cần giá trị trước đó, hãy tạo thói quen sử dụng gia tăng trước. Hãy nhất quán ngay cả với các loại dựng sẵn, bạn sẽ quen với nó và không có nguy cơ bị mất hiệu suất không cần thiết nếu bạn thay thế một loại dựng sẵn bằng một loại tùy chỉnh.

Ngữ nghĩa

  • i++nói increment i, I am interested in the previous value, though.
  • ++inói increment i, I am interested in the current valuehay increment i, no interest in the previous value. Một lần nữa, bạn sẽ quen với nó, ngay cả khi bạn không ngay bây giờ.

Knuth.

Tối ưu hóa sớm là gốc rễ của mọi tội lỗi. Như là bi quan sớm.


1
Thử nghiệm thú vị. Bây giờ, gần hai năm rưỡi sau, gcc 4.9 và Clang 3.4 cho thấy một xu hướng tương tự. Clang nhanh hơn một chút với cả hai, nhưng sự chênh lệch giữa tiền tố và hậu tố tồi tệ hơn gcc.
nhai vớ

Những gì tôi thực sự muốn thấy là một ví dụ thực tế trong đó ++ i / i ++ tạo ra sự khác biệt. Chẳng hạn, nó có tạo ra sự khác biệt trên bất kỳ trình lặp std nào không?
Jakob Schou Jensen

@JakobSchouJensen: Đây là những ví dụ điển hình trong thế giới thực. Hãy xem xét một ứng dụng lớn, với các cấu trúc cây phức tạp (ví dụ: cây kd, cây tứ giác) hoặc các thùng chứa lớn được sử dụng trong các mẫu biểu thức (để tối đa hóa thông lượng dữ liệu trên phần cứng SIMD). Nếu nó làm cho một sự khác biệt ở đó, tôi không thực sự chắc chắn lý do tại sao một người sẽ dự phòng tăng sau cho các trường hợp cụ thể nếu điều đó không cần thiết về ngữ nghĩa.
Sebastian Mach

@phresnel: Tôi không nghĩ toán tử ++ có trong mẫu biểu thức hàng ngày của bạn - bạn có ví dụ thực tế về điều này không? Việc sử dụng điển hình của toán tử ++ là trên các số nguyên và các vòng lặp. Đó là điều tôi nghĩ sẽ rất thú vị nếu biết có sự khác biệt nào không (dĩ nhiên không có sự khác biệt nào về số nguyên - nhưng lặp lại).
Jakob Schou Jensen

@JakobSchouJensen: Không có ví dụ kinh doanh thực tế, nhưng một số ứng dụng khủng hoảng số mà bạn đếm công cụ. Wrt iterators, hãy xem xét một bộ theo dõi tia được viết theo kiểu C ++ thành ngữ và bạn có một trình lặp để truyền tải chiều sâu đầu tiên, như vậy for (it=nearest(ray.origin); it!=end(); ++it) { if (auto i = intersect(ray, *it)) return i; }, đừng bận tâm đến cấu trúc cây thực tế (BSP, kd, Quadtree, Octree Grid, v.v.). Một ví dụ iterator sẽ cần phải duy trì một số quốc gia, ví dụ như parent node, child node, indexvà các công cụ như thế. Nói chung, lập trường của tôi là, ngay cả khi chỉ có một vài ví dụ tồn tại, ...
Sebastian Mach

20

Hoàn toàn không đúng khi nói rằng trình biên dịch không thể tối ưu hóa bản sao biến tạm thời trong trường hợp hậu tố. Một thử nghiệm nhanh với VC cho thấy, ít nhất, nó có thể làm điều đó trong một số trường hợp nhất định.

Trong ví dụ sau, mã được tạo giống hệt với tiền tố và hậu tố, ví dụ:

#include <stdio.h>

class Foo
{
public:

    Foo() { myData=0; }
    Foo(const Foo &rhs) { myData=rhs.myData; }

    const Foo& operator++()
    {
        this->myData++;
        return *this;
    }

    const Foo operator++(int)
    {
        Foo tmp(*this);
        this->myData++;
        return tmp;
    }

    int GetData() { return myData; }

private:

    int myData;
};

int main(int argc, char* argv[])
{
    Foo testFoo;

    int count;
    printf("Enter loop count: ");
    scanf("%d", &count);

    for(int i=0; i<count; i++)
    {
        testFoo++;
    }

    printf("Value: %d\n", testFoo.GetData());
}

Cho dù bạn làm ++ testFoo hay testFoo ++, bạn vẫn sẽ nhận được mã kết quả tương tự. Trong thực tế, không cần đọc số đếm từ người dùng, trình tối ưu hóa đã khiến mọi thứ giảm xuống mức không đổi. Vậy đây:

for(int i=0; i<10; i++)
{
    testFoo++;
}

printf("Value: %d\n", testFoo.GetData());

Kết quả như sau:

00401000  push        0Ah  
00401002  push        offset string "Value: %d\n" (402104h) 
00401007  call        dword ptr [__imp__printf (4020A0h)] 

Vì vậy, trong khi đó chắc chắn là phiên bản postfix có thể chậm hơn, nhưng cũng có thể là trình tối ưu hóa sẽ đủ tốt để thoát khỏi bản sao tạm thời nếu bạn không sử dụng nó.


8
Bạn đã quên lưu ý điểm quan trọng là ở đây mọi thứ đều được nội tuyến. Nếu định nghĩa của các toán tử không có sẵn, có thể tránh được bản sao được thực hiện trong mã ngoài dòng; với nội tuyến tối ưu là khá rõ ràng, vì vậy bất kỳ trình biên dịch sẽ làm điều đó.
Blaisorblade

14

Các Google C ++ Style Guide nói:

Preincrement và Preecrement

Sử dụng mẫu tiền tố (++ i) của các toán tử tăng và giảm với các trình vòng lặp và các đối tượng mẫu khác.

Định nghĩa: Khi một biến được tăng (++ i hoặc i ++) hoặc giảm (--i hoặc i--) và giá trị của biểu thức không được sử dụng, người ta phải quyết định xem có phải là prerement (decrement) hay postincrement (decrement) hay không.

Ưu điểm: Khi giá trị trả về bị bỏ qua, biểu mẫu "trước" (++ i) không bao giờ kém hiệu quả hơn biểu mẫu "bài" (i ++) và thường hiệu quả hơn. Điều này là do tăng sau (hoặc giảm) yêu cầu một bản sao của i được thực hiện, đó là giá trị của biểu thức. Nếu tôi là một iterator hoặc loại không vô hướng khác, sao chép tôi có thể tốn kém. Vì hai loại gia tăng hoạt động giống nhau khi giá trị bị bỏ qua, tại sao không luôn luôn tăng trước?

Nhược điểm: Truyền thống được phát triển, trong C, sử dụng tăng sau khi giá trị biểu thức không được sử dụng, đặc biệt là trong các vòng lặp. Một số tìm thấy tăng sau dễ đọc hơn, vì "chủ đề" (i) đi trước "động từ" (++), giống như trong tiếng Anh.

Quyết định: Đối với các giá trị vô hướng đơn giản (không phải đối tượng), không có lý do nào để thích một dạng và chúng tôi cho phép một trong hai. Đối với các trình vòng lặp và các loại mẫu khác, hãy sử dụng mức tăng trước.


1
"Quyết định: Đối với các giá trị vô hướng đơn giản (không phải đối tượng), không có lý do nào để thích một dạng và chúng tôi cho phép. Đối với các trình lặp và các loại mẫu khác, hãy sử dụng gia tăng trước."
Nosredna

2
Ơ, ..., và đó là cái gì?
Sebastian Mach

Liên kết được đề cập trong câu trả lời hiện đang bị hỏng
karol

4

Tôi muốn chỉ ra một bài viết xuất sắc của Andrew Koenig trên Code Talk rất gần đây.

http://dobbscodetalk.com/index.php?option=com_myblog&show=Effic-versus-intent.html&Itemid=29

Tại công ty chúng tôi cũng sử dụng quy ước của ++ iter để có tính nhất quán và hiệu suất khi áp dụng. Nhưng Andrew nêu lên chi tiết quá mức liên quan đến ý định so với hiệu suất. Có những lúc chúng ta muốn sử dụng iter ++ thay vì ++ iter.

Vì vậy, trước tiên hãy quyết định ý định của bạn và nếu trước hoặc sau không quan trọng thì hãy đi trước vì nó sẽ có một số lợi ích về hiệu suất bằng cách tránh tạo thêm đối tượng và ném nó.


Liên kết được cập nhật: drdobbs.com/arch architecture
Ofek Shilon

4

@Ketan

... làm tăng chi tiết quá mức liên quan đến ý định và hiệu suất. Có những lúc chúng ta muốn sử dụng iter ++ thay vì ++ iter.

Rõ ràng bài đăng và tiền gia tăng có ngữ nghĩa khác nhau và tôi chắc chắn rằng mọi người đều đồng ý rằng khi kết quả được sử dụng, bạn nên sử dụng toán tử thích hợp. Tôi nghĩ câu hỏi là người ta nên làm gì khi kết quả bị loại bỏ (như trong forcác vòng lặp). Câu trả lời cho câu hỏi này (IMHO) là, vì các cân nhắc về hiệu suất là không đáng kể, bạn nên làm những gì tự nhiên hơn. Đối với bản thân tôi ++ithì tự nhiên hơn nhưng kinh nghiệm của tôi cho tôi biết rằng tôi thuộc thiểu số và việc sử dụng i++sẽ gây ra ít chi phí kim loại hơn cho hầu hết mọi người đọc mã của bạn.

Sau tất cả, đó là lý do ngôn ngữ không được gọi là " ++C". [*]

[*] Chèn thảo luận bắt buộc về việc ++Ctrở thành một tên hợp lý hơn.


4
@Motti: (đùa) Tên C ++ là hợp lý nếu bạn nhớ lại Bjarne Stroustrup C ++ ban đầu được mã hóa thành một trình biên dịch trước tạo ra chương trình C. Do đó C ++ trả về một giá trị C cũ. Hoặc có thể là để tăng cường rằng C ++ có phần thiếu sót về mặt khái niệm ngay từ đầu.
kriss

4
  1. ++ i - nhanh hơn không sử dụng giá trị trả về
  2. i ++ - nhanh hơn bằng cách sử dụng giá trị trả về

Khi không sử dụng giá trị trả về, trình biên dịch được đảm bảo không sử dụng tạm thời trong trường hợp ++ i . Không được bảo đảm là nhanh hơn, nhưng đảm bảo không bị chậm hơn.

Khi sử dụng giá trị trả về, i ++ cho phép bộ xử lý đẩy cả phần tăng và phần bên trái vào đường ống do chúng không phụ thuộc vào nhau. ++ tôi có thể trì hoãn đường ống vì bộ xử lý không thể khởi động phía bên trái cho đến khi hoạt động tăng trước đã uốn khúc suốt. Một lần nữa, một gian hàng đường ống không được đảm bảo, vì bộ xử lý có thể tìm thấy những thứ hữu ích khác để gắn vào.


3

Mark: Chỉ muốn chỉ ra rằng các toán tử ++ là ứng cử viên tốt để được nội tuyến và nếu trình biên dịch chọn làm như vậy, bản sao dự phòng sẽ bị loại bỏ trong hầu hết các trường hợp. (ví dụ: các loại POD, mà các trình vòng lặp thường là.)

Điều đó nói rằng, vẫn còn phong cách tốt hơn để sử dụng ++ iter trong hầu hết các trường hợp. :-)


3

Sự khác biệt hiệu năng giữa ++ii++sẽ rõ ràng hơn khi bạn nghĩ về các toán tử như các hàm trả về giá trị và cách chúng được thực hiện. Để dễ hiểu hơn những gì đang xảy ra, các ví dụ mã sau sẽ sử dụng intnhư thể nó là một struct.

++ităng biến, sau đó trả về kết quả. Điều này có thể được thực hiện tại chỗ và với thời gian CPU tối thiểu, chỉ cần một dòng mã trong nhiều trường hợp:

int& int::operator++() { 
     return *this += 1;
}

Nhưng điều tương tự không thể được nói về i++.

Tăng sau ,, i++thường được xem là trả về giá trị ban đầu trước khi tăng. Tuy nhiên, một chức năng chỉ có thể trả về một kết quả khi nó được hoàn thành . Kết quả là, cần phải tạo một bản sao của biến chứa giá trị ban đầu, tăng biến, sau đó trả về bản sao giữ giá trị ban đầu:

int int::operator++(int& _Val) {
    int _Original = _Val;
    _Val += 1;
    return _Original;
}

Khi không có sự khác biệt về chức năng giữa tăng trước và tăng sau, trình biên dịch có thể thực hiện tối ưu hóa sao cho không có sự khác biệt về hiệu năng giữa hai. Tuy nhiên, nếu một loại dữ liệu tổng hợp như một structhoặc classcó liên quan, thì hàm tạo sao chép sẽ được gọi sau tăng dần và sẽ không thể thực hiện tối ưu hóa này nếu cần một bản sao sâu. Như vậy, tăng trước thường nhanh hơn và cần ít bộ nhớ hơn tăng sau.


1

@Mark: Tôi đã xóa câu trả lời trước đó của mình vì nó hơi lộn xộn và xứng đáng là một downvote cho điều đó một mình. Tôi thực sự nghĩ rằng đó là một câu hỏi hay theo nghĩa là nó hỏi những gì trong tâm trí của rất nhiều người.

Câu trả lời thông thường là ++ i nhanh hơn i ++, và không còn nghi ngờ gì nữa, nhưng câu hỏi lớn hơn là "khi nào bạn nên quan tâm?"

Nếu tỷ lệ thời gian CPU dành cho việc tăng vòng lặp nhỏ hơn 10%, thì bạn có thể không quan tâm.

Nếu tỷ lệ thời gian CPU dành cho việc tăng các vòng lặp lớn hơn 10%, bạn có thể xem các câu lệnh nào đang thực hiện việc lặp đó. Xem nếu bạn chỉ có thể tăng số nguyên thay vì sử dụng các trình vòng lặp. Rất có thể là bạn có thể, và trong khi nó có thể ở một khía cạnh nào đó ít mong muốn hơn, rất có thể bạn sẽ tiết kiệm được tất cả thời gian dành cho những người lặp đó.

Tôi đã thấy một ví dụ trong đó việc tăng vòng lặp tiêu thụ tốt hơn 90% thời gian. Trong trường hợp đó, việc tăng số nguyên thực hiện giảm thời gian thực hiện bằng cách đó. (tức là tốt hơn tốc độ tăng gấp 10 lần)


1

@wilmustell

Trình biên dịch có thể bỏ qua tạm thời. Nguyên văn từ các chủ đề khác:

Trình biên dịch C ++ được phép loại bỏ tạm thời dựa trên ngăn xếp ngay cả khi làm như vậy thay đổi hành vi chương trình. Liên kết MSDN cho VC 8:

http://msdn.microsoft.com/en-us/l Library / ms364057 (VS.80) .aspx


1
Điều đó không liên quan. NRVO tránh sự cần thiết phải sao chép t trong "CC :: toán tử ++ (int)" trở lại người gọi, nhưng i ++ vẫn sẽ sao chép giá trị cũ trên ngăn xếp của người gọi. Không có NRVO, i ++ tạo 2 bản sao, một bản t và một bản trả lại cho người gọi.
Blaisorblade

0

Một lý do tại sao bạn nên sử dụng ++ i ngay cả trên các loại tích hợp trong đó không có lợi thế về hiệu suất là để tạo thói quen tốt cho chính bạn.


3
Xin lỗi, nhưng điều đó làm phiền tôi. Ai nói đó là "thói quen tốt", khi nó gần như không bao giờ quan trọng? Nếu mọi người muốn biến nó thành một phần của kỷ luật của họ, điều đó tốt, nhưng hãy phân biệt các lý do quan trọng với các vấn đề sở thích cá nhân.
Mike Dunlavey

@MikeDunlavey ok, vậy bạn thường sử dụng bên nào khi không quan trọng? xD không phải cái này hay cái kia phải không! bài viết ++ (nếu bạn đang sử dụng nó với ý nghĩa chung. cập nhật nó, trả lại cái cũ) hoàn toàn kém hơn so với ++ trước (cập nhật nó, trả lại) không bao giờ có bất kỳ lý do nào bạn muốn có hiệu suất thấp hơn. trong trường hợp bạn muốn cập nhật nó sau đó, lập trình viên thậm chí sẽ không làm bài ++. không lãng phí thời gian sao chép khi chúng ta đã có nó. cập nhật nó sau khi chúng tôi sử dụng nó. sau đó các trình biên dịch có ý nghĩa chung mà bạn muốn nó có.
Puddle

@Puddle: Khi tôi nghe điều này: "không bao giờ có bất kỳ lý do nào bạn muốn có hiệu suất thấp hơn" Tôi biết tôi đang nghe "penny khôn ngoan - pound ngu ngốc". Bạn cần có một sự đánh giá cao về độ lớn liên quan. Chỉ khi điều này chiếm hơn 1% thời gian liên quan thì bạn mới nên suy nghĩ kỹ. Thông thường, nếu bạn đang nghĩ về điều này, có những vấn đề lớn hơn gấp triệu lần mà bạn không xem xét và đây là điều khiến phần mềm chậm hơn nhiều so với khả năng của nó.
Mike Dunlavey

@MikeDunlavey hồi tưởng vô nghĩa để thỏa mãn cái tôi của bạn. bạn đang cố tỏ ra giống một nhà sư thông thái, nhưng bạn không nói gì. cường độ liên quan ... nếu chỉ hơn 1% thời gian bạn nên quan tâm ... xD rê bóng tuyệt đối. nếu nó không hiệu quả, nó đáng để biết và sửa chữa. chúng tôi ở đây đang cân nhắc điều này vì lý do chính xác đó! chúng tôi không quan tâm đến việc chúng tôi có thể kiếm được bao nhiêu từ kiến ​​thức này. và khi tôi nói rằng bạn không muốn hiệu suất thấp hơn, hãy tiếp tục, giải thích một kịch bản chết tiệt sau đó. MR RỒI!
Puddle

0

Cả hai đều nhanh như vậy;) Nếu bạn muốn nó là phép tính giống nhau cho bộ xử lý, thì đó chỉ là thứ tự thực hiện khác nhau.

Ví dụ: đoạn mã sau:

#include <stdio.h>

int main()
{
    int a = 0;
    a++;
    int b = 0;
    ++b;
    return 0;
}

Sản xuất lắp ráp sau:

 0x0000000100000f24 <main+0>: push   %rbp
 0x0000000100000f25 <main+1>: mov    %rsp,%rbp
 0x0000000100000f28 <main+4>: movl   $0x0,-0x4(%rbp)
 0x0000000100000f2f <main+11>:    incl   -0x4(%rbp)
 0x0000000100000f32 <main+14>:    movl   $0x0,-0x8(%rbp)
 0x0000000100000f39 <main+21>:    incl   -0x8(%rbp)
 0x0000000100000f3c <main+24>:    mov    $0x0,%eax
 0x0000000100000f41 <main+29>:    leaveq 
 0x0000000100000f42 <main+30>:    retq

Bạn thấy rằng đối với một ++ và b ++, đó là một bản ghi nhớ bao gồm, vì vậy nó hoạt động tương tự;)


Đó là C, trong khi OP hỏi C ++. Trong C cũng vậy. Trong C ++ nhanh hơn là ++ i; do đối tượng của nó. Tuy nhiên, một số trình biên dịch có thể tối ưu hóa toán tử tăng sau.
Wiggler Jtag

0

Câu hỏi dự định là về khi kết quả không được sử dụng (điều đó rõ ràng từ câu hỏi cho C). Ai đó có thể khắc phục điều này vì câu hỏi là "wiki cộng đồng" không?

Về tối ưu hóa sớm, Knuth thường được trích dẫn. Đúng rồi. nhưng Donald Knuth sẽ không bao giờ bảo vệ với mật mã khủng khiếp mà bạn có thể thấy trong những ngày này. Bạn đã bao giờ thấy a = b + c trong số các số nguyên Java (không phải int) chưa? Đó là số tiền cho 3 chuyển đổi đấm bốc / unboxing. Tránh những thứ như thế là quan trọng. Và vô dụng viết i ++ thay vì ++ tôi cũng mắc lỗi tương tự. EDIT: Khi phresnel đưa nó vào một bình luận, điều này có thể được tóm tắt là "tối ưu hóa sớm là xấu, cũng như bi quan sớm".

Ngay cả việc mọi người quen với i ++ hơn là một di sản C đáng tiếc, gây ra bởi một sai lầm về khái niệm của K & R (nếu bạn tuân theo lập luận về ý định, đó là một kết luận hợp lý và bảo vệ K & R vì họ là K & R là vô nghĩa, họ là tuyệt vời, nhưng chúng không tuyệt vời như các nhà thiết kế ngôn ngữ, vô số lỗi trong thiết kế C tồn tại, từ get () đến strcpy (), đến API strncpy () (cần có API strlcpy () kể từ ngày 1) ).

Btw, tôi là một trong những người không sử dụng đủ C ++ để tìm ++ tôi khó chịu khi đọc. Tuy nhiên, tôi sử dụng điều đó vì tôi thừa nhận rằng nó đúng.


Tôi thấy bạn đang làm việc trên một bằng tiến sĩ. với sự quan tâm đến tối ưu hóa trình biên dịch và những thứ đó. Điều đó thật tuyệt, nhưng đừng quên hàn lâm là một buồng vang vọng và lẽ thường thường bị bỏ lại ngoài cửa, ít nhất là trong CS Bạn có thể quan tâm đến điều này: stackoverflow.com/questions/1303899/ trộm
Mike Dunlavey

Tôi chưa bao giờ thấy ++ikhó chịu hơn i++(thực tế, tôi thấy nó mát hơn), nhưng phần còn lại của bài viết của bạn nhận được sự thừa nhận đầy đủ của tôi. Có thể thêm một điểm "tối ưu hóa sớm là xấu xa, cũng như bi quan sớm"
Sebastian Mach

strncpyphục vụ một mục đích trong các hệ thống tập tin mà họ đang sử dụng tại thời điểm đó; tên tệp là bộ đệm 8 ký tự và nó không phải kết thúc bằng null. Bạn không thể đổ lỗi cho họ vì đã không nhìn thấy 40 năm trong tương lai của sự tiến hóa ngôn ngữ.
MM

@MattMcNabb: không có 8 ký tự tên tệp MS-DOS? C được phát minh với Unix. Dù sao, ngay cả khi strncpy có một điểm, việc thiếu strlcpy vẫn chưa được chứng minh đầy đủ: ngay cả C gốc có các mảng mà bạn không nên tràn, cần strlcpy; nhiều nhất, họ chỉ thiếu những kẻ tấn công có ý định khai thác lỗi. Nhưng người ta không thể nói rằng dự báo vấn đề này là tầm thường, vì vậy nếu tôi viết lại bài đăng của mình, tôi sẽ không sử dụng cùng một giai điệu.
Blaisorblade

@Blaisorblade: Như tôi nhớ, tên tệp UNIX ban đầu được giới hạn ở 14 ký tự. Việc thiếu strlcpy()được chứng minh bằng thực tế là nó chưa được phát minh.
Keith Thompson

-1

Đã đến lúc cung cấp cho mọi người những viên ngọc khôn ngoan;) - có một mẹo đơn giản để làm cho việc tăng tiền tố C ++ hoạt động khá giống với việc tăng tiền tố (Bản thân tôi đã phát hiện ra nó, nhưng tôi cũng thấy nó trong mã người khác, vì vậy tôi không một mình).

Về cơ bản, mẹo là sử dụng lớp người trợ giúp để hoãn tăng dần sau khi trở về và RAII đến để giải cứu

#include <iostream>

class Data {
    private: class DataIncrementer {
        private: Data& _dref;

        public: DataIncrementer(Data& d) : _dref(d) {}

        public: ~DataIncrementer() {
            ++_dref;
        }
    };

    private: int _data;

    public: Data() : _data{0} {}

    public: Data(int d) : _data{d} {}

    public: Data(const Data& d) : _data{ d._data } {}

    public: Data& operator=(const Data& d) {
        _data = d._data;
        return *this;
    }

    public: ~Data() {}

    public: Data& operator++() { // prefix
        ++_data;
        return *this;
    }

    public: Data operator++(int) { // postfix
        DataIncrementer t(*this);
        return *this;
    }

    public: operator int() {
        return _data;
    }
};

int
main() {
    Data d(1);

    std::cout <<   d << '\n';
    std::cout << ++d << '\n';
    std::cout <<   d++ << '\n';
    std::cout << d << '\n';

    return 0;
}

Phát minh là cho một số mã lặp tùy chỉnh nặng, và nó cắt giảm thời gian chạy. Chi phí tiền tố so với tiền tố là một tham chiếu bây giờ và nếu đây là toán tử tùy chỉnh di chuyển nặng, tiền tố và hậu tố mang lại cùng thời gian chạy cho tôi.


-5

++inhanh hơn i++bởi vì nó không trả về một bản sao cũ của giá trị.

Nó cũng trực quan hơn:

x = i++;  // x contains the old value of i
y = ++i;  // y contains the new value of i 

Ví dụ C này in "02" thay vì "12" mà bạn có thể mong đợi:

#include <stdio.h>

int main(){
    int a = 0;
    printf("%d", a++);
    printf("%d", ++a);
    return 0;
}

Tương tự cho C ++ :

#include <iostream>
using namespace std;

int main(){
    int a = 0;
    cout << a++;
    cout << ++a;
    return 0;
}
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.