2

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();
        }
    }
}
6
  • 1
    Your code example is awfully abstract. What is it intended to accomplish, exactly? Commented Jun 11, 2014 at 4:20
  • 1
    How many cores do you have? If you have a CPU intensive application, then you should have no more CPU-intensive threads than you have processor cores. Otherwise you're wasting a lot of time in thread context switches. 100% CPU isn't necessarily bad, so long as those cycles are used primarily for processing, and your user interface isn't lagging. Commented Jun 11, 2014 at 4:27
  • 1
    You might want to look in to TPL Dataflow instead of using Blocking Collections. It will auto throttle better. Commented Jun 11, 2014 at 4:29
  • My main question here, is if using ThreadPool vs Creating a Thread the correct aproach in a architectural meaning mostly. I can give you a context though, this class is called by a "another" thread that is background loader, so when I need to load a piece of data it tries to get a builder instance ,and generate it. This is actually being used in a voxel engine, but since its out of scope and I am really concerned about the design / implementation side of things. My issue here is using threadpool like this. I asking if this is the way to go. Commented Jun 11, 2014 at 4:30
  • The "problem," if there is one, isn't with the above code, as all it does is create the threads in a roundabout way. If you want suggestions on optimizing your program, then you should post the code that's eating the CPU. Commented Jun 11, 2014 at 4:30

1 Answer 1

1

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.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.