-
-
Save andrew-raphael-lukasik/5c3052c9730b5a85a4c49fd33dfd535e to your computer and use it in GitHub Desktop.
| using Unity.Collections; | |
| using Unity.Jobs; | |
| using Unity.Mathematics; | |
| using BurstCompile = Unity.Burst.BurstCompileAttribute; | |
| [BurstCompile] public unsafe struct NoSimd : IJob | |
| { | |
| public float a, b; | |
| public float* c; | |
| void IJob.Execute () { *c = a * b; } | |
| } | |
| [BurstCompile] public unsafe struct Simd : IJob | |
| { | |
| public float4 a, b; | |
| public float4* c; | |
| void IJob.Execute () { *c = a * b; } | |
| } |
observation
This came to me as a surprise but I noticed that Burst packs scalar math in a SIMD operations + will also correctly inline legacy Mathf and VectorN code now. This means that doing that manually is not a requirement nor a reasonable default anymore.
// plain human-readable code:
float GetGradient(float x0y0, float x1y0, float x0y1)
{
float dx = x1y0 - x0y0;
float dy = x0y1 - x0y0;
return Mathf.Abs(dx) + Mathf.Abs(dy);
}
// same code but obfuscated by simd-explicit code:
float GetGradient(float x0y0, float x1y0, float x0y1)
{
float2 d = new float2(x1y0, x0y1) - x0y0;
return math.csum(math.abs(d));
}
// similar but using Vector2:
float GetGradient(float x0y0, float x1y0, float x0y1)
{
Vector2 d = new Vector2(x1y0, x0y1) - new Vector2(x0y0,x0y0);
return Mathf.Abs(d.x) + Mathf.Abs(d.y);
}# all 3 methods generated identical set of operations:
vbroadcastss
vsubps
vandps
vpermilps
vaddssHere is a different case:
// #1 human-readable original code
static float BilinearInterpolator(float valx0y0, float valx0y1, float valx1y0, float valx1y1, float u, float v)
{
return (1 - u) * ((1 - v) * valx0y0 + v * valx0y1) + u * ((1 - v) * valx1y0 + v * valx1y1);
}
// #2 my attempt at trying to be clever
public static float BilinearInterpolator(float valx0y0, float valx0y1, float valx1y0, float valx1y1, float u, float v)
{
float2 one_sub_uv = new float2(1) - new float2(u, v);
float4 mul1 = new float4(one_sub_uv.y, v, one_sub_uv.y, v) * new float4(valx0y0, valx0y1, valx1y0, valx1y1);
float2 add = new float2(mul1.x, mul1.z) + new float2(mul1.y, mul1.w);
float2 mul2 = new float2(one_sub_uv.x, u) * new float2(add.x, add.y);
return math.csum(mul2);
}# 1 original
vsubss
vsubss
vinsertps
vmulps
vmovshdup
vaddss
vmulss
vmulps
vmovshdup
vaddss
vmulss
vaddss
# 2 my "optimization" attempt
vsubps
vshufps
vpermilps
vmulps
vhaddps
vinsertps
vmulps
vmovshdup
vaddssAs you can see my code generated less instructions, which seems good and feels good. But. But performance tests show little to no difference in how fast these 2 sets are. Mine even being sometimes the slower one :V
For weeks I thought this must have been a problem with my performance testing setup. But can't deny it anymore.
My conclusion, however it might change, is to leave SIMD optimizations to Burst because it catch up or beat me every time so far.
Data layouts and memory access patterns is where I see my role now, not here.
NoSimd : IJobSimd : IJobTL;DR: Instructions ending with
___psare simd, while___ssaren't.