Начнем с того, что это регистры. По сути отличие между ними в том, что xmm (128 bits) хранит меньше данных, чем ymm (256 bits).

Вы можете встретить такое в burst-generated коде, когда разглядываете бесконечные регистры в инспекторе берста.

Пример:

struct MyStruct { 
     public float a1; 
     public float a2; 
     public float a3; 
     public float a4; 
}

myStruct1 = myStruct2 будет использовать xmm (4 флота в 128 битах).

Если же добавить полей:

struct MyStruct { 
     public float a1; 
     public float a2; 
     public float a3; 
     public float a4; 
     public float a5; 
     public float a6; 
     public float a7; 
     public float a8; 
}

то теперь myStruct1 = myStruct2 будет использовать ymm (8 флотов в 256 битах).

Таким образом, мы считаем за одну операцию больше данных.

Но если же мы оставим 6 полей, то будет использован один vmovups xmm, а остальные два поля будут считываться mov rdx.

Read More  

Давайте представим, что у нас есть 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 _;
}
Read More  

Мы хотим выполнить несколько итераций (jobCount) и внутри каждой итерации еще по 128 итерации. Внутри всего этого мы хотим посчитать сумму из NativeArray input и положить в массив NativeArray arrSum: 

for (int j = 0; j < jobCount; ++j) {
     for (int i = 0; i < 128; ++i) {
         var sum = arrSum[i];
         sum += input[(j * 128) + i];
         arrSum[i] = sum;
     }
} 

Как видно из примера, массив input намного больше, чем arrSum. Мы думаем-думаем и решаем оптимизировать наш код. Получается примерно следующее: 

for (int i = 0; i < 128; ++i) {
     var sum = arrSum[i];
     for (int j = 0; j < jobCount; ++j) {
         sum += input[(j * 128) + i];
     }
     arrSum[i] = sum;
} 

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

Пример на самом деле плохой, но показывает нам, что нужно проверять код в Burst Inspector.

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

Read More