Я подумывал об использовании Lazy<T>
свойств, чтобы помочь улучшить производительность моего собственного кода (и узнать немного больше об этом). Я пришел сюда в поисках ответов о том, когда его использовать, но, кажется, везде, где я бываю, есть такие фразы:
Используйте отложенную инициализацию, чтобы отложить создание большого или ресурсоемкого объекта или выполнение ресурсоемкой задачи, особенно когда такое создание или выполнение может не произойти в течение срока жизни программы.
из MSDN Lazy <T> класса
Я немного растерялся, потому что не уверен, где провести черту. Например, я рассматриваю линейную интерполяцию как довольно быстрое вычисление, но если мне не нужно это делать, то может ли ленивая инициализация помочь мне избежать этого и стоит ли это того?
В конце концов я решил попробовать свой собственный тест и решил поделиться результатами здесь. К сожалению, я не очень разбираюсь в подобных тестах и поэтому рад получить комментарии, которые предлагают улучшения.
Описание
В моем случае мне было особенно интересно посмотреть, смогут ли Lazy Properties улучшить часть моего кода, которая выполняет большую часть интерполяции (большая часть которой не используется), и поэтому я создал тест, который сравнил 3 подхода.
Я создал отдельный тестовый класс с 20 тестовыми свойствами (давайте назовем их t-свойствами) для каждого подхода.
- GetInterp Class: Запускает линейную интерполяцию каждый раз, когда получается t-свойство.
- Класс InitInterp: Инициализирует t-свойства, выполняя линейную интерполяцию для каждого в конструкторе. Get просто возвращает двойной.
- Класс InitLazy: задает t-свойства как свойства Lazy, чтобы линейная интерполяция запускалась один раз при получении свойства в первый раз. Последующее получение должно просто вернуть уже рассчитанный дубль.
Результаты теста измеряются в мс и представляют собой среднее значение 50 экземпляров или 20 полученных значений. Затем каждый тест был выполнен 5 раз.
Результаты теста 1: Реализация (в среднем 50 экземпляров)
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
Результаты теста 2: Первый получить (в среднем 20 объектов получает)
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
Результаты теста 3: второе получение (в среднем 20 приобретений недвижимости)
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
наблюдения
GetInterp
быстрее всего создать экземпляр, как и ожидалось, потому что он ничего не делает. InitLazy
быстрее создать экземпляр, чем InitInterp
предполагать, что накладные расходы при настройке отложенных свойств быстрее, чем мои вычисления с линейной интерполяцией. Тем не менее, я немного запутался, потому что InitInterp
должен выполнить 20 линейных интерполяций (чтобы установить его t-свойства), но для его создания требуется всего 0,09 мс (тест 1), по сравнению сGetInterp
0,28 мс, чтобы выполнить только одну линейную интерполяцию в первый раз (тест 2) и 0,1 мс, чтобы сделать это во второй раз (тест 3).
Требуется InitLazy
почти в 2 раза больше времени, чем GetInterp
для получения свойства в первый раз, и при этом InitInterp
является самым быстрым, потому что он заполняет свои свойства во время создания экземпляра. (По крайней мере, это то, что он должен был сделать, но почему его результат был гораздо быстрее, чем одна линейная интерполяция? Когда именно он выполняет эти интерполяции?)
К сожалению, похоже, что в моих тестах происходит некоторая автоматическая оптимизация кода. Он должен принять GetInterp
то же самое время , чтобы получить собственность в первый раз , как это делает во второй раз, но он показывает , как более чем в 2 раза быстрее. Похоже, что эта оптимизация также влияет на другие классы, так как все они занимают примерно одинаковое количество времени для теста 3. Однако такая оптимизация может также иметь место в моем собственном рабочем коде, что также может быть важным фактором.
Выводы
Хотя некоторые результаты ожидаемые, есть и некоторые очень интересные неожиданные результаты, вероятно, из-за оптимизации кода. Даже для классов, которые выглядят так, как будто они выполняют большую часть работы в конструкторе, результаты создания экземпляров показывают, что они все еще могут быть созданы очень быстро по сравнению с получением двойного свойства. Хотя специалисты в этой области могут комментировать и исследовать более тщательно, я лично считаю, что мне нужно повторить этот тест снова, но с моим рабочим кодом, чтобы выяснить, какая оптимизация может происходить там же. Тем не менее, я ожидаю, что это InitInterp
может быть путь.
get { if (foo == null) foo = new Foo(); return foo; }
. И есть миллионы возможных мест, чтобы использовать это ...