Во-первых, ряд людей отталкивают Ольховского, заявляя, что его это ни о чем не беспокоит. В некоторых приложениях в некоторых средах очень важно избегать давления на сбор данных.
Компактный сборщик мусора имеет простую политику; он запускает сбор каждый раз, когда выделяется 1000 КБ памяти. Теперь предположим, что вы пишете игру, которая работает на компактной платформе, и физический движок каждый раз генерирует 1 КБ мусора. Физические движки обычно запускаются порядка 20 раз в секунду. Так что это 1200 КБ давления в минуту, и эй, это уже более одной коллекции в минуту только от физического движка. Если коллекция вызывает заметное зависание в игре, это может быть неприемлемо. В таком случае поможет все, что вы можете сделать, чтобы уменьшить давление на взыскание.
Я учусь этому сам на собственном горьком опыте, хотя я работаю в настольной среде CLR. У нас есть сценарии в компиляторе, в которых мы должны избегать давления на сбор данных, и для этого мы преодолеваем всевозможные обручи объединения объектов. Ольховский, я чувствую твою боль.
Итак, чтобы перейти к вашему вопросу, как вы можете перебирать коллекцию объединенных объектов, не создавая давления на сбор?
Во-первых, давайте подумаем, почему в типичном сценарии возникает давление сбора. Предположим, у вас есть
foreach(var node in ActiveNodes) { ... }
Логически это выделяет два объекта. Во-первых, он выделяет перечисляемое — последовательность — которая представляет последовательность узлов. Во-вторых, он выделяет перечислитель — курсор — который представляет текущую позицию в последовательности.
На практике иногда вы можете немного схитрить и иметь один объект, представляющий и последовательность, и перечислитель, но при этом у вас все равно будет выделен один объект.
Как мы можем избежать этого давления сбора? На ум приходят три вещи.
1) Во-первых, не создавайте метод ActiveNodes. Заставьте вызывающую сторону выполнять итерацию по пулу по индексу и самостоятельно проверять, доступен ли узел. Затем последовательность представляет собой пул, который уже выделен, а курсор представляет собой целое число, ни одно из которых не создает нового давления сбора. Цена, которую вы платите, это дублированный код.
2) Как предполагает Стивен, компилятор примет любые типы, которые имеют правильные общедоступные методы и свойства; они не должны быть IEnumerable и IEnumerator. Вы можете создать свою собственную последовательность изменяемых структур и объекты курсора, передать их по значению и избежать давления при сборе. Опасно иметь изменяемые структуры, но это возможно. Обратите внимание, что List<T>
использует эту стратегию для своего перечислителя; изучить его реализацию для идей.
3) Выделите последовательность и счетчики в куче как обычно и объедините их! Вы уже используете стратегию объединения, поэтому нет никаких причин, по которым вы не можете объединить счетчик. У перечислителей даже есть удобный метод «Сброс», который обычно просто выдает исключение, но вы можете написать собственный объект перечислителя, который использовал бы его для сброса перечислителя обратно в начало последовательности, когда он возвращается в пул.
Большинство объектов перечисляются только один раз за раз, поэтому в типичных случаях пул может быть небольшим.
(Теперь, конечно, у вас может возникнуть проблема курицы и яйца; как вы собираетесь перечислять пул счетчиков?)
person
Eric Lippert
schedule
30.03.2011
_pool
. - person Darin Dimitrov   schedule 30.03.2011IEnumerable<>
, вы можете скопировать этот фрагмент кода (удалив yield) куда угодно (но, очевидно, это может привести к большому дублированию кода) - person digEmAll   schedule 30.03.2011IEnumerable
как к мусору в лучшем случае вводит в заблуждение. Это не одноразовый тип, это не вызовет проблем в приложении, не о чем беспокоиться. Это не больше мусора, чем целое число, которое вы используете в своем цикле. - person Dan Puzey   schedule 30.03.2011