C#.Net, why does this SIMD optimized code run slower?

Viewed 85

Have tried to optimize my function in c# using System.Numerics Vector for SIMD operations. This is the original function:

private void RenderScanlinesTextureMapInternal(int[] minX, int[] maxX, float[] min1W, float[] max1W, System.Numerics.Vector2[] minTex, System.Numerics.Vector2[] maxTex, Texture t) {
    unsafe {
        fixed (byte* tbufferbyte = t.internalBuffer) {
            int tdimension = t.dimension;
            fixed (byte* f = backBuffer) {
                for (int y = 0; y < Height; y++) {
                    if (maxX[y] == 0 && minX[y] == 0) {
                        continue;
                    }
                    //
                    int lineLength = maxX[y] - minX[y];
                    //
                    float oneW = min1W[y];
                    float oneWDist = max1W[y] - min1W[y];
                    float dOneW = oneWDist / (float)lineLength;
                    //
                    float texX = minTex[y].X;
                    float texY = minTex[y].Y;
                    float texDistX = maxTex[y].X - minTex[y].X;
                    float texDistY = maxTex[y].Y - minTex[y].Y;
                    float dTexX = texDistX / lineLength;
                    float dTexY = texDistY / lineLength;
                    //
                    Int32* p;
                    p = (Int32*)f + ((int)y * Width) + minX[y];

                    for (int x = minX[y]; x < maxX[y]; x++, oneW += dOneW, texX += dTexX, texY += dTexY) {
                        float oneOneW = 1 / oneW;
                        float tu = texX * oneOneW;
                        float tv = texY * oneOneW;
                        float tudimensionF = tu * tdimension;
                        float tvdimensionF = tv * tdimension;
                        int tudimension = (int)tudimensionF;
                        int tvdimension = (int)tvdimensionF;
                        int u = tudimension & tdimension - 1;
                        int v = tvdimension & tdimension - 1;
                        int pos = (v * tdimension) + u;
                        *p++ = *((Int32*)tbufferbyte + pos);
                    }
                            
                }
            }
        }
    }
}

This is the function with SIMD operations:

private void RenderScanlinesTextureSIMD(int[] minX, int[] maxX, float[] min1W, float[] max1W, System.Numerics.Vector2[] minTex, System.Numerics.Vector2[] maxTex, Texture t) {
    unsafe {
        fixed (byte* tbufferbyte = t.internalBuffer) {
            int tdimension = t.dimension;
            fixed (byte* f = backBuffer) {
                int VectorFCount = Vector<float>.Count;
                //
                Vector<float> VoneOneW;
                Vector<float> Vtu;              //float tu = texX * oneOneW;
                Vector<float> Vtv;              //float tv = texY * oneOneW;
                Vector<float> VtuDimensionF;    // float tudimensionF = tu * tdimension;
                Vector<float> VtvDimensionF;    // float tvdimensionF = tv * tdimension;
                Vector<int> VtuDimension;       // int tudimension = (int)tudimensionF
                Vector<int> VtvDimension;       // int tvdimension = (int)tvdimensionF
                Vector<int> Vu;                 // int u = tudimension & tdimension - 1;
                Vector<int> Vv;                 // int v = tvdimension & tdimension - 1;
                Vector<int> Vpos;               // int pos = (v * tdimension) + u;
                Vector<float> Tfloat;

                //
                float[] Arrtdimension = new float[VectorFCount];
                int[] Arrtdimensionint = new int[VectorFCount];
                int[] Arrtdimensionminus1 = new int[VectorFCount];
                float[] ArroneW = new float[VectorFCount];
                float[] ArrtexX = new float[VectorFCount];
                float[] ArrtexY = new float[VectorFCount];
                float[] ArroneOneW = new float[VectorFCount];
                float[] ArrTu = new float[VectorFCount];
                float[] ArrTv = new float[VectorFCount];
                float[] ArrTuDimensionF = new float[VectorFCount];
                float[] ArrTvDimensionF = new float[VectorFCount];
                int[] ArrTuDimension = new int[VectorFCount];
                int[] ArrTvDimension = new int[VectorFCount];
                int[] Arru = new int[VectorFCount];
                int[] Arrv = new int[VectorFCount];
                int[] Arrpos = new int[VectorFCount];
                for (int y = 0; y < Height; y++) {
                    if (maxX[y] == 0 && minX[y] == 0) {
                        continue;
                    }
                    //
                    int lineLength = maxX[y] - minX[y];
                    //
                    float oneW = min1W[y];
                    float oneWDist = max1W[y] - min1W[y];
                    float dOneW = oneWDist / (float)lineLength;
                    //
                    float texX = minTex[y].X;
                    float texY = minTex[y].Y;
                    float texDistX = maxTex[y].X - minTex[y].X;
                    float texDistY = maxTex[y].Y - minTex[y].Y;
                    float dTexX = texDistX / lineLength;
                    float dTexY = texDistY / lineLength;
                    //
                    Int32* p;
                    p = (Int32*)f + ((int)y * Width) + minX[y];
                    int x = 0;
                    int lastBlockIndex = lineLength - (lineLength % VectorFCount);
                    for (x = minX[y]; x < minX[y]+lastBlockIndex; x+= VectorFCount) {
                        for (int b = 0; b < VectorFCount; b++) {
                            Arrtdimension[b] = tdimension;
                            Arrtdimensionint[b] = (int)tdimension;
                            Arrtdimensionminus1[b] = tdimension-1;
                            ArroneW[b] = oneW;
                            ArrtexX[b] = texX;
                            ArrtexY[b] = texY;
                            oneW += dOneW;
                            texX += dTexX;
                            texY += dTexY;
                        }

                        Tfloat = new Vector<float>(ArroneW);
                        VoneOneW = Vector<float>.One;
                        VoneOneW /= Tfloat;

                        Vtu = new Vector<float>(ArrtexX);
                        Vtu *= VoneOneW;

                        Vtv = new Vector<float>(ArrtexY);
                        Vtv *= VoneOneW;

                        VtuDimensionF = Vtu;
                        VtuDimensionF *= new Vector<float>(Arrtdimension);
                        VtuDimensionF.CopyTo(ArrTuDimensionF);

                        VtvDimensionF = Vtv;
                        VtvDimensionF *= new Vector<float>(Arrtdimension);
                        VtvDimensionF.CopyTo(ArrTvDimensionF);

                        for (int b = 0; b < VectorFCount; b++) {
                            ArrTuDimension[b] = (int)ArrTuDimensionF[b];
                            ArrTvDimension[b] = (int)ArrTvDimensionF[b];
                        }
                        VtuDimension = new Vector<int>(ArrTuDimension);
                        VtvDimension = new Vector<int>(ArrTvDimension);

                        Vu = VtuDimension;
                        Vu &= new Vector<int>(Arrtdimensionminus1);

                        Vv = VtvDimension;
                        Vv &= new Vector<int>(Arrtdimensionminus1);

                        Vpos = Vv;
                        Vpos *= new Vector<int>(Arrtdimensionint);

                        Vpos += Vu;

                        for (int b = 0; b < VectorFCount; b++) {
                            *p++ = *((Int32*)tbufferbyte + Vpos[b]);
                        }
                    }
                }
            }
        }
    }
}

The "optimized" one takes 3 times longer to run compared to original, what have i done wrong? This is my first attempt to use simd so maybe i have missed something important?

It takes 0.4 ms to render my scene with two polygons using the first function, and 1.7 ms with the second function.

UPDATE

I've verified the problem, it is as was said in the comments, loading back and forth from arrays caused the massive performance loss, especially stopping in the middle of the loop to unload two vectors into arrays in order to cast them to int only to reload them back in.

Switching to sse the code has now been rewritten using Sse42.ConvertToVector128Int32 And there is now a small improvement over the original naive function, another problem has however popped up. Sse will not let me multiply two int vectors together!

Sse Will only multiply float vectors, Sse2-Sse3 will do double, uint and float. Sse41-Sse42 However will do it, but only to return a vector128 long, with 2 values in it.

Its not an option either to use unit, because there is no function to convert float to uint.

Is SIMD not meant to be used with ints? Right now i have to do the last calculations int pos = (v * tdimension) + u; the bad way by pulling the data out of the vectors and this negates almost the entire gain. Does anyone have a solution?

UPDATE 2

Just an update for anyone finding this post with the same problem, starting in Sse41 you can use MultiplyLow to multiply two int vectors. Thanks to @Peter Cordes for the help

0 Answers
Related