2014-10-15 32 views
31

Giả sử tôi có một hàm lấy đối số thuộc loại T. Nó không đột biến nó, vì vậy tôi có thể lựa chọn đi qua nó bằng cách tham chiếu const const T& hoặc bằng giá trị T:Quy tắc ngón tay cái cho khi truyền theo giá trị nhanh hơn đi qua tham chiếu const?

void foo(T t){ ... } 
void foo(const T& t){ ... } 

Có một quy tắc của ngón tay cái của lớn T thế nào nên trở thành trước khi đi ngang qua tham chiếu const trở nên rẻ hơn hơn là đi qua giá trị? Ví dụ: giả sử tôi biết rằng sizeof(T) == 24. Tôi có nên sử dụng tham chiếu const hoặc giá trị không?

tôi cho rằng các nhà xây dựng bản sao của T là tầm thường. Nếu không, câu trả lời cho câu hỏi phụ thuộc vào sự phức tạp của hàm tạo bản sao, tất nhiên.

Tôi đã tìm kiếm những câu hỏi tương tự và stumbled khi một này:

template pass by value or const reference or...?

Tuy nhiên, câu trả lời được chấp nhận ( https://stackoverflow.com/a/4876937/1408611) không nêu bất kỳ chi tiết, nó chỉ nói:

Nếu bạn mong đợi T luôn là một loại số hoặc một loại rất là giá rẻ để sao chép, thì bạn có thể lấy đối số theo giá trị.

Vì vậy, nó không giải quyết được câu hỏi của tôi mà thay vào đó hãy đặt lại câu hỏi: Loại nhỏ phải là "rất rẻ để sao chép"?

+0

về mặt kỹ thuật, nếu nó lớn hơn 'sizeof (T *)' thì không hiệu quả để vượt qua giá trị (không bao gồm những thứ như tạo bản sao bên trong hàm được gọi) – Creris

+7

@TheOne Không hề. Bạn phải xem xét chi phí tải các phần của đối số mà bạn sử dụng thông qua con trỏ bạn nhận được. Điều này rất lộn xộn rất nhanh, vì vậy cách tốt nhất là dùng thử. Nhưng ví dụ, nếu đối số duy nhất của bạn là một cặp POD gồm hai số nguyên, đi qua giá trị có thể (tùy thuộc vào ABI) chỉ sử dụng hai thanh ghi, trong khi đi qua con trỏ sẽ lãng phí một thanh ghi có sẵn và yêu cầu thêm hướng dẫn bên trong hàm. – delnan

+0

Đồng thời xem xét việc chuyển một thành viên của một cấu trúc/lớp khi chỉ cần một thành viên. –

Trả lời

27

Nếu bạn có lý do để nghi ngờ có một hiệu suất đáng kể đạt được, cắt nó ra với các quy tắc của ngón cái và đo. Mục đích của lời khuyên bạn trích dẫn là bạn không sao chép một lượng lớn dữ liệu mà không có lý do gì, nhưng đừng gây nguy hiểm cho việc tối ưu hóa bằng cách làm cho mọi thứ trở thành một tham chiếu. Nếu một cái gì đó là trên các cạnh giữa "rõ ràng giá rẻ để sao chép" và "rõ ràng đắt tiền để sao chép", sau đó bạn có thể đủ khả năng hoặc là tùy chọn. Nếu bạn phải có quyết định lấy đi từ bạn, lật một đồng xu.

Một loại là giá rẻ để sao chép nếu nó không có constructor sao chép funky và sizeof của nó là nhỏ. Không có số khó cho "nhỏ" đó là tối ưu, thậm chí trên cơ sở mỗi nền tảng vì nó phụ thuộc rất nhiều vào mã gọi và chính hàm đó. Chỉ cần đi theo cảm giác ruột của bạn. Một, hai, ba từ nhỏ. Mười, ai biết. Ma trận 4x4 không nhỏ.

+0

Cũng phụ thuộc vào tần suất mà chức năng này được gọi. Một lý do khác để đo lường. –

+3

* Một loại có giá rẻ để sao chép nếu nó không có hàm tạo bản sao sôi nổi và kích thước của nó nhỏ. * - điều này có thể gây hiểu nhầm. Thông báo trước ở đây là một lớp có thể có một "hàm tạo bản sao funky" mà không có lập trình viên biết về nó, ví dụ: 'struct A {std :: list x; }; '. Một lớp như vậy có thể có một 'sizeof' nhỏ và không có một hàm tạo bản sao rõ ràng, nhưng nó vẫn có thể rất tốn kém để sao chép. – arman

12

Nguyên tắc thích hợp nhất của ngón tay cái theo ý kiến ​​của tôi là vượt qua bằng cách tham khảo khi:

sizeof(T) >= sizeof(T*) 

Ý tưởng đằng sau này là khi bạn đi bằng cách tham khảo, lúc tồi tệ nhất trình biên dịch của bạn có thể thực hiện điều này bằng cách sử dụng con trỏ .

Điều này tất nhiên không đưa vào tài khoản sự phức tạp của nhà xây dựng bản sao của bạn và di chuyển ngữ nghĩa và tất cả các địa ngục có thể được tạo ra xung quanh vòng đời đối tượng của bạn. Ngoài ra nếu bạn không quan tâm đến việc tối ưu hóa vi mô, bạn có thể vượt qua mọi thứ bằng tham chiếu const, trên hầu hết các máy con trỏ là 4 hoặc 8 byte, rất ít loại nhỏ hơn và thậm chí trong trường hợp đó bạn sẽ mất một vài (ít hơn 8) byte sao chép hoạt động và một số indirections mà trong thế giới hiện đại rất có thể sẽ không được nút cổ chai của bạn :)

+0

Tôi tự hỏi liệu vùng đệm có thể ảnh hưởng đến hiệu suất ở đây không? – Basilevs

+13

Tôi không nghĩ rằng quy tắc này có bất kỳ cơ sở nào trong thực tế. Bạn phải xem xét chi phí tải các phần của đối số mà bạn sử dụng thông qua con trỏ bạn nhận được. Lý do về điều này bị lộn xộn rất nhanh chóng, vì vậy cách tốt nhất là thử nó. Nếu, ví dụ, tham số duy nhất là một cặp hai số nguyên, đi qua giá trị có thể (tùy thuộc vào ABI) chỉ sử dụng hai thanh ghi, trong khi đi qua con trỏ lãng phí một thanh ghi có sẵn và yêu cầu hướng dẫn bổ sung bên trong hàm. Tôi sẽ sai về mặt thận trọng, tức là sử dụng một bội số nhỏ của kích thước con trỏ. – delnan

+0

@Basilevs có lẽ nhưng không có bất kỳ biện pháp chúng tôi không thể thực sự thảo luận rằng, từ bản năng (mà là officaly một cách xấu của lý luận liên quan đến hiệu suất :)) tôi muốn nói nó không phải là "đó" lớn, nhưng một lần nữa nó cần được kiểm tra – Drax

4

Tôi tin rằng tôi sẽ chọn chuyển giá trị bất cứ khi nào có thể (nghĩa là: khi ngữ nghĩa yêu cầu tôi không cần đối tượng thực tế để hoạt động). Tôi sẽ tin tưởng trình biên dịch để thực hiện các bước di chuyển thích hợp và copy-elision.

Sau khi mã của tôi có ngữ nghĩa chính xác, tôi sẽ cấu hình nó để xem tôi có đang tạo bản sao không cần thiết hay không; Tôi sẽ sửa đổi những điều đó cho phù hợp.

Tôi tin rằng cách tiếp cận này sẽ giúp tôi tập trung vào phần quan trọng nhất của phần mềm: chính xác. Và tôi sẽ không nhận được trên đường của trình biên dịch --- can thiệp; ức chế --- để thực hiện tối ưu hóa (tôi biết tôi không thể đánh bại nó).

