Как стать автором
Обновить

Комментарии 67

Связано это, якобы, с другой реализацией сбора мусора и туманным Editor Overhead
Попробуйте запустить не в редакторе, а собранное приложение.

Раз уж запускали в редакторе — что говорит профайлер? Куда уходит основное время? На каких версиях производили сравнение?
Написал же, что собрал и запустил .exe. Работает на 20% быстрее.
Да, простите за невнимательность. Что на счет скрина профайлера?
Решил отделить мухи от котлет. Многопоточность и моя реализация — это второй вопрос. Взял базовую алгоритмическую операцию — сжатие массива целых чисел алгоритмом LZW.
Реализация: github.com/hippogamesunity/SimpleGif/blob/master/SimpleGif/GifCore/LzwEncoder.cs

В консольном приложении на тестовом массиве эта операция выполняется за 18 ms. В Unity она же выполняется за 380 ms.

Скриншот профайлера по этой операции: yadi.sk/i/dMwQV18X3YgDhy

Выходит, что базовая операция вроде проверки ключа в словаре выполняется в Unity в 20 раз медленнее.

Похожая проблема: answers.unity.com/questions/325710/dll-call-10x-slower-in-unity.html
Последняя версия Unity 2018, C# 6, .NET 4.0 (как и в консольном приложении). Профайлер показывает Editor Overhead. Анализ производительности алгоритма я проводил (профайлером VS).
Юзаю NatCorder — для рендринга видео. Очень быстро и без лагов. Так, что видимо проблема не в Unity
Подозреваю, что идет обращение к системным библиотекам Android и iOS. Реализация не находится в коде Unity.
Он работает не только под моб. платформы. На mac, win, linux всё тоже хорошо работает. Но скорее всего по поводу мобильных платформ, вы правы. Так же, когда-то искал что-то, для создание gif из юнити и нашёл — github.com/Chman/Moments. И работало довольно быстро
Да, нехорошо с примером получилось) Но это пример. В Unity на этом месте стоит yield return null, главный поток в Coroutine спокойно дожидается завершения потоков кодирования.
Поясните, пожалуйста, как volatile влияет на производительность Unity? Почему в консольном приложении все летает?
Я предполагаю, что в этом потоке binary и exception может быть закешировано и всё-еще равно null, хотя другие потоки его давно посчитали
Переменную необходимо объявлять как volatile в тех случаях, когда ее значение может быть изменено чем-либо, не зависящем от того участка кода, в котором она фигурирует (например, параллельно выполняющимся потоком).
То есть данные посчитаны, но в потоке еще не появились и этот цикл молотит впустую пока не сбросится кеш.

retran, вы это хотели сказать?
Спасибо за разъяснение, но не помогло. Да и не должно это поведение отличаться в консольном приложении и в Unity.
TheShock да, спасибо. Учитывая, что внутри Thread.sleep, поведение вообще трудно предсказуемо.
1. volatile на производительность повлияет отрицательно, но у вас там в принципе баг, этот цикл может работать неправильно.
2. Вы измеряете время выполнения кода с Thread.sleep внутри. Который в принципе может вести себя непредсказуемо.

Посмотрел реализации EncodeParallel/DecodeParallel:
1. Параллельный код так в целом писать не стоит, но придираться не буду.
2. Такая реализация дает очень высокий contention, то есть ваши потоки скорее всего большую часть времени висят на блокировках.

Я бы:
1. Переписал бы код без блокировок, для этого алгоритма это легко.
2. Посмотрел бы как устроен ThreadPool в Unity. И погонял бы под профайлером. Я ничего не знаю о Unity, но почти уверен, что дело в разнице реализаций.
Нет, это UnityEngine.Networking.
В Unity я так же вижу все 16 потоков. Только выполняются они гораздо медленнее.
Thread.sleep это затычка для консольного приложения. И с ней оно отлично работает. В Unity вызов заменен на yield return null, т.е. основной поток просто выходит и вернется сюда на следующем кадре.
А вы не могли бы выложить исходники юнити проекта? Интересно посмотреть на разницу с тем что сейчас в репозитории, возможно там будет виден источник проблем.
Спасибо за готовность помочь! Источников проблем оказалось 2:

1. строковый ключ в алгоритме сжатия LZW, заменил
next = code + " " + colorIndexes[i];
на
next = 257 * (code + 1) + colorIndexes[i];

2. использование LINQ для больших коллекций. Похоже действительно, у сборщика мусора появляется много работы после реализации LINQ в Unity. Убрал LINQ где это было уместно.

