Страница 1 из 1

Экономия на работе однотипных скриптов

СообщениеДобавлено: 28 май 2017, 21:06
Inessa
В ходе профилирования своей игры я обнаружила следующий момент.

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


У меня по уровню вдоль гоночной трассы программно генерируются и расставляются игровые объекты типа Coins - вращающиеся объекты в виде монеты.

Пользователь во время езды касается монетки, монетка исчезает и попадает в копилку игрока.

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

К этому префабу подключен скрипт-ротатор, вращающий монетку вокруг своей оси.

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

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

Следовательно, необходимо найти какой-то способ сэкономить на выполнении этой однотипной операции вращения одной и той же монетки.

Вот скрипт который я использую для этих целей.

Подскажите, пожалуйста, что нужно сделать, чтобы как-то сэкономить ресурсы на вращении монет?

Т.е. чтобы Unity обсчитывал вращение для одной монетки, а применял это вращение для всех остальных монеток?

Синтаксис:
Используется csharp
using UnityEngine;
using System.Collections;

public class PickUpRotator : MonoBehaviour
{
        void Update()
        {
                transform.Rotate(new Vector3(15, 30, 45) * Time.deltaTime);    
        }
}
 

Re: Экономия на работе однотипных скриптов

СообщениеДобавлено: 28 май 2017, 21:28
samana
Inessa писал(а):Т.е. чтобы Unity обсчитывал вращение для одной монетки, а применял это вращение для всех остальных монеток?

Вы сами ответили на свой вопрос.
Все монетки должны находится в массиве. В Update один раз рассчитываете вращение, а потом в цикле перебираете все монетки и устанавливаете их поворот таким же.

Re: Экономия на работе однотипных скриптов

СообщениеДобавлено: 28 май 2017, 21:45
seaman
Зачем иметь сразу все монетки? У Вас весь уровень с 500 монетами на экране виден?

Re: Экономия на работе однотипных скриптов

СообщениеДобавлено: 28 май 2017, 22:06
Cr0c
Сделал на одном скрипте все монетки - 500 штук грузят на 13%-15% процессор
Синтаксис:
Используется csharp
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class coin_test : MonoBehaviour {

    public GameObject prefab_coin;
    [Range(10,1000)]
    public int coin_count = 10;
    [Range(0f, 10f)]
    public float speed = 1f;
    [SerializeField]
    public List<Transform> trs = new List<Transform>();
    public float dist = 10f;

    // Use this for initialization
    void Start () {
        Vector3 up_left =    Camera.main.ScreenToWorldPoint(new Vector3(0f, Screen.height, dist));
        Vector3 zero =  Camera.main.ScreenToWorldPoint(new Vector3(0f, 0f, dist));
        Vector3 down_right = Camera.main.ScreenToWorldPoint(new Vector3(Screen.width, 0f, dist));
        Vector3 hor = down_right - zero;
        Vector3 ver = up_left - zero;

        for (int i=0; i<coin_count; i++)
        {
            float rx = Random.Range(0f, 1f);
            float ry = Random.Range(0f, 1f);
            Vector3 pos = zero + hor * rx + ver * ry;
            Transform tr = (Instantiate(prefab_coin, pos, Quaternion.identity)).transform;
            trs.Add(tr);
        }
    }

    // Update is called once per frame
    void Update () {
        //foreach (Transform tr in trs)
        for (int i=0; i<trs.Count; i++)
            trs[i].Rotate(Vector3.up, speed, Space.World);
        }
}
 


P.S.:
В Update один раз рассчитываете вращение, а потом в цикле перебираете все монетки и устанавливаете их поворот таким же.

это уменьшает нагрузку примерно на 1%-1.5% по профайлеру

Re: Экономия на работе однотипных скриптов

СообщениеДобавлено: 29 май 2017, 00:03
Inessa
Скажите, а какие существуют метрики, чтобы я могла корректно сравнивать проделанные тесты?

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

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

Сейчас же (в приведенном выше скрипте) в памяти необходимо хранить List<Transform> для каждой монетки и совершать с ним различные манипуляции.

Причем операции с доступом к элементам List<> считаются одними из самых медленных операций, что ставит под вопрос использования списков для нужных мне целей.

Я попыталась запустить у себя приведенный выше скрипт.

При этом мне потребовалось заменить

Transform tr = (Instantiate(prefab_coin, pos, Quaternion.identity)).transform;

на

Transform tr = (Instantiate(prefab_coin, pos, Quaternion.identity)) as Transform;

т.к. в скрипте возникла ошибка компиляции.

Я также заменила

trs[i].Rotate(Vector3.up, speed, Space.World);

на

trs[i].Rotate(new Vector3(15, 30, 45) * Time.deltaTime);