Có nói rằng, các tham chiếu danh nghĩa được thực hiện dưới dạng con trỏ. Vì vậy, trong một vaccum, mà không xem xét ngữ nghĩa, sao chép, di chuyển ngữ nghĩa, và những thứ như thế, nó sẽ hiệu quả hơn khi truyền con trỏ/tham chiếu bất cứ thứ gì có kích thước lớn hơn con trỏ.

+0

Như tôi đã giải thích trong nhiều bình luận trước đây, không, lớn hơn một con trỏ không ngụ ý truyền bằng tham chiếu hiệu quả hơn, ngay cả trong chân không của bạn. – delnan

13

Truyền giá trị thay vì tham chiếu const có lợi thế là trình biên dịch biết giá trị sẽ không thay đổi. "const int & x" không có nghĩa là giá trị không thể thay đổi; nó chỉ có nghĩa là mã của bạn không được phép thay đổi nó bằng cách sử dụng mã định danh x (không có một số phép thuật mà trình biên dịch sẽ nhận thấy). Hãy lấy ví dụ khủng khiếp nhưng hoàn toàn hợp pháp này:

static int someValue; 

void g (int i) 
{ 
    --someValue; 
} 

void f (const int& x) 
{ 
    for (int i = 0; i < x; ++i) 
     g (i); 
} 

int main (void) 
{ 
    someValue = 100; 
    f (someValue); 
    return 0; 
} 

Hàm bên trong f, x không thực sự là hằng số! Nó thay đổi mỗi khi g (i) được gọi, vì vậy vòng lặp chỉ chạy từ 0 đến 49! Và vì trình biên dịch thường không biết cho dù bạn đã viết mã khủng khiếp như thế này, nó phải giả định rằng x có thể thay đổi khi g được gọi. Kết quả là, bạn có thể mong đợi mã sẽ chậm hơn nếu bạn đã sử dụng "int x".

Điều tương tự cũng đúng đối với nhiều đối tượng cũng có thể được chuyển qua tham chiếu. Ví dụ, nếu bạn vượt qua một đối tượng bằng const & và đối tượng có thành viên int hoặc unsigned int, thì bất kỳ phép gán nào sử dụng char *, int * hoặc unsigned int * có thể thay đổi thành viên đó, trừ khi trình biên dịch có thể chứng minh bằng cách khác. Thông qua giá trị, bằng chứng là dễ dàng hơn nhiều cho trình biên dịch.

+0

Nếu tôi đang thiết kế một ngôn ngữ/khung công tác, tôi sẽ định nghĩa một tham số/tham số ngữ nghĩa cho các trường hợp mà cả người gọi lẫn phương thức được gọi đều không được phép thay đổi mục đã chuyển; pass-by-value và pass-by-reference sẽ tương đương trong trường hợp đó, vì vậy một trình biên dịch có thể tự động chọn cái nào hiệu quả hơn. Ngoài ra, một hàm có thể xác định nhiều điểm vào dựa trên việc đối tượng được truyền vào có thể được sửa đổi ở phía cuộc gọi hay không hoặc người gọi có cần đối tượng sau cuộc gọi hay không và có phương thức được gọi là thực hiện một bản sao nếu cần. – supercat

+0

D chỉ có (dữ liệu không thay đổi). Vì vậy, làm nhiều ngôn ngữ khác. – Demi

+0

Chỉ xảy ra với "mã khủng khiếp" như thế này, (ví dụ: sử dụng các biến toàn cục), hoặc trình biên dịch có hiểu các phạm vi biến và làm cho giáo dục đoán về 'const'ness trong các trường hợp bình thường không? –

3

Để tóm tắt "T" trong quy tắc "C++" trừu tượng, hãy sử dụng cách phản ánh tốt hơn ý định, đối với đối số không được sửa đổi hầu như luôn "chuyển giá trị". Bên cạnh đó, các trình biên dịch thực tế cụ thể mong đợi một mô tả trừu tượng như vậy và sẽ truyền T của bạn theo cách hiệu quả nhất, bất kể bạn làm như thế nào trong nguồn.