Проблем с потоками в Unity нет, все работает как и в консольном приложении.
Я извиняюсь, но опыта программирования потоков у меня нет) Поэтому благодарен за любые замечания. Если придраться, то как стоит написать?
Почему потоки висят в блокировках, если в lock расположены только присвоения, там нет никаких вычислений?
Кроме того, это не дает ответа на вопрос, почему этот же код в Unity работает в 20 раз медленнее)

Как я понимаю в Unity вообще своя система работы с многопоточностью. И если Вам надо написать производительное приложение консольное, то вы можете использовать System.Threading.


Программа на C# запускается как единственный поток, автоматически создаваемый CLR и операционной системой (“главный” или первичный поток), и становится многопоточной при помощи создания дополнительных потоков.

В Unity же дела обстоят совсем иначе. Там исконно все объекты являются производными от MonoBehaviour, что создает разного рода накладки. Не шибко производительным код считается из-за плохой организации хранения объектов в памяти и доступа к ним. Так же многие встроенные методы (Start, Update, Awake) работают по принципу черного ящика и где именно находится точка входа сложно понять как и понять в каком порядке они вызываются. По хорошему посмотреть Ваш проект на Unity, посмотреть на то как там вызываются все методы, как Вы адоптировали код. Если вы импортировали библиотеки, то имеет место overhead вызова метода и загрузки библиотеки, а если вы в каждом кадре такое делаете, то не мудрено что производительность упала многократно. Так же возникают и свои накладки в работе Garbage Collector'а, если у вас создается слишком много объектов, которые потом редко используются.
Писать приложение в консоли и писать в Unity это 2 разные вещи. В помощь могу Вам посоветовать вот эту статью, которая, возможно, поможет ответить на Ваши вопросы.
https://software.intel.com/en-us/articles/putting-your-data-and-code-in-order-data-and-layout-part-2

Подскажите, пожалуйста, а в Unity есть TPL? Сходу нагуглить не получилось.
Есть что-то похожее.
C# Job System
Спасибо, посмотрел. К сожалению, вот это — github.com/Unity-Technologies/EntityComponentSystemSamples/blob/master/Documentation/content/scheduling_a_job_from_a_job.md делает его практически неюзабельным для многопоточных числодробилок.
Почему? Вы всегда можете написать свой пул, оптимизированный под конкретно ваши задачи.
Скачал пример, запустил в однопоточном варианте для LargeSample.gif (400х400, 100 кадров). Получилось Decode: 2.3s, Encode: 5.5s.
Запрофайлил, и сразу в глаза бросается .NET Memory Allocations почти 4GB, что много для такой гифки (все кадры в 32 bit цвете без сжатия — 64MB).
Скриншот из профайлера


Я думаю после оптимизации аллокаций всё станет достаточно быстро без многопоточности и где угодно.
Нужно:
— Указывать capacity для коллекций при инициализации
— Заменить ключ словаря со String на что-нибудь более легковесное
— Очень желательно не использовать LINQ с тысячами элементов
Спасибо, уже сделал ключ long, стало немного быстрее. Но проблема лагов в Unity не исчезла. Написал в UPD статье простой сценарий, который это подтверждает. Что думаете по этому поводу?
largeArray.Contains(1)

У массива нет метода Contains, это LINQ метод. Соответственно результаты зависят от реализации LINQ и оптимизатора.

Более правильный вариант:
Array.IndexOf(largeArray, 1) != -1
Похоже дело действительно в особенностях Garbage Collector в Unity, ведь LINQ генерирует много мусора. А я очень люблю LINQ. Придется посидеть над этим с профайлером.
Есть такая библиотека которая не генрит много мусора как стандартная LINQ, и оптимизирована для работы с UNITY:
smooth.foundations
Но в вашем случае лучше использовать IndexOf дабы не добавлять зависимости
Действительно, IndexOf работает быстрее LINQ в 7 раз в моем случае. Что ж, придется выпиливать LINQ из библиотеки GIF)
Разница 5ms против 2100ms выглядит ну слишком странной.
может быть компилятор C# оптимизирует вызов метода Contains(1) в цикле, так как в нем константа?
я бы попробовал Contains(i)
Он может вообще этот цикл убрать, если определит что Contains ничего не изменяет. Правильным вариантом будет сохранять результат от Contains в переменную снаружи цикла, и как-то использовать эту переменную после цикла.
Проверил, результат тот же. Я полагаю, что компиляция консольного приложения и Unity приложения происходят одинаково.
Нет, но проверю, спасибо!
Часто говорят, что Unity медленный.

