Hiệu suất của phương thức tĩnh so với phương thức cá thể


108

Câu hỏi của tôi liên quan đến các đặc điểm hiệu suất của phương thức tĩnh so với phương thức thể hiện và khả năng mở rộng của chúng. Giả sử đối với trường hợp này rằng tất cả các định nghĩa lớp đều nằm trong một tổ hợp duy nhất và nhiều kiểu con trỏ rời rạc là bắt buộc.

Xem xét:

public sealed class InstanceClass
{
      public int DoOperation1(string input)
      {
          // Some operation.
      }

      public int DoOperation2(string input)
      {
          // Some operation.
      }

      // … more instance methods.
}

public static class StaticClass
{
      public static int DoOperation1(string input)
      {
          // Some operation.
      }

      public static int DoOperation2(string input)
      {
          // Some operation.
      }

      // … more static methods.
}

Các lớp trên đại diện cho một mẫu kiểu trợ giúp.

Trong một lớp cá thể, việc giải quyết phương thức cá thể mất một chút thời gian để thực hiện như đối lập với StaticClass.

Câu hỏi của tôi là:

  1. Khi việc giữ trạng thái không phải là vấn đề cần quan tâm (không cần trường hoặc thuộc tính), có phải luôn tốt hơn nếu sử dụng một lớp tĩnh không?

  2. Trong trường hợp có một số lượng đáng kể các định nghĩa lớp tĩnh này (ví dụ như 100, với một số phương thức tĩnh mỗi phương thức) thì điều này sẽ ảnh hưởng tiêu cực đến hiệu suất thực thi hoặc tiêu thụ bộ nhớ so với cùng một số định nghĩa lớp cá thể?

  3. Khi một phương thức khác trong cùng một lớp cá thể được gọi, việc phân giải cá thể có còn xảy ra không? Ví dụ: sử dụng từ khóa [this] như this.DoOperation2("abc")từ bên trong DoOperation1cùng một trường hợp.



ý bạn là gì về "độ phân giải trường hợp"? Ở mức IL, con trỏ "this" có sẵn giống như bất kỳ biến cục bộ nào khác. Trên thực tế, trên một số phiên bản CLR / JIT cũ, bạn có thể gọi một instance-method trên NULL miễn là nó không chạm vào 'this' - mã chỉ lướt qua và không bị lỗi gì .. bây giờ CLR / JIT chứa null rõ ràng- kiểm tra trên tất cả các thành viên invoke ..
Quetzalcoatl

> vijaymukhi.com/documents/books/ilbook/chap8.htm và 'phiên bản cuộc gọi' thay vì chỉ 'cuộc gọi'. Cái trước mong đợi tham số 'this' và cái sau - thì không.
quetzalcoatl

@Quetzalcoatl xin lỗi vì sự nhầm lẫn, câu hỏi là nhiều phương pháp hơn để xử lý từ cùng một phiên bản và nếu điều đó yêu cầu phiên bản đó phải được giải quyết cho chính nó.
Bernie White

1
@quetzalcoatl Tôi cho rằng ý của anh ấy là, "trình biên dịch có thoát khỏi việc kiểm tra thisđiểm trỏ đến một thứ gì đó khi một lớp gọi một phương thức thể hiện trên chính nó không?"
Jon Hanna

Câu trả lời:


152

Về lý thuyết, một phương thức static sẽ hoạt động tốt hơn một chút so với phương thức instance, tất cả những thứ khác đều bằng nhau, vì có thêm thistham số ẩn .

Trong thực tế, điều này tạo ra sự khác biệt nhỏ đến mức nó sẽ bị ẩn trong nhiễu của các quyết định trình biên dịch khác nhau. (Do đó hai người có thể "chứng minh" một người tốt hơn người kia với kết quả không đồng ý). Ít nhất là vì thisnó thường được thông qua trong một sổ đăng ký và thường có trong sổ đăng ký đó để bắt đầu.