Hoặc, để nói về biên soạn naivie và bố cục, "rất rẻ để sao chép" là "bất kỳ thứ gì bạn có thể tải trong một thanh ghi đơn". Không nhận được bất kỳ rẻ hơn thực sự.

3

Nếu bạn định sử dụng "quy tắc ngón tay cái" cho giá trị so vớiby-const tham chiếu, sau đó làm điều này:

  • chọn ONE cách tiếp cận và sử dụng nó ở khắp mọi nơi
  • thoả thuận cái nào giữa tất cả các đồng nghiệp của bạn
  • chỉ sau đó, trong "thực hiện bằng tay điều chỉnh" giai đoạn, bắt đầu thay đổi mọi thứ
  • và sau đó, chỉ thay đổi chúng nếu bạn nhìn thấy một sự cải thiện đo lường
+4

Quy tắc của ngón tay cái không thay thế cảm giác thông thường. Nếu đó là BLATANTLY OBVIOUS rằng một trong những cách tiếp cận là tốt hơn trong một số tình huống, không cần phải trì hoãn việc ra quyết định cho đến sau một thử nghiệm; và không cần phải kiểm tra. Sử dụng quy tắc chung cho các quyết định nhỏ mà yếu tố "lãng phí thời gian" được đo trong thời gian phát triển chứ không phải là thời gian của CPU. – hoosierEE

1

Các chàng trai ở đây là chính xác rằng hầu hết thời gian nó không quan trọng khi sizeof struct/class là nhỏ và không có constructor sao chép ưa thích. Tuy nhiên đó không phải là lý do cho việc không biết gì. Dưới đây là what happens trong một số ABI hiện đại như x64. Bạn có thể thấy rằng trên nền tảng đó, ngưỡng tốt cho quy tắc ngón tay cái của bạn là vượt qua giá trị trong đó loại là loại POD và sizeof() < = 16, vì nó sẽ được chuyển qua hai thanh ghi. Quy tắc của ngón tay cái là tốt, họ giữ cho bạn khỏi lãng phí thời gian và giới hạn trí tuệ vào các quyết định nhỏ như thế này mà không di chuyển kim.

Tuy nhiên, đôi khi nó sẽ quan trọng. Khi bạn đã xác định với một profiler rằng bạn có một trong những trường hợp (và NOT trước khi trừ khi nó là siêu rõ ràng!), Sau đó bạn cần phải hiểu các chi tiết cấp thấp - không nghe platitudes an ủi về cách nó không quan trọng, mà SO đầy. Một số điều cần lưu ý:

  • Đi qua giá trị cho trình biên dịch biết đối tượng không thay đổi. Có những loại mã độc, liên quan đến chủ đề hoặc hình cầu, nơi điều được chỉ ra bởi tham chiếu const đang được thay đổi ở một nơi khác. Mặc dù chắc chắn bạn không viết mã độc, trình biên dịch có thể có một thời gian khó khăn chứng minh rằng, và phải tạo ra mã rất bảo thủ như là một kết quả.
  • Nếu có quá nhiều đối số để vượt qua bằng cách đăng ký, thì không quan trọng sizeof là gì, đối tượng sẽ được chuyển vào ngăn xếp.
  • Một đối tượng nhỏ và POD ngày nay có thể phát triển và đạt được một hàm tạo bản sao đắt tiền vào ngày mai. Người thêm những thứ đó có lẽ đã không kiểm tra xem nó có được chuyển qua tham chiếu hay theo giá trị không, vì thế mã được sử dụng để thực hiện lớn có thể đột nhiên chug. Việc chuyển qua tham chiếu const là một tùy chọn an toàn hơn nếu bạn làm việc trên một nhóm không có kiểm tra hồi quy hiệu năng toàn diện. Bạn sẽ bị cắn bởi điều này sớm hay muộn.
Các vấn đề liên quan