Простите, что вмешиваюсь в технические споры, но с точки зрения достаточно поигравшего в игры на юнити человека, меня удивляет, что графика по виду времен 2008 года (quern — самое пристойное из сыгранного) и ранее, может лагать на современных видеокартах. Непонятно, почему не делать игры на оптимизированных движках, UE, например. Я без претензий к качеству игр, встречаются и хорошие (к слову, очень понравилась Lifeless Planet), вопросы к просто убогой производительности. Получаешь, грубо говоря, солитер, жрущий как крузис какой-нибудь.

Жрет не сама юнити, а жрет модель программирования к которой Unity как фреймворк сподвигает разработчиков.
Вспомните старые приложения на дельфи которые висли при нажатии на кнопочку обновить, потому что UI тред блокировался пока идет ответ от базы.

В Unity все пользовательские скрипты отрабатывают в промежутках между кадрами. Чтобы получить ровные 60fps, нужно размазать нагрузку ровным слоем по всем промежуткам (~16мс) между кадрами. Это довольно сложно сделать. Соответственно чуть промахнулся — лови фриз.
Вообще и Unity жрет, и поверхностное знание программирования тоже часто становится причиной тормозов. Нагрузку «размазывать» действительно приходится. Для этого в Unity есть Coroutines. Хотя многие считают, что Coroutines это потоки. Есть в Unity и настоящие потоки, для работы с которыми нужны дополнительные навыки, которые есть далеко не у всех разработчиков игр вроде меня)
Ну собственно в Delphi это и сейчас никуда не делось. В UI треде желательно вообще быстренько обрабатывать только местные события. Но Delphi в упрощении этого добра уже шагнул далеко. Те же TTask, TParallel, TThread и прочее значительно упрощают жизнь. Ну а по поводу Unity… я не понимаю и не приемлю языки, которые не превращаются после компиляции в минимальный и быстрый машинный код, не содержащий никаких оверхедов. Такая неприязнь, наверное еще со времен, когда писал свой Scortched Earth на спектруме используя бейсик для механики+ассемблер для всех эффектов. Сейчас мне это очень напоминает Android NDK…
Привет! Моя статья не предполагает холивары на эту тему. Если мы говорим, что Unity работает медленно, то разбираемся, почему. Нашли 2 проблемы: в моей библиотеке и в реализации LINQ в Unity. Говорить, что игры на Unity тормозят или что в Unity плохая графика, по моему, довольно безосновательно.
>Моя статья не предполагает холивары на эту тему.

Простите, что натолкнул на такую мысль, но я не для холивара спрашивал. Во время игры все время интересовало, чем юнити предпочтительнее UE (у того тоже насколько я знаю, бесплатная версия есть), но не у кого было спросить. После собственно игры как-то тема уже переставала быть актуальной и на серьёзные исследования и сравнения меня уже не хватало. Прочитал, вспомнил игровой опыт, немного поплакал и решил спросить у бывалых.
Ну, первое, почему стоит выбрать Unity — это низкий порог входа (C# в сравнении с C++) и скорость разработки. В большинстве случаев аспекты производительности не имеют определяющего значения. Да, С++ быстрее. А ассемблер еще быстрее.
Спасибо, стало понятнее.
Заголовок спойлера
Теперь буду загадывать на Новый год, чтобы все разработчики игр освоили c++.
Теперь буду загадывать на Новый год, чтобы все разработчики игр освоили c++.
Мой друг, отличный С++ гейм-девелопер утверждает, что даже хорошо зная язык на C# писать проще и быстрее, особенно если ты инди.
В этом нет необходимости. С одной стороны Unity «под капотом» и так работает на c++. С другой стороны, начиная с новых beta версий Unity постепенно переходит на ECS — всё будет работать значительно быстрее.

Эээ… а каким образом УСЫ ака ECS ускорят приложения? Прям заинтриговали :) ECS всего лишь метод, способ, подход, при построении архитектуры приложения. C# Jobs это другие котлеты, вот они могут ускорить работу. Но что усы, что джобсы имеют смысл при работе с кучей одинаковых сущностей, там где появляется выигрыш от распараллеливания задач. Например, RTS. В остальных случаях, это весьма сомнительное удовольствие. Хотя бы потому, что нужна писать в несколько раз больше кода, а пальцы, они знаете, не казенные :) Маркетологи юнитеков не зря едят свой хлеб, я смотрю апологетов усов становится все больше, при том что добрая половина, я уверен, никогда усов и не трогала.
Вы вместо прочтения пространных статей, лучше попробуйте заюзать юнитёвскую ECS в реальном проекте(Хотя бы флапибёрдс или ранер какой запилите на ней).
Как архитектурное решение для всей игры оно не подходит, а подходит только для управления и визуализации однотипных 3д объектов(здесь огромный приток производительности), при попытке заюзать её для спрайтовых объектов, вас ждёт конфуз, и гугление решений использования спрайтов

