2009-05-27 26 views
18

Vì một lý do nào đó, tôi luôn giả định rằng các trường readonly có chi phí liên quan đến chúng, mà tôi nghĩ là CLR theo dõi xem trường có được khởi tạo hay không. Chi phí ở đây sẽ là sử dụng bộ nhớ bổ sung để theo dõi trạng thái và kiểm tra khi gán giá trị. Có lẽ tôi giả định điều này bởi vì tôi không biết một trường readonly chỉ có thể được khởi tạo bên trong một hàm tạo hoặc trong khai báo trường và không có kiểm tra thời gian chạy, bạn sẽ không thể đảm bảo nó không được gán nhiều lần trong nhiều phương pháp khác nhau. Nhưng bây giờ tôi biết điều này, nó có thể dễ dàng được kiểm tra tĩnh bởi trình biên dịch C#, phải không? Vì vậy, là trường hợp?Có bất kỳ chi phí nào trong thời gian chạy để chỉ đọc không?

Lý do khác là tôi đã đọc rằng việc sử dụng readonly có tác động hiệu suất 'nhẹ', nhưng họ chưa bao giờ đi vào xác nhận quyền sở hữu này và tôi không thể tìm thấy thông tin về chủ đề này, do đó câu hỏi của tôi. Tôi không biết tác động nào khác của hiệu suất có thể ngoài việc kiểm tra thời gian chạy.

Lý do thứ ba là tôi thấy rằng readonly được lưu giữ trong IL được biên dịch là initonly, do đó, lý do cho thông tin này ở trong IL là readonly là gì hơn là đảm bảo bởi trình biên dịch C# không bao giờ được gán cho bên ngoài một hàm tạo hoặc khai báo? Mặt khác, tôi đã phát hiện ra rằng bạn có thể đặt giá trị của một readonly int thông qua phản ánh mà không có CLR ném một ngoại lệ, điều này không thể thực hiện được nếu readonly là kiểm tra thời gian chạy.

Vì vậy, tôi đoán là: 'tính chỉ đọc' chỉ là một tính năng thời gian biên dịch, bất kỳ ai có thể xác nhận/từ chối điều này? Và nếu có, lý do cho thông tin này được đưa vào IL là gì?

Trả lời

16

Bạn phải xem xét nó từ cùng một quan điểm như các công cụ sửa đổi truy cập. Các công cụ sửa đổi truy cập tồn tại trong IL, nhưng chúng thực sự là một kiểm tra thời gian chạy? (1) Tôi không thể trực tiếp gán các trường riêng vào thời gian biên dịch, (2) Tôi có thể gán chúng bằng cách sử dụng sự phản chiếu. Cho đến nay có vẻ như không có kiểm tra thời gian chạy, chẳng hạn như chỉ đọc.

Nhưng hãy kiểm tra công cụ sửa đổi truy cập. Làm như sau:

  1. Tạo hội A.dll với public class C
  2. Tạo một B.exe hội tham chiếu A.dll. B.exe sử dụng lớp C.
  3. Xây dựng hai cụm. Chạy B.exe hoạt động tốt.
  4. Xây dựng lại A.dll nhưng đặt lớp C thành nội bộ. Thay thế A.dll trong thư mục của B.exe.

Bây giờ, chạy B.exe ném ngoại lệ thời gian chạy.

Các công cụ sửa đổi truy cập cũng tồn tại trong IL, phải không? Vì vậy, mục đích của họ là gì? Mục đích là các assembly khác tham chiếu đến một assembly .Net cần phải biết chúng được phép truy cập và những gì chúng không được phép truy cập, cả thời gian biên dịch và thời gian chạy.

Có vẻ như chỉ có mục đích tương tự trong IL. Nó nói với các hội đồng khác cho dù họ có thể viết cho một lĩnh vực trên một loại cụ thể. Tuy nhiên, chỉ đọcdường như không để có cùng kiểm tra thời gian chạy đó mà các công cụ sửa đổi truy cập hiển thị trong mẫu của tôi ở trên.Có vẻ như chỉ đọc là một kiểm tra thời gian biên dịch và không xảy ra trong thời gian chạy. Hãy xem một mẫu hiệu suất tại đây: Read-only performance vs const.

Một lần nữa, điều này không có nghĩa là IL là vô ích. IL đảm bảo rằng lỗi biên dịch xảy ra ngay từ đầu. Hãy nhớ rằng, khi bạn xây dựng bạn không xây dựng chống lại mã, nhưng hội đồng.

+0

Rất tiếc, bạn đã tìm thấy cùng một phân tích hiệu suất mà tôi đã làm. Vì vậy, +1 cho bạn và tôi đã xóa câu trả lời (chi tiết hơn) của tôi. – Eddie

+0

Làm cho cảm giác hoàn hảo, cảm ơn :) – JulianR

6

Nếu bạn đang sử dụng một biến thể hiện, chuẩn, chỉ đọc sẽ thực hiện gần như giống hệt với một biến thông thường. IL được thêm vào sẽ trở thành một kiểm tra thời gian biên dịch, nhưng bị bỏ qua khá nhiều trong thời gian chạy.

Nếu bạn đang sử dụng một thành viên readonly tĩnh, mọi thứ là một chút khác nhau ...

Kể từ khi thành viên readonly tĩnh được thiết lập trong constructor tĩnh, JIT "biết" rằng một giá trị tồn tại. Không có bộ nhớ thêm - chỉ đọc chỉ ngăn chặn các phương pháp khác từ thiết lập này, nhưng đó là một kiểm tra thời gian biên dịch.

Trong khi JIT biết thành viên này không bao giờ có thể thay đổi, nó bị "mã hóa cứng" khi chạy, do đó hiệu ứng cuối cùng cũng giống như có giá trị const. Sự khác biệt là nó sẽ mất nhiều thời gian hơn trong thời gian JIT, vì trình biên dịch JIT cần phải làm thêm công việc để cố định giá trị của người đọc vào vị trí. (Tuy nhiên, điều này sẽ rất nhanh.)

Expert C++/CLI bởi Marcus Hegee có giải thích hợp lý về điều này.

3

Ngay cả khi chỉ đọc có hiệu lực tại thời gian biên dịch, vẫn cần lưu trữ dữ liệu trong hội đồng (tức là IL). CLR là CommonNgôn ngữ Thời gian chạy - các lớp được viết bằng một ngôn ngữ có thể được sử dụng và mở rộng bằng các ngôn ngữ khác.

Vì mọi trình biên dịch cho CLR sẽ không biết cách đọc và biên dịch mọi ngôn ngữ khác, để bảo toàn ngữ nghĩa của trường readonly, dữ liệu đó cần được lưu trữ trong assembly để trình biên dịch cho các ngôn ngữ khác sẽ tôn trọng nó. Tất nhiên, thực tế là trường được đánh dấu readonly có nghĩa là JIT có thể thực hiện những việc khác, như tối ưu hóa (ví dụ: sử dụng giá trị nội tuyến), v.v. tạo IL sửa đổi một trường initonly bên ngoài của hàm tạo tương ứng (ví dụ hoặc tĩnh, tùy thuộc vào loại trường), sẽ dẫn đến một hội đồng không thể xác minh.

4

Một điểm quan trọng chưa được đề cập bởi bất kỳ câu trả lời nào khác là khi một trường chỉ đọc được truy cập hoặc khi bất kỳ thuộc tính nào được truy cập, yêu cầu được đáp ứng bằng cách sử dụng bản sao dữ liệu. Nếu dữ liệu được đề cập là loại giá trị có nhiều hơn 4-8 byte dữ liệu thì chi phí của việc sao chép bổ sung này đôi khi có thể đáng kể. Lưu ý rằng trong khi có một bước nhảy lớn về chi phí khi cấu trúc tăng từ 16 byte lên 17, cấu trúc có thể lớn hơn một chút và vẫn nhanh hơn các lớp trong nhiều ứng dụng, nếu chúng không được sao chép quá thường xuyên. Ví dụ, nếu một trong những nghĩa vụ phải có một loại đại diện cho các đỉnh của một tam giác trong không gian ba chiều. Việc triển khai đơn giản sẽ là một cấu trúc chứa cấu trúc với ba float cho mỗi điểm; có thể tổng cộng 36 byte. Nếu các điểm và tọa độ trong mỗi điểm là các trường công khai có thể thay đổi, bạn có thể truy cập someTriangle.P1.X một cách nhanh chóng và dễ dàng mà không phải sao chép bất kỳ dữ liệu nào ngoại trừ tọa độ Y của đỉnh 1. Mặt khác, nếu P1 là thuộc tính hoặc Trường readonly, trình biên dịch sẽ phải sao chép P1 thành cấu trúc tạm thời và sau đó đọc X từ đó.

+0

Đây có thể là một câu hỏi rất cũ, nhưng trong trường hợp bất kỳ người đọc tương lai nào quan tâm, bài viết blog sau đây của Jon Skeet chứa nhiều thông tin chi tiết hơn liên quan đến câu trả lời này: http: //codeblog.jonskeet .uk/2014/07/16/vi-tối ưu hóa-the-ngạc nhiên-không hiệu quả-of-readonly-lĩnh vực/ –

+0

@ KlitosKyriacou: Tôi không nghĩ rằng tôi đã nhìn thấy bài viết cụ thể trước đây. NET đã cung cấp một phương tiện mà theo đó các phương thức cấu trúc có thể hứa sẽ không viết 'this' hoặc nói rõ ràng rằng chúng làm được, vì điều đó sẽ giúp (1) cải thiện hiệu năng trong nhiều trường hợp viết 'this' [như được ghi trong blog], và (2) cho phép một cách an toàn có các phương thức viết' this' và có trình biên dịch từ chối các nỗ lực để gọi chúng khi không có biến. Có một số trường hợp mà kiểu dữ liệu lôgic ngữ nghĩa nhất sẽ là một cấu trúc có thể thay đổi, vì ... – supercat

+0

... mẫu 'structVar = structVar.withSomeChange();' không thể được tạo thành an toàn luồng, nhưng 'structVar. makeSomeChange() 'có thể [sử dụng' Interlocked.CompareExchange']. Thật không may, nếu một lớp cho thấy một thuộc tính của kiểu cấu trúc [giả thuyết] 'AtomicBitVector32', trình biên dịch sẽ âm thầm tạo ra mã không có thật cho một phép toán như' thing.flags.TestAndSetBit (23); 'if' Flags' là một thuộc tính chứ không phải là cánh đồng. – supercat

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