Как правильно работать с длиной массива?

Рейтинг: 1Ответов: 3Опубликовано: 07.08.2023

К примеру, у меня есть цикл на 1 000 000 итераций, в котором используется массив.

Будет правильнее, если я вынесу длинну массива в отдельную переменную, которая находится за пределами цикла, и после буду обращаться к ней? Или правильнее будет обращаться к свойству массива Length?

Какой из способов менее затратный по ресурсу? Когда вычисляется свойство Length, при определении массива или при обращение к этому свойству?

Ответы

▲ 3Принят

Свойство T[].Length вычисляется при создании массива и далее изменено быть не может. Поэтому явное кеширование этого значения в локальную переменную не даст ощутимого прироста к производительности.

А вот при работе с изменяемыми коллекциями, например с List<T> кеширование может быть полезным, но не всегда, нужно разбирать частные случаи.

▲ 4

Предлагаю рассмотреть следующий код:

public void M1() {
    var array = new int[10];
    
    for (int i = 0; i < array.Length; i++)
        array[i] = 1;
}

public void M2() {
    var array = new int[10];
    
    int len = 10; // array.Length;
    
    for (int i = 0; i < len; i++)
        array[i] = 1;
}

public void M3() {
    var array = new int[10];
    
    int len = 20;
    
    for (int i = 0; i < len; i++)
        array[i] = 1;
}

public void M4(int len) {
    var array = new int[10];
            
    for (int i = 0; i < len; i++)
        array[i] = 1;
}

Смотрим машинный код на sharplab.io (вкладка JIT Asm).

Можно увидеть, что в методе M1 код ассемблера не содержит проверок на выход за пределы массива.

В методе M2 компилятор достаточно умён, чтобы определить, что значение переменной len равно размеру массива (можно задать явно 10, можно задать array.Length) и тоже не вставляет такие проверки.

В методе M3 значение len отличается от константы, используемой при создании массива, поэтому генерируется дополнительный код с проверками.

В методе M4 аналогично, компилятор не может знать, какое значение придёт извне в параметр len, поэтому добавляет кучу проверок.


Примечание.
При использовании ArrayPool<T> в большинстве случаев не получится использовать свойство Length, т. к. размер массива, полученного из пула, будет превышать запрошенный размер. Поэтому придётся указывать размер переменной/константой.

▲ 0

Само собой, все вычисляемые свойства нужно кешировать. Если производительность является главным критерием, то нужно кешировать всё, что используется более одного раза. Это в равной степени относится и к доступу к члену через ".", и к доступу к элементу коллекции по индексу или ключу.

До входа в цикл сделайте все предварительные вычисления и сохраните значения во временных переменных.

UPD

using System;
public class Program {
    public static void Main() {
    var a = new int[100000000]; // 10E8
    var b = new int[100000000];
    var watch = new System.Diagnostics.Stopwatch();
    watch.Start();
    for (int i = 0; i < a.Length; a[i++] = 3);
    Console.WriteLine(watch.ElapsedMilliseconds / 1000.0); // 0,11
    watch.Reset(); var bl = b.Length; watch.Start();
    for (int i = 0; i < bl; b[i++] = 5);
    Console.WriteLine(watch.ElapsedMilliseconds / 1000.0); // 0,11
    }
}

Результаты, естественно, получаются разные, но близкие, с переменной время чуть меньше (на несколько миллисекунд) или равное, причём включение или отключение оптимизации на результат не влияет.

Microsoft (R) Visual C# Compiler version 4.8.4084.0 for C# 5