Как говорил дедушка Ленин, — «Практика критерий истины»
Может вы просто не умеете готовить? Или в Unity плохо приготовленный ECS.

Крайне успешно использую собственный ECS с расширениями в своем домашнем велосипеде (не на юнити).
Честно говоря, не знаю что за проблемы у Unity со спрайтами и чем они вообще могут мешать ECS'у. И почему 3д-объекты в этом плане должны отличаться от спрайтовых.
Крайне успешно использую собственный ECS

Аналогично.
Или в Unity плохо приготовленный ECS.

Поэтому и советую попробовать.
ECS Unity ещё очень в альфе, если взлетит бинды для работы с другими кусками юнити появятся позже.
Хотя бы потому, что нужна писать в несколько раз больше кода, а пальцы, они знаете, не казенные :)


Юзал LeoECS для три в ряд — получил меньше кода чем в обычной реализации, что я сделал не так?
Ну сдается мне, как минимум, вы не научились внимательно читать. Товарищ sith писал:
с новых beta версий Unity постепенно переходит на ECS
Разговор шел о реализации усов в юнити. Напишите свой шедевр используя
… что усы, что джобсы
от юнитеков, сразу себя почувствуете великим писателем. Ну и а если по существу, то у меня нет оснований вам верить на слово, мозоли на пальцах не дают. К коду систем, которые были бы, скажем контроллерами, в случае «стандартного» подхода, у вас неизбежно добавилась кучка компонентов для сущностей. Это первое, что приходит в голову. Количество систем, наверняка несоизмеримо с количеством контроллеров, при том же «стандартном» подходе. Вы еще на энтитас попробуйте там еще один приятный + в виде кодогенерации. С платной версией с рослином еще сносно, но без него рефактор превращается в попаболь ) И поймите меня правильно, что я вовсе не противник усов, я даже за, но не оголтело, как сейчас народ кинулся, а с умом, там где это уместно. Ну реально, есть более красивые решения. Вас не напрягает, что в усах очень высокий процент холостого хода? Потому как запрошенные вами сущности (набор компонентов) ищутся в каждом кадре. Есть они, нет их, контейнер пытается их найти в неком списке. То есть идет ненужное итерирование. Это конечно не смертельно, можно и забить, но мой перфекционизм не дает мне спать ) Попробуйте повозится не с псевдособытийной, а с реально событийной моделью архитектуры.
у меня нет оснований вам верить на слово

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

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

Я тоже за продуманное использование. Оголтело их советуют, те кто с ними не особо работал. Налепить неоптимального говнокода можно в рамках любой архитектуры, в этой архитектуре наказание за говнокод может прийти моментально.

«Попробуйте повозится не с псевдособытийной, а с реально событийной моделью архитектуры.» Я использовал разные архитектуры(8лет на флеше), а там событийный подход из коробки.

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

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

Как минус, холостой ход и выборки.

> Да, С++ быстрее. А ассемблер еще быстрее.

На самом деле, не всегда.
Хороший компилятор С++ (Visual C, GCC) обычно генерирует код лучше, чем если писать вручную.
Плюс может использовать SSE/AVX инструкции.
Согласен. «Крутизна» графики, что в юнити, что в анриале никак не привязаны к самому движку и зависят только от количества, качества и крутизны шейдеров. Разве что можно посмотреть только где проще и удобнее работать с самими материалами в самом редакторе и какой из них позволяет проще «воротить» графику не углубляясь в дебри программирования.
Короче говоря проблема в реализации Linq. Написал цикл с простым перемножением, протестировал в .net 4.7.1, .net Core 2.0 и в Unity Unity 2018.1.0f2 с настройками рантайма .net 4.x Equivalet / .net Standard 2.0 и получил абсолютно идентичные результаты.

Результат для меня довольно загадочный. Не вижу особых отличий между Linq и другим кодом чтобы он так тормозил (в сотни раз). Кто прояснит причину?
Может быть в Mono корявая реализация, как у них было с foreach? Если чистый Mono без юнити даст такие же результаты, то дело в нём.
Просто у юнити старая версия моно, с очень медленным сборщиком мусора, а линк генерит очень много мусора. На юнити надо писать код с минимальным количеством мусорных аллокаций, в принципе это верно и для большого C#, но там предел, когда аллокация и сборка мусора начинают занимать заметное время сильно дальше.
Используете LINQ -> имеете кучу аллокаций -> всё тормозит
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации