Tôi đã xem xét việc sử dụng Lazy<T>
các thuộc tính để giúp cải thiện hiệu suất của mã của riêng tôi (và để tìm hiểu thêm một chút về nó). Tôi đến đây để tìm câu trả lời về thời điểm sử dụng nó nhưng dường như mọi nơi tôi đến đều có những cụm từ như:
Sử dụng khởi tạo lười biếng để trì hoãn việc tạo ra một đối tượng lớn hoặc sử dụng nhiều tài nguyên hoặc thực thi một nhiệm vụ sử dụng nhiều tài nguyên, đặc biệt khi việc tạo hoặc thực thi đó có thể không xảy ra trong suốt thời gian của chương trình.
từ MSDN Lazy <T> Class
Tôi có một chút bối rối vì tôi không chắc chắn nơi để vẽ đường. Ví dụ, tôi coi phép nội suy tuyến tính là một phép tính khá nhanh nhưng nếu tôi không cần phải làm điều đó thì việc khởi tạo lười biếng có thể giúp tôi tránh làm điều đó không và nó có đáng không?
Cuối cùng, tôi quyết định thử làm bài kiểm tra của riêng mình và tôi nghĩ rằng mình sẽ chia sẻ kết quả ở đây. Thật không may, tôi không thực sự là một chuyên gia trong việc thực hiện các loại thử nghiệm này và vì vậy tôi rất vui khi nhận được ý kiến đề xuất cải tiến.
Sự miêu tả
Đối với trường hợp của tôi, tôi đặc biệt quan tâm xem liệu Lazy Properties có thể giúp cải thiện một phần mã của tôi không thực hiện nhiều phép nội suy (hầu hết không được sử dụng) và vì vậy tôi đã tạo một thử nghiệm so sánh 3 cách tiếp cận.
Tôi đã tạo một lớp thử nghiệm riêng biệt với 20 thuộc tính thử nghiệm (hãy gọi chúng là thuộc tính t) cho mỗi cách tiếp cận.
- Lớp GetInterp: Chạy nội suy tuyến tính mỗi khi có thuộc tính t.
- Lớp initInterp: Khởi tạo các thuộc tính t bằng cách chạy phép nội suy tuyến tính cho từng hàm trong hàm tạo. Nhận chỉ trả lại một gấp đôi.
- Lớp initLazy: Thiết lập các thuộc tính t là thuộc tính Lazy để phép nội suy tuyến tính được chạy một lần khi thuộc tính được nhận lần đầu tiên. Sau đó nhận được chỉ nên trả lại một gấp đôi đã được tính toán.
Các kết quả kiểm tra được đo bằng ms và trung bình là 50 tức thời hoặc 20 tài sản nhận được. Mỗi bài kiểm tra sau đó được chạy 5 lần.
Kết quả kiểm tra 1: Khởi tạo (trung bình 50 lần khởi tạo)
Class 1 2 3 4 5 Avg %
------------------------------------------------------------------------
GetInterp 0.005668 0.005722 0.006704 0.006652 0.005572 0.0060636 6.72
InitInterp 0.08481 0.084908 0.099328 0.098626 0.083774 0.0902892 100.00
InitLazy 0.058436 0.05891 0.068046 0.068108 0.060648 0.0628296 69.59
Kết quả thử nghiệm 2: Nhận đầu tiên (trung bình 20 tài sản được)
Class 1 2 3 4 5 Avg %
------------------------------------------------------------------------
GetInterp 0.263 0.268725 0.31373 0.263745 0.279675 0.277775 54.38
InitInterp 0.16316 0.161845 0.18675 0.163535 0.173625 0.169783 33.24
InitLazy 0.46932 0.55299 0.54726 0.47878 0.505635 0.510797 100.00
Kết quả kiểm tra 3: Nhận lần thứ hai (trung bình 20 tài sản được)
Class 1 2 3 4 5 Avg %
------------------------------------------------------------------------
GetInterp 0.08184 0.129325 0.112035 0.097575 0.098695 0.103894 85.30
InitInterp 0.102755 0.128865 0.111335 0.10137 0.106045 0.110074 90.37
InitLazy 0.19603 0.105715 0.107975 0.10034 0.098935 0.121799 100.00
Quan sát
GetInterp
là nhanh nhất để khởi tạo như mong đợi vì nó không làm gì cả. InitLazy
nhanh hơn để khởi tạo hơn là InitInterp
đề xuất rằng chi phí trong việc thiết lập các thuộc tính lười biếng nhanh hơn tính toán nội suy tuyến tính của tôi. Tuy nhiên, tôi hơi bối rối ở đây vì InitInterp
phải thực hiện 20 phép nội suy tuyến tính (để thiết lập thuộc tính t) nhưng chỉ mất 0,09 ms để khởi tạo (thử nghiệm 1), so với GetInterp
chỉ mất 0,28 ms để thực hiện một phép nội suy tuyến tính lần đầu tiên (thử nghiệm 2) và 0,1 ms để thực hiện lần thứ hai (thử nghiệm 3).
Lần đầu tiên phải mất InitLazy
gần 2 lần so với GetInterp
để có được một tài sản, trong khi đó InitInterp
là nhanh nhất, bởi vì nó chiếm được các thuộc tính của nó trong thời gian khởi tạo. (Ít nhất đó là những gì nó nên làm nhưng tại sao nó lại tạo ra kết quả nhanh hơn nhiều so với phép nội suy tuyến tính đơn lẻ? Khi nào chính xác thì nó thực hiện các phép nội suy này?)
Thật không may, có vẻ như có một số tối ưu hóa mã tự động đang diễn ra trong các thử nghiệm của tôi. Sẽ mất GetInterp
cùng thời gian để có được một tài sản lần đầu tiên như lần thứ hai, nhưng nó đang hiển thị nhanh hơn gấp 2 lần. Có vẻ như việc tối ưu hóa này cũng ảnh hưởng đến các lớp khác vì tất cả chúng đều mất cùng thời gian cho thử nghiệm 3. Tuy nhiên, việc tối ưu hóa như vậy cũng có thể diễn ra trong mã sản xuất của riêng tôi cũng có thể là một sự cân nhắc quan trọng.
Kết luận
Mặc dù một số kết quả như mong đợi, nhưng cũng có một số kết quả bất ngờ rất thú vị có lẽ là do tối ưu hóa mã. Ngay cả đối với các lớp trông giống như chúng đang làm rất nhiều công việc trong hàm tạo, kết quả khởi tạo cho thấy chúng vẫn có thể được tạo ra rất nhanh, so với việc có một thuộc tính kép. Mặc dù các chuyên gia trong lĩnh vực này có thể bình luận và điều tra kỹ lưỡng hơn, cảm nhận cá nhân của tôi là tôi cần phải thực hiện thử nghiệm này một lần nữa nhưng trên mã sản xuất của tôi để kiểm tra loại tối ưu hóa nào cũng có thể xảy ra ở đó. Tuy nhiên, tôi đang mong đợi rằng đó InitInterp
có thể là con đường để đi.
get { if (foo == null) foo = new Foo(); return foo; }
. Và có hàng trăm nơi có thể sử dụng nó ...