2008-10-26 38 views
26

Có hiệu quả hơn cho một lớp để truy cập vào biến thành viên hoặc biến cục bộ không? Ví dụ: giả sử bạn có phương thức (gọi lại) có trách nhiệm duy nhất là nhận dữ liệu, thực hiện các phép tính trên đó, sau đó chuyển nó sang các lớp khác. Hiệu năng-khôn ngoan, nó sẽ làm cho tinh thần hơn để có một danh sách các biến thành viên mà phương pháp populates khi nó nhận được dữ liệu? Hoặc chỉ cần khai báo các biến cục bộ mỗi khi gọi phương thức gọi lại?Hiệu suất C++ truy cập các biến thành viên so với biến cục bộ

Giả sử phương pháp này sẽ được gọi là hàng trăm lần trong một giây ...

Trong trường hợp tôi không được rõ ràng, đây là một số ví dụ nhanh:

// use local variables 
class thisClass { 
    public: 
     void callback(msg& msg) 
     { 
      int varA; 
      double varB; 
      std::string varC; 
      varA = msg.getInt(); 
      varB = msg.getDouble(); 
      varC = msg.getString(); 

      // do a bunch of calculations 
     } 

}; 

// use member variables 
class thisClass { 
    public: 
     void callback(msg& msg) 
     { 
      m_varA = msg.getInt(); 
      m_varB = msg.getDouble(); 
      m_varC = msg.getString(); 

      // do a bunch of calculations 
     } 

    private: 
     int m_varA; 
     double m_varB; 
     std::string m_varC; 

}; 
+0

Bất kỳ sự khác biệt nào gần như chắc chắn sẽ bị che khuất bởi chi phí truy xuất các giá trị ngay từ đầu. Và vài trăm lần một giây là không có gì. Chi phí của khung stack để * gọi * chức năng của bạn sẽ có khả năng lùn bất cứ điều gì bạn đã hiển thị. Có mùi như tối ưu hóa sớm. – Shog9

+0

Downvoted đã - tối nay đám đông khó khăn – fizzer

Trả lời

38

Tóm tắt điều hành: Trong hầu như tất cả các trường hợp, không quan trọng, nhưng có một lợi thế nhỏ cho các biến cục bộ.

Cảnh báo: Bạn đang tối ưu hóa vi mô. Bạn sẽ phải bỏ ra hàng giờ để tìm hiểu mã được cho là sẽ giành được một nano giây.

Cảnh báo: Trong trường hợp của bạn, hiệu suất không phải là câu hỏi, nhưng vai trò của các biến - chúng là tạm thời hay trạng thái của thisClass?

Cảnh báo: Quy tắc tối ưu hóa thứ nhất, thứ hai và cuối cùng: đo lường!


Trước hết, hãy nhìn vào lắp ráp điển hình được tạo ra cho x86 (nền tảng của bạn có thể thay đổi):

// stack variable: load into eax 
mov eax, [esp+10] 

// member variable: load into eax 
mov ecx, [adress of object] 
mov eax, [ecx+4] 

Khi địa chỉ của đối tượng được nạp, int một thanh ghi, các hướng dẫn giống hệt nhau . Tải địa chỉ đối tượng thường có thể được ghép nối với một lệnh trước đó và không đạt thời gian thực hiện.

Nhưng điều này có nghĩa là thanh ghi ecx không khả dụng cho các tối ưu hóa khác. Tuy nhiên, CPU hiện đại thực hiện một số thủ đoạn mãnh liệt để làm cho ít vấn đề hơn.

Ngoài ra, khi truy cập nhiều đối tượng, điều này có thể làm bạn tốn thêm tiền. Tuy nhiên, đây là ít hơn một chu kỳ trung bình, và thường có nhiều opprtunities để ghép nối hướng dẫn.

Địa phương bộ nhớ: đây là cơ hội để ngăn xếp giành được thời gian lớn. Đầu stack là hầu như luôn luôn trong bộ nhớ cache L1, do đó, tải có một chu kỳ. Đối tượng có nhiều khả năng bị đẩy trở lại bộ đệm L2 (quy tắc ngón tay cái, 10 chu kỳ) hoặc bộ nhớ chính (100 chu kỳ).

Tuy nhiên, bạn chỉ trả tiền này cho lần truy cập đầu tiên. nếu tất cả những gì bạn có là một lần truy cập, 10 hoặc 100 chu kỳ là không đáng kể. nếu bạn có hàng nghìn lượt truy cập, dữ liệu đối tượng cũng sẽ nằm trong bộ đệm L1.

Tóm lại, mức tăng nhỏ đến mức hầu như không bao giờ có ý nghĩa sao chép các biến thành viên vào người dân địa phương để đạt được hiệu suất tốt hơn.

+1

C++ không có ABI cụ thể. Nhưng những cái tôi đã đọc luôn dành một cho con trỏ này. Vì vậy, không đạt được. Ngoài ra, bạn quên sao chép chi phí vào/ra của người dân địa phương. –

+0

bạn nói đúng, những điều này cũng nên được đề cập. – peterchen

+0

Vì dữ liệu lớp được đặt liên tiếp vào bộ nhớ, lần đầu tiên bạn truy cập bất kỳ dữ liệu thành viên nào, hầu như tất cả trạng thái của lớp sẽ được tải dưới dạng dòng bộ nhớ cache vào L1. Sau đó, các truy cập phải giống nhau. – AndreasT

3

này nên được vấn đề biên dịch của bạn. Thay vào đó, hãy tối ưu hóa để bảo trì: Nếu thông tin chỉ được sử dụng cục bộ, hãy lưu trữ nó trong các biến cục bộ (tự động). Tôi ghét đọc các lớp học rải rác với các biến thành viên mà không thực sự cho tôi biết bất cứ điều gì về lớp, nhưng chỉ một số chi tiết về cách một loạt các phương pháp làm việc cùng nhau: (

Thực tế, tôi sẽ ngạc nhiên nếu biến cục bộ Tuy nhiên, chúng nhanh chóng được lưu trữ trong bộ đệm, vì chúng gần với phần còn lại của dữ liệu chức năng (khung gọi) và một con trỏ đối tượng có thể ở đâu đó hoàn toàn khác - nhưng tôi chỉ đoán ở đây:

0

và chắc chắn rằng nó tạo ra sự khác biệt đầu tiên - hàng trăm lần một giây không phải là một gánh nặng lớn đối với một bộ vi xử lý hiện đại. bất kỳ sự khác biệt. Cả hai sẽ được bù đắp liên tục từ một con trỏ, người dân địa phương sẽ được từ con trỏ ngăn xếp và các thành viên sẽ được từ con trỏ "này".

0

Sử dụng các biến thành viên sẽ nhanh hơn một chút vì chúng chỉ phải được cấp phát một lần (khi đối tượng được tạo) thay vì mỗi lần gọi lại được gọi. Nhưng so với phần còn lại của công việc bạn có thể làm tôi mong đợi đây sẽ là một tỷ lệ rất nhỏ. Benckmark cả hai và xem đó là nhanh hơn.

5

Tôi muốn các biến cục bộ trên nguyên tắc chung hơn, vì chúng giảm thiểu trạng thái có thể thay đổi ác trong chương trình của bạn. Đối với hiệu suất, hồ sơ của bạn sẽ cho bạn biết tất cả những gì bạn cần biết. Người dân địa phương nên nhanh hơn cho ints và có lẽ các nội trang khác, vì chúng có thể được đặt trong sổ đăng ký.

1

Trong ý kiến ​​của tôi, nó không nên ảnh hưởng đến hiệu suất, bởi vì:

  • Trong ví dụ đầu tiên của bạn, các biến được truy cập thông qua một tra cứu trên stack, ví dụ [ESP] +4 có nghĩa là kết thúc ngăn xếp hiện tại cộng với bốn byte.
  • Trong ví dụ thứ hai, các biến được truy cập thông qua một tra cứu liên quan đến điều này (nhớ, varB bằng this-> varB). Đây là một hướng dẫn máy tương tự.

Do đó, không có nhiều khác biệt.

Tuy nhiên, bạn nên tránh sao chép chuỗi;)

4

Câu hỏi ngớ ngẩn.
Tất cả phụ thuộc vào trình biên dịch và những gì nó làm để tối ưu hóa.

Thậm chí nếu nó đã hoạt động bạn đã đạt được những gì? Cách để làm xáo trộn mã của bạn?

Truy cập biến thường được thực hiện thông qua con trỏ và và bù đắp.

  • Pointer đến Object + bù đắp
  • Pointer để Stack Khung + bù đắp

Cũng đừng quên để thêm vào chi phí của việc di chuyển các biến để lưu trữ địa phương và sau đó sao chép các kết quả trở lại . Tất cả điều đó có thể có nghĩa là ít hơn vì trình biên dịch có thể đủ thông minh để tối ưu hóa hầu hết mọi thứ.

+0

Bạn có ý nghĩa gì về chi phí của các biến chuyển sang lưu trữ cục bộ sau đó sao chép kết quả trở lại? Đó là một phần của câu hỏi ... là có bất kỳ lợi ích hiệu suất trong việc sao chép các giá trị cho các biến thành viên chứ không phải là biến cục bộ? –

0

Ngoài ra, còn có tùy chọn thứ ba: địa phương tĩnh. Chúng không được phân bổ lại mỗi lần hàm được gọi (trên thực tế, chúng được bảo toàn qua các cuộc gọi) nhưng chúng không gây ô nhiễm lớp với các biến thành viên quá mức.

+0

Anh ta vẫn phải khởi tạo chúng mỗi lần để có được hành vi tương tự. Và "phân bổ" cho các biến địa phương số tiền để tăng thêm con trỏ ngăn xếp chồng lên nhau. Vì vậy, một trong hai cách, chi phí là chi phí khởi tạo. – Shog9

1

Lượng dữ liệu bạn sẽ tương tác sẽ có ảnh hưởng lớn hơn đến tốc độ thực thi so với cách bạn thể hiện dữ liệu trong việc triển khai thuật toán.

Bộ xử lý không thực sự quan tâm nếu dữ liệu nằm trên ngăn xếp hoặc trên heap (ngoài cơ hội mà phần trên cùng của ngăn xếp sẽ nằm trong bộ nhớ cache của bộ xử lý như đã đề cập), nhưng với tốc độ tối đa, dữ liệu sẽ phải phù hợp với bộ nhớ cache của bộ xử lý (cache L1 nếu bạn có nhiều hơn một mức cache, mà tất cả các bộ vi xử lý hiện đại đều có). Bất kỳ tải nào từ bộ nhớ cache L2 - hoặc $ DEITY cấm, bộ nhớ chính - sẽ làm chậm quá trình thực hiện. Vì vậy, nếu bạn đang xử lý một chuỗi có kích thước vài trăm KB và có cơ hội trên mọi yêu cầu, sự khác biệt thậm chí sẽ không thể đo lường được. Hãy nhớ rằng trong hầu hết các trường hợp, tốc độ 10% trong một chương trình là khá nhiều không thể phát hiện đối với người dùng cuối (trừ khi bạn quản lý để giảm thời gian chạy của lô hàng qua đêm từ 25h trở xuống dưới 24h) vì vậy đây là không đáng lo ngại hơn trừ khi bạn chắc chắn và có đầu ra profiler để sao lưu rằng đoạn mã đặc biệt này nằm trong phạm vi 10% -20% 'vùng nóng' có ảnh hưởng lớn đến thời gian chạy chương trình của bạn.

Các cân nhắc khác phải quan trọng hơn, như bảo trì hoặc các yếu tố bên ngoài khác. Ví dụ, nếu mã trên nằm trong mã đa luồng, sử dụng các biến cục bộ có thể làm cho việc triển khai dễ dàng hơn.

1

Điều đó phụ thuộc, nhưng tôi hy vọng sẽ hoàn toàn không có sự khác biệt. Điều quan trọng là: Việc sử dụng các biến thành viên như là các trạng thái tạm thời sẽ làm cho mã của bạn không reentrant - Ví dụ, nó sẽ thất bại nếu hai luồng cố gọi callback() trên cùng một đối tượng. Sử dụng địa phương tĩnh (hoặc biến thành viên tĩnh) thậm chí còn tồi tệ hơn, bởi vì sau đó mã của bạn sẽ thất bại nếu hai chủ đề cố gắng gọi lại gọi lại() trên bất kỳ đối tượng thisClass nào hoặc con cháu.

+0

Re-entrancy! = Đồng thời. Bạn thực hiện hai câu lệnh đúng được kết nối bởi một "sai nghĩa" gây hiểu nhầm. Mã không tham gia lại có thể bị sai ngay cả trong mã đơn luồng, ví dụ nếu cuộc gọi lại gọi một cái gì đó gọi lại nó, hoặc nếu nó được gọi từ một trình xử lý tín hiệu đã làm gián đoạn nó. +1 anyway. –

+0

Vấn đề với việc xúi giục cả hai là đôi khi người ngây thơ nghĩ rằng bằng cách thêm khóa, hoặc chỉ có một chủ đề, họ có thể làm cho mã của họ được ủy thác lại an toàn. Không phải như vậy: điều này chỉ làm cho nó đồng thời an toàn, và có những nguyên nhân khác cho tái entrancy. –

+0

"Re-entrancy! = Concurrency". Bạn nói đúng - cảm ơn. Tôi đã thay đổi 'tức là' thành 'ví dụ'. – Roddy

1

Một vài điểm chưa được đề cập một cách rõ ràng bởi những người khác:

  • Bạn đang có khả năng cách gọi toán tử gán trong mã của bạn. ví dụ: varC = msg.getString();

  • Bạn có một số chu kỳ lãng phí mỗi khi khung chức năng được thiết lập. Bạn đang tạo các biến, hàm tạo mặc định được gọi, sau đó gọi toán tử gán để nhận giá trị RHS vào các địa phương.

  • Tuyên bố những người dân địa phương phải là const-refs và, tất nhiên, khởi tạo chúng.

  • Biến thành viên có thể nằm trên vùng heap (nếu đối tượng của bạn được phân bổ ở đó) và do đó không phải là địa phương.

  • Ngay cả một vài chu kỳ được lưu là tốt - tại sao lại lãng phí thời gian tính toán, nếu bạn có thể tránh nó.

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