Unity 6.3
0 онлайн 78 гостей 3 в системе
Вход
Программирование в Unity Шаг 141 из 210

Оптимизация массивов

На следующей странице приведены примеры повышения производительности кода при использовании массивов и.

Передача массивов в качестве параметров методам

Иногда удобно написать метод, который создает новый массив, заполняет его значениями и возвращает его. Однако, если этот метод вызывается многократно, то каждый раз выделяется новая память.

Следующий пример кода показывает пример метода, который создает массив каждый раз, когда его вызывают:

// Bad C# script example: Every time the RandomList method is called it
// allocates a new array
using UnityEngine;
using System.Collections;

public class ExampleScript : MonoBehaviour {
    float[] RandomList(int numElements) {
        var result = new float[numElements];
        
        for (int i = 0; i < numElements; i++) {
            result[i] = Random.value;
        }
        
        return result;
    }
}

Один из способов избежать выделения памяти каждый раз — это воспользоваться тем, что массив является типом ссылки. Вы можете изменить массив, переданный методу в качестве параметра, и результаты останутся после возвращения метода. Для этого вы можете настроить примерный код следующим образом:

// Good C# script example: This version of method is passed an array to fill
// with random values. The array can be cached and re-used to avoid repeated
// temporary allocations
using UnityEngine;
using System.Collections;

public class ExampleScript : MonoBehaviour {
    void RandomList(float[] arrayToFill) {
        for (int i = 0; i < arrayToFill.Length; i++) {
            arrayToFill[i] = Random.value;
        }
    }
}

Этот код заменяет существующее содержимое массива новыми значениями. Этот рабочий процесс требует, чтобы вызывающий код выполнил первоначальное распределение массива, но функция не генерирует никакого нового мусора при вызове. Затем массив может быть повторно использован и заполнен случайными числами при следующем вызове этого метода без каких-либо новых распределений на управляемой куче.

Предотвращение повторяющегося доступа к массиву Unity APIs

Одной из причин непреднамеренных распределений на массивах является повторяющийся доступ к Unity APIs, которые возвращают массивы. Все Unity APIs, которые возвращают массивы, создают новую копию массива каждый раз, когда к ним обращаются. Если ваш код обращается к массиву Unity API чаще, чем это необходимо, это может повлиять на производительность вашего приложения.

В качестве примера, следующий код создает четыре копии массива вершин за цикл итераций. Выделения происходят каждый раз, когда доступно свойство .vertices:

// Bad C# script example: this loop create 4 copies of the vertices array per iteration
void Update() {
    for(int i = 0; i < mesh.vertices.Length; i++) {
        float x, y, z;

        x = mesh.vertices[i].x;
        y = mesh.vertices[i].y;
        z = mesh.vertices[i].z;

        // ...

        DoSomething(x, y, z);   
    }
}

Вы можете рефакторизовать этот код в один массив распределения, независимо от количества итераций петли. Чтобы сделать это, настройте код на захват массива вершин перед петлей:

// Better C# script example: create one copy of the vertices array
// and work with that
void Update() {
    var vertices = mesh.vertices;

    for(int i = 0; i < vertices.Length; i++) {

        float x, y, z;

        x = vertices[i].x;
        y = vertices[i].y;
        z = vertices[i].z;

        // ...

        DoSomething(x, y, z);   
    }
}

Оптимальный способ сделать это — поддерживать List вершин, которые кэшируются и повторно используются между кадрами, а затем использовать Mesh.GetVertices для заполнения их по мере необходимости.

// Best C# script example: create one copy of the vertices array
// and work with that.
List<Vector3> m_vertices = new List<Vector3>();

void Update() {
    mesh.GetVertices(m_vertices);

    for(int i = 0; i < m_vertices.Length; i++) {

        float x, y, z;

        x = m_vertices[i].x;
        y = m_vertices[i].y;
        z = m_vertices[i].z;

        // ...

        DoSomething(x, y, z);   
    }
}

Хотя CPU производительность последствий доступа к свойству, которое выделяет массив один раз не высока, повторные доступы в пределах жестких петель создать CPU повторяющиеся доступы расширяют управляемая куча.

Эта проблема распространена на мобильных устройствах, так как Input.touches API ведут себя аналогично предыдущему примеру. Также распространено, что проекты содержат код, похожий на следующий, где распределение происходит каждый раз, когда доступно свойство .touches:

// Bad C# script example: Input.touches returns an array every time it’s accessed
for ( int i = 0; i < Input.touches.Length; i++ ) {
   Touch touch = Input.touches[i];

    // …
}

Чтобы улучшить это, вы можете настроить свой код, чтобы вывести выделение массива из условия цикла:

// Better C# script example: Input.touches is only accessed once here
Touch[] touches = Input.touches;

for ( int i = 0; i < touches.Length; i++ ) {

   Touch touch = touches[i];

   // …
}

Следующий пример кода преобразует предыдущий пример в свободный от выделения Touch API:

// BEST C# script example: Input.touchCount and Input.GetTouch don’t allocate at all.
int touchCount = Input.touchCount;

for ( int i = 0; i < touchCount; i++ ) {
   Touch touch = Input.GetTouch(i);

   // …
}

Примечание: Доступ к свойству (Input.touchCount) остается вне условия цикла, чтобы сохранить CPU эффект вызова метода get свойства.

Альтернативный нераспределяющий APIs

Некоторые Unity APIs имеют альтернативные версии, которые не вызывают выделения памяти. Вы должны использовать их, когда это возможно. В следующей таблице содержится пример выделения APIs и их невыделяющих альтернатив:

Распределение API Нераспределение API альтернатива
Physics.RaycastAll Physics.RaycastNonAlloc
Animator.parameters Animator.parameterCount и Animator.GetParameter
Renderer.sharedMaterials Renderer.GetSharedMaterials

В общем случае, если метод возвращает массив, обычно существует не выделяющая массив версия API, которую можно использовать для передачи массива.

Использовать статические экземпляры массивов нулевой длины

Некоторые команды разработчиков предпочитают возвращать пустые массивы вместо null, когда метод, имеющий значение массива, должен возвращать пустое множество. Эта схема кодирования распространена во многих управляемых языках, в частности в C# и Java.

Когда метод возвращает массив нулевой длины, эффективнее вернуть заранее выделенный статический экземпляр такого массива, чем каждый раз создавать новый пустой.

Использование собственных массивов или других собственных контейнеров для больших массивов

Сборщик мусора Unity является эвристическим: он рассматривает любое поле размером с указатель как указатель. Сборщик мусора проверяет каждый встреченный указатель и каждую ссылку.

При использовании больших массивов (с более чем 10 тысячами элементов) сборщик мусора может интерпретировать большие массивы как длинный список указателей, которые ему нужно проверить, что увеличивает нагрузку на память и замедляет сборщик мусора.

Скрипт VM также должен выделять большие куски управляемого пространства кучи для больших массивов, что увеличивает вероятность того, что случайное значение будет выглядеть как действительный адрес в диапазоне адресов памяти управляемого куска кучи. Большие массивы также являются одним из основных факторов, способствующих фрагментации управляемого кучи.

Если вам нужно выделить массивы такой длины, рассмотрите возможность использования типов коллекций в Unity.Collections пространства имен (включая NativeArray) в Unity ядро API, и структуры данных в Unity Пакет коллекций. В качестве дополнительного преимущества, используя эти коллекции совместимы с система работы и Burst.

Дополнительные ресурсы