Điểm cuối cùng này có nghĩa là về lý thuyết, chúng ta nên mong đợi một phương thức tĩnh lấy một đối tượng làm tham số và thực hiện một điều gì đó với nó sẽ kém hơn một chút so với phương thức tương đương như một thể hiện trên cùng một đối tượng đó. Tuy nhiên, một lần nữa, sự khác biệt rất nhỏ đến mức nếu bạn cố gắng đo lường nó, bạn có thể sẽ phải đo lường một số quyết định trình biên dịch khác. (Đặc biệt là vì khả năng nếu tham chiếu đó được đăng ký trong toàn bộ thời gian cũng khá cao).

Sự khác biệt về hiệu suất thực sự sẽ phụ thuộc vào việc bạn có các đối tượng trong bộ nhớ một cách giả tạo để làm điều gì đó tự nhiên là tĩnh, hay bạn đang kết nối các chuỗi truyền đối tượng theo những cách phức tạp để làm những gì tự nhiên nên có.

Do đó đối với số 1. Khi việc giữ trạng thái không phải là mối quan tâm, tốt hơn hết bạn nên ở trạng thái tĩnh, bởi vì đó là điều mà tĩnh là để làm . Đó không phải là mối quan tâm về hiệu suất, mặc dù có một quy tắc tổng thể để chơi tốt với tối ưu hóa trình biên dịch - nhiều khả năng ai đó đã nỗ lực tối ưu hóa các trường hợp sử dụng bình thường hơn là những trường hợp sử dụng lạ.

Số 2. Không có gì khác biệt. Có một mức chi phí nhất định cho mỗi lớp đối với mỗi thành viên mà nó liên quan đến cả lượng siêu dữ liệu có, số lượng mã có trong tệp DLL hoặc EXE thực tế và sẽ có bao nhiêu mã được ghép nối. Điều này giống nhau cho dù là phiên bản hay tĩnh.

Với mục 3, thisthis vậy. Tuy nhiên lưu ý:

  1. Các thistham số được truyền vào một thanh ghi đặc biệt. Khi gọi một phương thức thể hiện trong cùng một lớp, nó có thể sẽ nằm trong thanh ghi đó rồi (trừ khi nó được lưu trữ và thanh ghi được sử dụng vì lý do nào đó) và do đó không cần thực hiện hành động nào để thiết lậpthis nó thành những gì nó cần được đặt thành . Điều này áp dụng cho một mức độ nhất định, ví dụ: hai tham số đầu tiên của phương thức là hai tham số đầu tiên của một lệnh gọi mà nó thực hiện.

  2. Vì sẽ rõ ràng rằng thiskhông phải là null, điều này có thể được sử dụng để tối ưu hóa cuộc gọi trong một số trường hợp.

  3. Vì rõ ràng đó thiskhông phải là null, điều này có thể làm cho các cuộc gọi phương thức nội tuyến trở lại hiệu quả hơn, vì mã được tạo ra để giả mạo cuộc gọi phương thức có thể bỏ qua một số kiểm tra null mà nó có thể cần.

  4. Điều đó nói rằng, séc null rất rẻ!

Cần lưu ý rằng các phương thức tĩnh chung chung hoạt động trên một đối tượng, thay vì các phương thức cá thể, có thể giảm một số chi phí được thảo luận tại http://joeduffyblog.com/2011/10/23/on-generics-and-some-of- tổng chi phí liên quan / trong trường hợp tĩnh nhất định đó không được gọi cho một loại nhất định. Như anh ấy nói "Như một điều gì đó sang một bên, hóa ra các phương pháp mở rộng là một cách tuyệt vời để làm cho các nội dung trừu tượng chung chung trở nên đáng chơi hơn."

Tuy nhiên, lưu ý rằng điều này chỉ liên quan đến việc khởi tạo các kiểu khác được sử dụng bởi phương thức, không tồn tại. Như vậy, nó thực sự không áp dụng cho nhiều trường hợp (một số phương thức thể hiện khác đã sử dụng kiểu đó, một số mã khác ở nơi khác đã sử dụng kiểu đó).

