С помощью таких простых штук можно определить нынешнюю структуру между собой.

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

Читать далее  

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

Из минусов - отсутствие safety handler.

Важно: UnsafeList содержит данные о количестве элементов, т.е. относиться к этой структуре нужно как к любому ValueType (оно будет копироваться).

В принципе это касается всех коллекций.

Читать далее  

Давайте представим, что у нас есть 2 структуры:

public struct V3 {
  public float3 x;
}

public struct V4 {
  public float4 x;
}

А теперь напишем джобу, которая перекладывает данные из одного массива в другой:

[BurstCompile]
public struct MyJob : IJob {
  [ReadOnly] public NativeArray<V3/V4> source;
  public NativeArray<V3/V4> dest;
  public void Execute() {
    for (int i = 0; i < source.Length; ++i) {
      dest[i] = source[i];
    }
  }
}

Какой вариант джобы будет работать быстрее? Логика подсказывает нам, что V3, т.к. данных копировать нужно меньше, да и вообще размер будет намного меньше. Давайте разберемся же, что там получается на выходе: Для V3 варианта мы должны скопировать структуру значение за значением, т.е. 3 раза.

Для V4 варианта мы вроде должны скопировать 4 значения. Но тут вламывается векторизация и выходит, что вариант V4 будет работать примерно на треть быстрее, чем вариант V3. Но не расстраивайтесь, можно все исправить: (да, можно исправить разными способами)

public struct V3 {
  public float3 x;
  public float _;
}
Читать далее  

MemoryAllocator проще всего представить как один большой неразрывный массив байт. Чтобы положить туда данные - нужно всего лишь знать по какому индексу это делать. Для этого аллокатор разбивается на блоки. В пустом аллокаторе блок всего один, он занимает всю область памяти от начала и до конца. Блок - это структура, у которой есть часть заголовка (с указателями на следующий/предыдущий блоки, состоянием "свободен"/"занят" и размером блока) и следом сами данные.

[block_size][state][prev][next][user_data]

Когда мы просим аллокатор дать нам память определенного размера, нам нужно найти свободный блок памяти. Тут мы просто переходим от первого блока до последнего и ищем блок подходящего размера. Если блок не нашли - добавляем новый. А возвращаем мы не unsafe-указатель, а собственный указатель, в котором записан тот самый индекс в нашем массиве байт. Когда мы просим освободить память, мы выставляем блоку состояние "свободен" и мерджим его с соседними свободными блоками, если такие есть.

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

Таким образом мы получаем следующие бенефиты:

1. Большой кусок памяти, который мы можем скопировать/передать по сети/уничтожить очень быстро;

2. Выдаваемые указатели можно так же передавать по сети, т.к. они будут валидны на любом клиенте, если тот имеет такой же аллокатор;

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

Реализацию можно посмотреть тут: https://github.com/chromealex/csharp-memory-allocator

Читать далее  

Многие встречались и даже использовали memcpy. В юнити это UnsafeUtility.MemCpy. Но там есть еще UnsafeUtility.MemMove. Главное отличие в том, что memcpy работает быстрее, т.к. не делает дополнительных рекомендаций в отношении области памяти, а вот memmove делает. Проще говоря, не воспользуйтесь memcpy, если два блока пересекаются, т.к. это приводит к неопределенному результату. Т.е. когда вы копируете данные из одного массива в другой - память никак не пересекается и можно использовать memcpy. Но если вы хотите «переместить» данные в один массив, воспользуйтесь memmove.

Читать далее  

При сериализации структуры в бинарник можно использовать подход *(T*)ptr = value, где ptr — указатель на байт массива, a value — данные, которые мы хотим туда записать. Если вы используете управляемый массив, не забудьте использовать фиксированный.

Читать далее