т.к. мне нужно проверить самый тяжелый случай, когда вращение происходит по 3-м осям и мне необходимо использовать Time.deltaTime.

После осуществления всех этих манипуляций и запуска скрипта - префаб с монетками инстанцировался - произошло создание заданного количества монеток.

Но список List<> так и оказался незаполненным.

Соответственно в методе Update() у меня ничего не происходит - монетки не вращаются.

Поэтому сделать какие-либо выводы я не могу.

В связи с этим возникает еще несколько дополнительных вопросов.

1. Как исправить ситуации с учетом того, что, возможно, использование List<Transform> - не самый оптимальный вариант?

2. Как все-таки запустить этот тестовый скрипт, чтобы инстанцированные монетки вращались с использованием расчета только одного Transform?

3. Как можно корректно сравнить исходный и модифицированный вариант?

4. Какие метрики производительности для этого нужно использовать?

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

Нужно понимать насколько загружен процессор в общем для исходного и модифицированного вариантов.

Т.е. возможен, например, случай, когда исходный вариант тратит ресурсы процессора на 50% (условная цифра), а модифицированный вариант - на 75% (тоже условная цифра).

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

Но выигрыша от модифицированного варианта все-равно нет, т.к. накладные расходы на хранение, обработку и манипуляцию данными в списке увеличили общую нагрузку на процессор, съели дополнительную оперативную память и т.д.

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

5. Как с помощью такой метрик как FPS корректно провести оценку производительности (вычислительных затрат) при сравнении исходного и модифицированного вариантов скриптов, используемых для вращения монеток?

Re: Экономия на работе однотипных скриптов

СообщениеДобавлено: 29 май 2017, 01:04
DbIMok
В общем-то это общеизвестно https://blogs.unity3d.com/ru/2015/12/23 ... ate-calls/ Не хотите List, используте массив. Метрики - тестовая сценка и замеры.

Re: Экономия на работе однотипных скриптов

СообщениеДобавлено: 29 май 2017, 06:46
Valentinus
я думаю имеет смысл вертеть монетки только те что видны на экране.
методами OnBecameVisible() и OnBecameInvisible() устанавливаете bool переменную в true или false, а в Update делаете вращение только если переменная в true.

таким образом у вас из 500 монет будут крутиться может от силы штук 50. то есть уменьшение затрат на вращение - в десять раз.

кроме того, можно вертеть не в Update, который вызывается каждый кадр, а сделать функцию, которую запускать через InvokeRepeat с периодичностью 0.1f секунды (если приращение угла поворота небольшое, то будет плавно, а впрочем, даже если и большое приращение- во время гонки игрок вряд ли будет рассматривать плавность верчения монетки).
это тоже даст уменьшение затрат на вращение в несколько раз.

Re: Экономия на работе однотипных скриптов

СообщениеДобавлено: 29 май 2017, 07:59
Cr0c
Inessa писал(а):Причем операции с доступом к элементам List<> считаются одними из самых медленных операций, что ставит под вопрос использования списков для нужных мне целей.
Там же по индексу - это не итератор, это быстро.
Inessa писал(а):При этом мне потребовалось заменить
Transform tr = (Instantiate(prefab_coin, pos, Quaternion.identity)).transform;
на
Transform tr = (Instantiate(prefab_coin, pos, Quaternion.identity)) as Transform;
т.к. в скрипте возникла ошибка компиляции.

У меня работает, у Вас тоже всё должно было работать.
Inessa писал(а):Я также заменила
trs[i].Rotate(Vector3.up, speed, Space.World);
на
trs[i].Rotate(new Vector3(15, 30, 45) * Time.deltaTime);
т.к. мне нужно проверить самый тяжелый случай, когда вращение происходит по 3-м осям и мне необходимо использовать Time.deltaTime.

Разницы никакой, всё равно там собирается кватернион.

Re: Экономия на работе однотипных скриптов

СообщениеДобавлено: 29 май 2017, 13:45
jetyb
В совсем уж жестких случаях (если считать вращение монетки визуальной характеристикой, не привязанной к видео), можно вычислять вращение в вершинном шейдере (наподобие того как вращаются частицы в системе частиц или поворачиваются спрайты):

а. Можно на CPU считать общую матрицу вращения, передавать ее в шейдер (Shader.SerGlobalMatrix) - шейдере умножать и на нее. Недостаток - все монетки будут вращаться с одинаковой фазой.

б. Каждому объекту дать свою уникальную фазу: fi_i число (0, 2pi), в шейдер передавать текущее время t . Вращение считать в вершинном шейдере для каждого объекта индивидуально в зависимости от текущего времени (fi_i + t)