Tóm lược:

  1. Chủ yếu là chi phí hiệu suất của instance và static dưới đây không đáng kể.
  2. Ví dụ, chi phí phát sinh sẽ đến khi bạn lạm dụng static hoặc ngược lại. Nếu bạn không đưa nó vào quyết định của mình giữa tĩnh và phiên bản, bạn có nhiều khả năng nhận được kết quả chính xác.
  3. Có một số trường hợp hiếm hoi trong đó các phương thức chung tĩnh trong một kiểu khác dẫn đến việc tạo ra ít kiểu hơn so với các phương thức chung ví dụ, có thể khiến nó đôi khi có một lợi ích nhỏ là hiếm khi được sử dụng (và "hiếm khi" đề cập đến kiểu mà nó được sử dụng với thời gian tồn tại của ứng dụng, không phải tần suất nó được gọi). Một khi bạn hiểu những gì anh ấy đang nói trong bài viết đó, bạn sẽ thấy rằng dù sao thì nó 100% không liên quan đến hầu hết các quyết định tĩnh so với phiên bản. Chỉnh sửa: Và nó chủ yếu chỉ có chi phí đó với ngen, không phải với mã jitted.

Chỉnh sửa: Một lưu ý về cách kiểm tra null rẻ như thế nào (mà tôi đã tuyên bố ở trên). Hầu hết các kiểm tra null trong .NET hoàn toàn không kiểm tra null, thay vào đó chúng tiếp tục những gì chúng sẽ làm với giả định rằng nó sẽ hoạt động và nếu một ngoại lệ truy cập xảy ra, nó sẽ được chuyển thành a NullReferenceException. Như vậy, về mặt khái niệm, mã C # liên quan đến kiểm tra null vì nó đang truy cập vào một thành viên cá thể, chi phí nếu nó thành công thực sự là 0. Một ngoại lệ sẽ là một số lệnh gọi nội tuyến, (vì chúng muốn hoạt động như thể chúng được gọi là thành viên thể hiện) và chúng chỉ nhấn vào một trường để kích hoạt cùng một hành vi, vì vậy chúng cũng rất rẻ và dù sao thì chúng vẫn có thể bị loại (ví dụ: nếu bước đầu tiên trong phương thức liên quan đến việc truy cập một trường như nó vốn có).


Bạn có thể nhận xét về việc liệu câu hỏi static vs instance có bất kỳ ảnh hưởng nào đến tính liên kết của bộ nhớ cache không? Việc phụ thuộc vào cái này hay cái kia có nhiều khả năng gây ra lỗi bộ nhớ cache hơn không? Có một dàn ý tốt giải thích tại sao không?
scriptocalypse

@scriptocalypse Không hẳn vậy. Bộ nhớ cache hướng dẫn sẽ không thấy bất kỳ sự khác biệt nào và ở cấp độ đó không có nhiều sự khác biệt giữa việc truy cập dữ liệu thông qua thishoặc thông qua một tham số rõ ràng. Tác động lớn hơn ở đây sẽ là mức độ gần của dữ liệu với dữ liệu liên quan (trường kiểu giá trị hoặc giá trị mảng gần hơn dữ liệu trong trường kiểu tham chiếu) và các mẫu truy cập.
Jon Hanna

"về lý thuyết, chúng ta nên mong đợi một phương thức tĩnh lấy một đối tượng làm tham số và thực hiện điều gì đó với nó sẽ kém hơn một chút so với phương thức tương đương như một thể hiện trên cùng một đối tượng đó." - Ý của bạn là nếu phương thức mẫu trên lấy tham số là đối tượng thay vì chuỗi, không tĩnh là tốt hơn? ví dụ: Tôi có phương thức tĩnh của tôi lấy đối tượng làm tham số và tuần tự hóa nó thành chuỗi và trả về chuỗi. bạn có đề nghị sử dụng non-static trong trường hợp này không?
batmaci

1
@batmaci Tôi có nghĩa là có một cơ hội tốt obj.DoSomehting(2)sẽ rẻ hơn một chút DoSomething(obj, 2)nhưng như tôi cũng đã nói sự khác biệt là rất nhỏ và quá phụ thuộc vào những thứ nhỏ bé có thể kết thúc khác nhau trong lần biên dịch cuối cùng nên nó không thực sự đáng lo ngại. Nếu bạn đang làm một thứ gì đó đắt tiền (liên quan đến loại khác biệt trong cách chơi ở đây) như tuần tự thứ gì đó thành một chuỗi thì nó đặc biệt vô nghĩa.
Jon Hanna

