Ответ 1
Когда объект, занимающий менее 85 000 байт ОЗУ и не являющийся массивом double
, создается, он помещается в область памяти, называемую кучей Generation Zero. Каждый раз, когда куча Gen0 растет до определенного размера, каждый объект в куче Gen0, к которому система может найти живую ссылку, копируется в кучу Gen1; куча Gen0 затем размалывается по всей массе, поэтому у нее есть место для новых объектов. Если куча Gen1 достигает определенного размера, все, на что ссылается ссылка, будет скопировано в кучу Gen2, после чего кучу Gen0 можно удалить с помощью массива.
Если многие объекты создаются и сразу заброшены, куча Gen0 будет многократно заполняться, но очень мало объектов из кучи Gen0 нужно будет скопировать в кучу Gen1. Следовательно, куча Gen1 будет заполняться очень медленно, если вообще. В отличие от этого, если большинство объектов в куче Gen0 по-прежнему ссылаются при заполнении кучи Gen0, системе придется копировать эти объекты в кучу Gen1. Это заставит систему тратить время на копирование этих объектов, а также кучу Gen1, чтобы заполнить ее настолько, что ее придется сканировать для живых объектов, и все живые объекты оттуда должны быть скопированы снова в кучу Gen2, Все это занимает больше времени.
Другая проблема, которая замедляет работу вашего первого теста, заключается в том, что при попытке идентифицировать все живые объекты Gen0 система может игнорировать любые объекты Gen1 или Gen2, только если они не были затронуты со времени последней коллекции Gen0. Во время первого цикла массив objects
будет постоянно касаться; следовательно, каждой коллекции Gen0 придется потратить время на ее обработку. Во втором цикле он не касался вообще, так что даже если будет столько же коллекций Gen0, что они не займут столько времени, сколько нужно. Во время третьего цикла массив будет постоянно касаться, но никаких новых объектов кучи не создаются, поэтому циклов сбора мусора не потребуется, и не имеет значения, сколько времени они будут выполнять.
Если бы вы добавили четвертый цикл, который создал и оставил объект на каждом проходе, но который также запомнил в слоте массива ссылку на ранее существовавший объект, я бы ожидал, что это займет больше времени, чем в комбинированное время второго и третьего циклов, хотя он будет выполнять те же операции. Возможно, не так много времени, как первый цикл, так как очень немногие из вновь созданных объектов нужно будет скопировать из кучи Gen0, но дольше, чем второй, из-за дополнительной работы, необходимой для определения того, какие объекты все еще живы. Если вы хотите исследовать вещи еще дальше, может быть интересно провести пятый тест с вложенным циклом:
for (int ii=0; ii<1024; ii++)
for (int i=ii; i<Count; i+=1024)
..
Я не знаю точных данных, но .NET пытается избежать сканирования целых больших массивов, из которых только небольшая часть затрагивается путем разбивки их на куски. Если затронут кусок большого массива, все ссылки внутри этого фрагмента должны быть отсканированы, но ссылки, хранящиеся в кусках, которые не были затронуты с момента последней коллекции Gen0, могут быть проигнорированы. Разрыв цикла, как показано выше, может привести к тому, что .NET перестанет касаться большинства блоков в массиве между коллекциями Gen0, что, возможно, приведет к более медленному времени, чем первый цикл.