简体   繁体   中英

C# Multithreading - Performance and architecture of system

So I am developing a C# Application, which is quite CPU intensive.

Currently I am using ThreadPool to process the tasks async, but this is proving to be not working as I expected.

Take this class I use this to retrive a Builder class to generate a chunk.

public class ChunkBuilderProvider
{
    private readonly BlockingCollection<ChunkBuilder> Builders;

    public ChunkBuilderProvider()
    {
        Builders = new BlockingCollection<ChunkBuilder>();
        for (int i = 0; i < Configs.BuilderMaxInstance; i++)
            Builders.Add(new ChunkBuilder());
    }

    public ChunkBuilder GetBuilder()
    {
        ChunkBuilder c;
        return Builders.TryTake(out c, -1) ? c : null;
    }

    public void ReplaceBuilder(ChunkBuilder c)
    {
        Builders.Add(c);
    }

    public int IdleBuilders()
    {
        return Builders.Count;
    }

    internal bool Build(Chunk c)
    {
        if (c.State == Chunk.ChunkState.Generating)
            return false;
        var b = GetBuilder();
        if (b == null)
            return false;
        ThreadPool.QueueUserWorkItem(a =>
        {
            b.Generate(c);
            ReplaceBuilder(b);
        });
        return true;
    }
}

The Generate task is VERY CPU intensive, running this with 5 builders, bursts my CPU to 100% usage.

Ants Shows me this :

[Seems like I can't post images here]

Edit: The CPU intensive code is this :

    using System;
using System.Collections.Generic;
using HyroVoxelEngine.Graphics.Primitives;
using HyroVoxelEngine.Voxels.Blocks;
using HyroVoxelEngine.Voxels.Chunks;
using SharpDX;


namespace HyroVoxelEngine.Voxels.Meshing
{
    public class GreedyMeshing
    {
        private static readonly int[][][] VerticesOffset = new int[6][][]
        {
//TOP
            new int[9][]
            {
                new int[3] {-1, 1, 1}, new int[3] {0, 1, 1}, new int[3] {1, 1, 1}, new int[3] {-1, 1, 0}, new int[3] {0, 1, 0}, new int[3] {1, 1, 0}, new int[3] {-1, 1, -1},
                new int[3] {0, 1, -1}, new int[3] {1, 1, -1}
            },
//North
            new int[9][]
            {
                new int[3] {-1, -1, 1}, new int[3] {0, -1, 1}, new int[3] {1, -1, 1}, new int[3] {-1, 0, 1}, new int[3] {0, 0, 1}, new int[3] {1, 0, 1}, new int[3] {-1, 1, 1},
                new int[3] {0, 1, 1}, new int[3] {1, 1, 1}
            },
//Bottom
            new int[9][]
            {
                new int[3] {-1, -1, -1}, new int[3] {0, -1, -1}, new int[3] {1, -1, -1}, new int[3] {-1, -1, 0}, new int[3] {0, -1, 0}, new int[3] {1, -1, 0}, new int[3] {-1, -1, 1},
                new int[3] {0, -1, 1}, new int[3] {1, -1, 1}
            },
//SOUTH
            new int[9][]
            {
                new int[3] {-1, 1, -1}, new int[3] {0, 1, -1}, new int[3] {1, 1, -1}, new int[3] {-1, 0, -1}, new int[3] {0, 0, -1}, new int[3] {1, 0, -1}, new int[3] {-1, -1, -1},
                new int[3] {0, -1, -1}, new int[3] {1, -1, -1}
            },
//West
            new int[9][]
            {
                new int[3] {1, 1, 1}, new int[3] {1, 0, 1}, new int[3] {1, -1, 1}, new int[3] {1, 1, 0}, new int[3] {1, 0, 0}, new int[3] {1, -1, 0}, new int[3] {1, 1, -1},
                new int[3] {1, 0, -1}, new int[3] {1, -1, -1}
            },
//East
            new int[9][]
            {
                new int[3] {-1, -1, 1}, new int[3] {-1, 0, 1}, new int[3] {-1, 1, 1}, new int[3] {-1, -1, 0}, new int[3] {-1, 0, 0}, new int[3] {-1, 1, 0}, new int[3] {-1, -1, -1},
                new int[3] {-1, 0, -1}, new int[3] {-1, 1, -1}
            }
        };

        private Block[][][] Blocks;
        private List<int> Index;
        private int VOXEL_SIZE = 1;
        private Chunk chunk;
        private List<VoxelVertex> vertices;
        public void SetChunk(Chunk c)
        {
            chunk = c;
            Blocks = c.Blocks;
        }
        public ChunkPrimitive Greedy()
        {
            Index = new List<int>(10000);
            vertices = new List<VoxelVertex>(8000);
            /*
         * These are just working variables for the algorithm - almost all taken 
         * directly from Mikola Lysenko's javascript implementation.
         */
            int i, j, k, l, w, h, u, v, n;
            var side = VoxelFace.Direction.None;
            int[] x = {0, 0, 0};
            int[] q = {0, 0, 0};
            int[] du = {0, 0, 0};
            int[] dv = {0, 0, 0};
            /*
         * We create a mask - this will contain the groups of matching voxel faces 
         * as we proceed through the chunk in 6 directions - once for each face.
         */
            VoxelFace voxelFace, voxelFace1;
            int[] Dimensions = {Chunk.SizeX, Chunk.SizeY, Chunk.SizeZ};
            VoxelFace[] mask;
            /**
         * We start with the lesser-spotted boolean for-loop (also known as the old flippy floppy). 
         * 
         * The variable backFace will be TRUE on the first iteration and FALSE on the second - this allows 
         * us to track which direction the indices should run during creation of the quad.
         * 
         * This loop runs twice, and the inner loop 3 times - totally 6 iterations - one for each 
         * voxel face.
         */
            for (bool backFace = true, b = false; b != backFace; backFace = backFace && b, b = !b)
            {
                /*
             * We sweep over the 3 dimensions - most of what follows is well described by Mikola Lysenko 
             * in his post - and is ported from his Javascript implementation.  Where this implementation 
             * diverges, I've added commentary.
             */
                for (int d = 0; d < 3; d++)
                {
                    /*
                 * These are just working variables to hold two faces during comparison.
                 */
                    u = (d + 1)%3;
                    v = (d + 2)%3;
                    x[0] = 0;
                    x[1] = 0;
                    x[2] = 0;
                    q[0] = 0;
                    q[1] = 0;
                    q[2] = 0;
                    q[d] = 1;
                    mask = new VoxelFace[Dimensions[u]*Dimensions[v]];
                    /*
                 * Here we're keeping track of the side that we're meshing.
                 */
                    if (d == 0)
                        side = backFace ? VoxelFace.Direction.West : VoxelFace.Direction.East;
                    else if (d == 1)
                        side = backFace ? VoxelFace.Direction.Bottom : VoxelFace.Direction.Top;
                    else if (d == 2)
                        side = backFace ? VoxelFace.Direction.South : VoxelFace.Direction.North;
                    /*
                 * We move through the dimension from front to back
                 */
                    for (x[d] = -1; x[d] < Dimensions[d];)
                    {
                        n = 0;
                        for (x[v] = 0; x[v] < Dimensions[v]; x[v]++)
                        {
                            for (x[u] = 0; x[u] < Dimensions[u]; x[u]++)
                            {
                                /*
                             * Here we retrieve two voxel faces for comparison.
                             */
                                voxelFace = (x[d] >= 0) ? getVoxelFace(x[0], x[1], x[2], side) : null;
                                voxelFace1 = (x[d] < Dimensions[d] - 1) ? getVoxelFace(x[0] + q[0], x[1] + q[1], x[2] + q[2], side) : null;
                                mask[n++] = ((voxelFace != null && voxelFace1 != null && voxelFace.Equals(voxelFace1))) ? null : backFace ? voxelFace1 : voxelFace;
                            }
                        }
                        x[d]++;
                        /*
                     * Now we generate the mesh for the mask
                     */
                        n = 0;
                        for (j = 0; j < Dimensions[v]; j++)
                        {
                            for (i = 0; i < Dimensions[u];)
                            {
                                if (mask[n] != null)
                                {
                                    /*
                                 * We compute the width
                                 */
                                    for (w = 1; i + w < Dimensions[u] && mask[n + w] != null && mask[n + w].Equals(mask[n]); w++) {}
                                    /*
                                 * Then we compute height
                                 */
                                    bool done = false;
                                    for (h = 1; j + h < Dimensions[v]; h++)
                                    {
                                        for (k = 0; k < w; k++)
                                        {
                                            if (mask[n + k + h*Dimensions[u]] == null || !mask[n + k + h*Dimensions[u]].Equals(mask[n]))
                                            {
                                                done = true;
                                                break;
                                            }
                                        }
                                        if (done)
                                            break;
                                    }
                                    /*
                                 * Here we check the "transparent" attribute in the VoxelFace class to ensure that we don't mesh 
                                 * any culled faces.
                                 */
                                    if (!mask[n].Transparent)
                                    {
                                        /*
                                     * Add quad
                                     */
                                        x[u] = i;
                                        x[v] = j;
                                        du[0] = 0;
                                        du[1] = 0;
                                        du[2] = 0;
                                        du[u] = w;
                                        dv[0] = 0;
                                        dv[1] = 0;
                                        dv[2] = 0;
                                        dv[v] = h;
                                        /*
                                     * And here we call the quad function in order to render a merged quad in the scene.
                                     * 
                                     * We pass mask[n] to the function, which is an instance of the VoxelFace class containing 
                                     * all the attributes of the face - which allows for variables to be passed to shaders - for 
                                     * example lighting values used to create ambient occlusion.
                                     */
                                        Quad(new Vector3(x[0], x[1], x[2]), new Vector3(x[0] + du[0], x[1] + du[1], x[2] + du[2]),
                                            new Vector3(x[0] + du[0] + dv[0], x[1] + du[1] + dv[1], x[2] + du[2] + dv[2]), new Vector3(x[0] + dv[0], x[1] + dv[1], x[2] + dv[2]), w, h,
                                            mask[n], backFace);
                                    }
                                    /*
                                 * We zero out the mask
                                 */
                                    for (l = 0; l < h; ++l)
                                    {
                                        for (k = 0; k < w; ++k)
                                            mask[n + k + l*Dimensions[u]] = null;
                                    }
                                    /*
                                 * And then finally increment the counters and continue
                                 */
                                    i += w;
                                    n += w;
                                }
                                else
                                {
                                    i++;
                                    n++;
                                }
                            }
                        }
                    }
                }
            }
            if (vertices.Count == 0 || Index.Count == 0)
                return null;
            return new ChunkPrimitive(vertices.ToArray(), Index.ToArray());
        }
        private VoxelFace getVoxelFace(int x, int y, int z, VoxelFace.Direction side)
        {
            VoxelFace voxelFace = new VoxelFace(side);
            voxelFace.Type = Blocks[x][y][z].Type;
            voxelFace.Light = chunk.LightValue[x][y][z];
            voxelFace.Side = side;
            voxelFace.LightSettings = CountSolidCorner(voxelFace, x, y, z);
            return voxelFace;
        }

        private int[] CountSolidCorner(VoxelFace voxelFace, int x, int y, int z)
        {
            var side = voxelFace.Side;
            int bottomLeft = 0;
            int bottomRight = 0;
            int TopLeft = 0;
            int TopRight = 0;
            var pos = new Vector3(x, y, z);

            #region TOP BOTOM

            //SOUTH = -z
            //NORTH = +z
            //West = -X
            //est = X;
            int[][] vertOff = VerticesOffset[(int) side];
            if (GetBlockSolid(vertOff[6], x, y, z))
                bottomLeft++;
            if (GetBlockSolid(vertOff[8], x, y, z))
                bottomRight++;
            if (GetBlockSolid(vertOff[2], x, y, z))
                TopRight++;
            if (GetBlockSolid(vertOff[0], x, y, z))
                TopLeft++;
            if (GetBlockSolid(vertOff[1], x, y, z))
            {
                TopLeft++;
                TopRight++;
            }
            if (GetBlockSolid(vertOff[7], x, y, z))
            {
                bottomLeft++;
                bottomRight++;
            }
            if (GetBlockSolid(vertOff[3], x, y, z))
            {
                TopLeft++;
                bottomLeft++;
            }
            if (GetBlockSolid(vertOff[5], x, y, z))
            {
                TopRight++;
                bottomRight++;
            }

            if (side == VoxelFace.Direction.Bottom)
                return new[] {TopLeft, TopRight, bottomLeft, bottomRight};
            if (side == VoxelFace.Direction.Top)
                return new[] {bottomLeft, bottomRight, TopLeft, TopRight};
            if (side == VoxelFace.Direction.West)
                return new[] {bottomLeft, TopLeft, bottomRight, TopRight};
            if (side == VoxelFace.Direction.East)
                return new[] {bottomRight, TopRight, bottomLeft, TopLeft};
            if (side == VoxelFace.Direction.North)
                return new[] {TopLeft, bottomLeft, TopRight, bottomRight};

            #endregion

            //COM x Positivo
            //TOP -  TR - BR - TL - BL
            return new[] {4, 4, 4, 4};
        }

        private bool GetBlockSolid(int[] offset, int x, int y, int z)
        {
            x = x + offset[0];
            y = y + offset[1];
            z = z + offset[2];
            if (x  < 0 | y  < 0 | z < 0)
                return true;
            if (x  >= Chunk.SizeX | y  >= Chunk.SizeY | z  >= Chunk.SizeZ)
                return true;
            return !Block.IsSolidBlock(Blocks[x][y ][z ].Type);
        }
        private void Quad(Vector3 bottomLeft, Vector3 topLeft, Vector3 topRight, Vector3 bottomRight, int width, int height, VoxelFace voxel, bool backFace)
        {
            BlockTexture texture = new BlockTexture();
            Vector2[] UVList = new Vector2[4];
            var vert = new VoxelVertex[4];
            Vector3 normal = voxel.Normal;
            if (voxel.Side == VoxelFace.Direction.Top) {}
            //switch (voxel.Side)
            //{
            //    case VoxelFace.Direction.Bottom:
            //        normal = - Vector3.UnitY;
            //        texture = TextureHelper.GetTexture(voxel.Type, BlockFaceDirection.YDecreasing);
            //        UVList = TextureHelper.GetUVMapping((int) texture, BlockFaceDirection.YDecreasing);
            //        break;
            //    case VoxelFace.Direction.Top:
            //        normal = Vector3.UnitY;
            //        texture = TextureHelper.GetTexture(voxel.Type, BlockFaceDirection.YIncreasing);
            //        UVList = TextureHelper.GetUVMapping((int) texture, BlockFaceDirection.YIncreasing);
            //        break;
            //    case VoxelFace.Direction.West:
            //        normal = Vector3.UnitX;
            //        texture = TextureHelper.GetTexture(voxel.Type, BlockFaceDirection.XIncreasing);
            //        UVList = TextureHelper.GetUVMapping((int) texture, BlockFaceDirection.XIncreasing);
            //        break;
            //    case VoxelFace.Direction.East:
            //        normal = -Vector3.UnitX;
            //        texture = TextureHelper.GetTexture(voxel.Type, BlockFaceDirection.XDecreasing);
            //        UVList = TextureHelper.GetUVMapping((int) texture, BlockFaceDirection.XDecreasing);
            //        break;
            //    case VoxelFace.Direction.North:
            //        normal = -Vector3.UnitZ;
            //        texture = TextureHelper.GetTexture(voxel.Type, BlockFaceDirection.ZDecreasing);
            //        UVList = TextureHelper.GetUVMapping((int) texture, BlockFaceDirection.ZDecreasing);
            //        break;
            //    case VoxelFace.Direction.South:
            //        normal = Vector3.UnitZ;
            //        texture = TextureHelper.GetTexture(voxel.Type, BlockFaceDirection.ZIncreasing);
            //        UVList = TextureHelper.GetUVMapping((int) texture, BlockFaceDirection.ZIncreasing);
            //        break;
            //}
            int ic = (Index.Count/6)*4;
            int[] indexes = !backFace ? new[] {2, 0, 1, 1, 3, 2} : new[] {2, 3, 1, 1, 0, 2};
            vert[0] = new VoxelVertex(bottomLeft*(VOXEL_SIZE), normal, UVList[0], new Vector3(voxel.Light, voxel.LightSettings[0], (int) (voxel.Side)));
            vert[1] = new VoxelVertex(bottomRight*(VOXEL_SIZE), normal, UVList[1], new Vector3(voxel.Light, voxel.LightSettings[1], (int) (voxel.Side)));
            vert[2] = new VoxelVertex(topLeft*(VOXEL_SIZE), normal, UVList[2], new Vector3(voxel.Light, voxel.LightSettings[2], (int) (voxel.Side)));
            vert[3] = new VoxelVertex(topRight*(VOXEL_SIZE), normal, UVList[3], new Vector3(voxel.Light, voxel.LightSettings[3], (int) (voxel.Side)));
            if (voxel.LightSettings[0] + voxel.LightSettings[3] > voxel.LightSettings[1] + voxel.LightSettings[2])
                indexes = !backFace ? new[] {0, 1, 3, 3, 2, 0} : new[] {0, 2, 3, 3, 1, 0};

            //int[] indexes = !backFace ? new[] { 0, 3, 2, 2, 1, 0 } : new[] { 0, 1, 2, 2, 0, 2 };
            Index.Add(indexes[0] + ic);
            Index.Add(indexes[1] + ic);
            Index.Add(indexes[2] + ic);
            Index.Add(indexes[3] + ic);
            Index.Add(indexes[4] + ic);
            Index.Add(indexes[5] + ic);
            vertices.Add(vert[0]);
            vertices.Add(vert[1]);
            vertices.Add(vert[2]);
            vertices.Add(vert[3]);
        }

        internal void Dispose()
        {
            throw new NotImplementedException();
        }
    }
}

It is highly depends on how the ChunkBuilder is thead-safe itself. If it hasn't its own chunk-dependent state (and this is good design), you don't need the builders collection. And all you flow can looks as simple as:

using System.Threading.Tasks;

...

public IEnumerable<Chunk> GetChunks()
{
    // Here you can return the whole chunks collection or yield them one by one.
    yield return new Chunk();
}

public void DoIt()
{
    ChunkBuilder builder = new ChunkBuilder();
    ParallelOptions options = new ParallelOptions() {
        MaxDegreeOfParallelism = 4
    };
    Parallel.ForEach(this.GetChunks(), options, chunk => builder.Generate(chunk));
}

If you ChunkBuilder contains chunk processing state, then there are two choices:

1) If ChunkBuilder initialization is simple and fast, just create builder when processing required:

    Parallel.ForEach(this.GetChunks(), options, chunk => new ChunkBuilder().Generate(chunk));

2) If ChunkBuilder is slow to construct, its better to redesign according to SRP rules a little and extract chunk processing state into separate object.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM