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

Пускаем волны многопоточности[Job System] Часть 1.

СообщениеДобавлено: 29 май 2018, 20:21
lawsonilka
Пускаем волны многопоточности. Часть 1.

Не так давно официально, и давно неофициально, юнитеки хотели внедрить систему для работы с параллельными вычислениями. Многопоточность уже очень давно доступна в unity(нет, корутины это не многопоточность!), но все эти инструменты приходилось создавать самому разработчику, самому решать проблемы и обходить подводные камни. Юнитеки же хотели всю работу перекинуть на движок, а разработчику предоставить только рычаги управления. Так вот наконец они представили новую систему задач(ориг. Job system) в версии 2018 года для решения этой проблемы.

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


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

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

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

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

...структуры, классы!?
Скрытый текст:
Главное отличие структур от классов в том что структура это тип значения - при передаче они буквально копируют себя(данные), классы же наоборот ссылочный тип данных, при передаче данных они возвращают ссылку на эти самые данные находящиеся в памяти.


Для того чтобы представить какую либо структуру в виде задачи существует несколько интерфейсов, каждый для своего рода задач:
- IJob
- IJobParallelFor
- IJobParallelForTransform

В первом случае IJob простой интерфейс с одним методом Execute который ни чего не принимает и не возвращает. Этот интерфейс самый простой вариант для создания новой задачи. После запуска задачи можно выполнять вычисления в методе Execute. Этот метод сработает только один раз, к примеру можно в нем вывести что нибудь в консоль.
Синтаксис:
Используется csharp
public struct JobStruct : IJob {

 public void Execute() {
  Debug.Log("It's alive");
 }

}


Следующий интерфейс IJobParallelFor чуть посложнее.
Синтаксис:
Используется csharp
public struct JobStruct : IJobParallelFor {

 public void Execute(int index) {
  Debug.Log("Loop index: " + index);
 }

}

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

Ну и IJobParallelForTransform интерфейс который, как понятно из названия, служит для конкретной работы со свойствами transform'а объекта.
Синтаксис:
Используется csharp
public struct JobStruct : IJobParallelForTransform {

 public void Execute(int index, TransformAccess transform) {
  Debug.Log("Rotation: " + transform.rotation);
 }

}

Здесь уже есть второй параметр TransformAccess который содержит в себе свойства transform'а - позицию, вращение, размер.

В новой системе нет прямого доступа к unity объектам - управлять напрямую компонентами и объектами через задачи у вас не получиться. В задачах можно работать только с простыми типами данных(int, float, byte и тд.), а конкретней то только с не преобразуемыми.

Так, структуры с задачами написаны, теперь можно переходить к самому главное - их выполению.

Полный цикл выполнения задачи состоит из таких действий как:
- создание задачи
- наполение задачи данными для вычисления
- выполнение задачи
- сбором полученных данных

Для создания задачи разберем самый первый пример с интерфейсом IJob.
Синтаксис:
Используется csharp
public struct JobStruct : IJob {
 
 public void Execute() {
  Debug.Log("It's alive");
 }

}

Создадим простой MonoBehaviour скрипт.
Синтаксис:
Используется csharp
using Unity.Jobs;

public class SimpleMono : MonoBehaviour {

}

Не забывайте импортировать нужные библиотеки.

Далее в методе Start создадим экземпляр задачи.
Синтаксис:
Используется csharp
public class SimpleMono : MonoBehaviour {

 void Start() {
  JobStruct job = new JobStruct();
 }

}

Так как пока она не содержит ни какие данные, то следущим шагом можно поместить ее в "банк" задач. Выглядит это так.
Синтаксис:
Используется csharp
public class SimpleMono : MonoBehaviour {

 void Start() {
  JobStruct job = new JobStruct();
  job.Schedule();
 }

}

Этим действием мы создали новый экземпляр задачи в неком "банке" планировщике задач. Для выполнения задачи нужно поместить ее в какой либо поток, а этим действием мы как бы резервировали для задачи отдельный поток, который пока ни чего еще не выполняет, чтобы это случилось мы должны пометить эту задачу как "свободную для выполнения" и для этого нужно вызвать метод Complete.
Синтаксис:
Используется csharp
public class SimpleMono : MonoBehaviour {

 void Start() {
  JobStruct job = new JobStruct();
  job.Schedule().Complete();
 }

}

Теперь когда вы запустите игру, то увидите в консоли надпись. Это и есть ваша, возможно первая, многопоточная программа.
Изображение

Планировщик сам распределяет задачи между потоками.

Для того чтобы контроллировать процесс выполнения задач есть отдельный объект JobHandle - это урезанная версия того самого "планировщика", который на самом деле можно получить еще при вызове метода Schedule.

Вернемся на два действия назад, сотрем метод Complete и создадим экземпляр JobHandle объекта.

Синтаксис:
Используется csharp
public class SimpleMono : MonoBehaviour {

 void Start() {
  JobStruct job = new JobStruct();
  JobHandle handle = job.Schedule();
 }

}

Теперь после вызова метода Schedule мы получаем объект JobHandle. Мы можем опять вызвать метод Complete чтобы выполнить задачу уже у самого объекта JobHandle.
Синтаксис:
Используется csharp
public class SimpleMono : MonoBehaviour {

 void Start() {
  JobStruct job = new JobStruct();
  JobHandle handle = job.Schedule();
  handle.Complete();
 }

}

Это точно также выполнит задачу.
С помощью свойства IsCompleted объекта JobHandle можно узнать когда задача была выполнена.
Синтаксис:
Используется csharp
public class SimpleMono : MonoBehaviour {

 void Start() {
  JobStruct job = new JobStruct();
  JobHandle handle = job.Schedule();
  handle.Complete();
  if (handle.IsCompleted) {
   print("Job is complete");
  }
 }

}

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

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

следующая часть

Re: Пускаем волны многопоточности[Job System] Часть 1.

СообщениеДобавлено: 30 май 2018, 16:15
ikhtd
На сколько процентов такой способ мнгопоточности может повысить производительность разрабатываемой игры?
Графа само собой свою часть съест - только учитывая скриптовую часть.

Re: Пускаем волны многопоточности[Job System] Часть 1.

СообщениеДобавлено: 30 май 2018, 17:13
lawsonilka
На сколько процентов такой способ мнгопоточности может повысить производительность разрабатываемой игры?

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

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