Có một điều, có lẽ hiển nhiên, nhưng quan trọng bị thiếu trong câu trả lời tuyệt vời này: một phương thức instance yêu cầu một instance và việc tạo một instance không hề rẻ. Ngay cả một mặc định ctorvẫn yêu cầu khởi tạo tất cả các trường. Khi bạn đã có một phiên bản, câu trả lời này sẽ áp dụng ("tất cả những thứ khác đều như nhau"). Tất nhiên, giá đắt cctorcũng có thể làm cho các phương thức tĩnh chậm đi, nhưng đó chỉ là lần gọi đầu tiên, nó áp dụng cho các phương thức instance như nhau. Xem thêm docs.microsoft.com/en-us/previous-versions/dotnet/articles/…
Abel

8

Khi việc giữ trạng thái không phải là vấn đề cần quan tâm (không cần trường hoặc thuộc tính), có phải luôn tốt hơn nếu sử dụng một lớp tĩnh không?

Tôi sẽ nói có. Khi tuyên bố một điều gì đó, staticbạn tuyên bố một ý định thực hiện không trạng thái (nó không bắt buộc, nhưng một ý định của một cái gì đó mà người ta mong đợi)

Trong trường hợp có một số lượng đáng kể các lớp tĩnh này (chẳng hạn như 100, với một số phương thức tĩnh mỗi lớp) thì điều này sẽ ảnh hưởng tiêu cực đến hiệu suất thực thi hoặc mức tiêu thụ bộ nhớ khi so sánh với cùng một số lớp cá thể?

Đừng nghĩ như vậy, trừ khi bạn chắc chắn rằng các lớp tĩnh thực sự không ổn định, nếu không, việc phân bổ bộ nhớ rất dễ gây rối và làm rò rỉ bộ nhớ.

Khi từ khóa [this] được sử dụng để gọi một phương thức khác trong cùng một lớp cá thể, việc phân giải cá thể có còn xảy ra không?

Không chắc chắn, về điểm này (đây là một chi tiết thực hiện hoàn toàn của CLR), nhưng hãy nghĩ là có.


Các phương pháp tĩnh không thể bị bắt chước, Nếu bạn thực hiện TDD hoặc thậm chí chỉ thử nghiệm đơn vị, điều này sẽ ảnh hưởng đến các thử nghiệm của bạn rất nhiều.
trampster

@trampster Tại sao? Nó chỉ là một phần logic. Bạn có thể dễ dàng chế nhạo những gì bạn cung cấp cho nó? Để có được hành vi đúng. Và dù sao đi nữa, rất nhiều phương thức tĩnh sẽ là những phần logic riêng trong một hàm.
M. Mimpen

@ M.Mimpen miễn là bạn để nó thành những phần riêng tư nhỏ của bạn, nếu nó là phương thức công khai và bạn sử dụng nó từ các lần đóng khác và cần thay đổi những gì nó thực hiện trong thử nghiệm của bạn thì bạn bị mắc kẹt, những thứ như IO tệp hoặc quyền truy cập cơ sở dữ liệu hoặc mạng gọi vv, nếu đặt trong phương pháp tĩnh sẽ trở thành unmockable, trừ trường hợp như bạn nói rằng bạn bơm một sự phụ thuộc mockable như một tham số cho phương thức tĩnh
trampster

-2

phương thức tĩnh nhanh hơn nhưng ít OOP hơn, nếu bạn sử dụng các mẫu thiết kế, phương pháp tĩnh có thể là mã xấu, để viết logic nghiệp vụ tốt hơn mà không cần tĩnh, các chức năng phổ biến như đọc tệp, WebRequest, v.v. tốt hơn nhận ra là tĩnh ... bạn câu hỏi không có phổ quát câu trả lời


16
Bạn không đưa ra lý lẽ nào cho tuyên bố của mình.
ymajoros

2
@ fjch1997 2 người ủng hộ dường như nghĩ khác (cho những gì nó đáng giá). Nhận xét phiếu phản đối được khuyến khích thực hành trên stackexchange: meta.stackexchange.com/questions/135/…
ymajoros
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.