/// <summary>Executes a loop in chunks, using up to the given number of threads.</summary> /// <param name="maxParallelism">The maximum number of threads to use to execute the loop.</param> /// <include file="documentation.xml" path="/Utilities/Tasks/ParallelForChunked/*"/> public static void ParallelFor(int start, int endExclusive, Action <int, int, LoopThreadInfo> body, int maxParallelism) { if (start > endExclusive || maxParallelism <= 0) { throw new ArgumentOutOfRangeException(); } if (body == null) { throw new ArgumentNullException(); } uint iterations = (uint)(endExclusive - start), parallelism = Math.Min((uint)maxParallelism, iterations); if (iterations == 0) { return; } if (parallelism == 1) { body(start, endExclusive, new LoopThreadInfo(0)); } else { WorkItemTask[] tasks = new WorkItemTask[parallelism]; for (uint basicChunkSize = iterations / parallelism, errorInc = iterations % parallelism, error = 0, i = 0; i < (uint)tasks.Length; i++) { int threadNumber = (int)i, chunkStart = start; // make a copy of these so they can be properly captured by the closure uint chunkSize = basicChunkSize; // say we're dividing 100 iterations over 6 threads. then each chunk should be 16.666... iterations in length. since we // can't have a fractional iteration, we'll truncate the chunk size (to 16) and keep track of an error value. the error // will be incremented by the fractional part of the ideal chunk size (0.666...). if the error is greater than or equal // to 0.5, then we'll add one to the chunk size and subtract 1 from the error. since we don't want to use floating point // math, due to its inaccuracy, we'll do the same thing with integers. the error increment will be the integer remainder // from the calculation of the chunk size (so 100/6 leaves a remainder of 4). and instead of comparing against 0.5, we'll // compare double the error against the divisor (parallelism). this is the same as comparing the error against half the // divisor, except that it avoids truncation error. unfortunately, i can't really prove that the algorithm works for all // combinations of iterations and thread counts, but it seems to, and i'm showing my trust in it by not adding a special // case for the last chunk that simply takes up the remaining iterations. error += errorInc; if (error * 2 >= parallelism) { chunkSize++; error -= parallelism; } tasks[i] = new WorkItemTask(task => { LoopThreadInfo info = new LoopThreadInfo(threadNumber); body(chunkStart, chunkStart + (int)chunkSize, info); }); start += (int)chunkSize; } new CompositeTask(tasks).Run(); } }
/// <summary>Executes a loop using up to the given number of threads.</summary> /// <param name="maxParallelism">The maximum number of threads to use to execute the loop.</param> /// <include file="documentation.xml" path="/Utilities/Tasks/ParallelFor/*[not(@name='body')]"/> /// <include file="documentation.xml" path="/Utilities/Tasks/ParallelForWithInit/*"/> public static void ParallelFor <T>(int start, int endExclusive, Func <LoopThreadInfo, T> threadInitializer, Action <int, T, LoopThreadInfo> body, int maxParallelism) { if (start > endExclusive || maxParallelism <= 0) { throw new ArgumentOutOfRangeException(); } if (threadInitializer == null || body == null) { throw new ArgumentNullException(); } int iterations = endExclusive - start; if (iterations == 0) { return; } if (maxParallelism > iterations) { maxParallelism = iterations; } if (maxParallelism == 1) { LoopThreadInfo info = new LoopThreadInfo(0); T value = threadInitializer(info); for (; start < endExclusive; start++) { body(start, value, info); } } else { WorkItemTask[] tasks = new WorkItemTask[maxParallelism]; for (int i = 0; i < tasks.Length; i++) { int threadNumber = i; tasks[i] = new WorkItemTask(task => { LoopThreadInfo info = new LoopThreadInfo(threadNumber); T value = threadInitializer(info); // get an available index int currentIndex; while (true) { do { currentIndex = start; if (currentIndex == endExclusive) { goto done; } } while(Interlocked.CompareExchange(ref start, currentIndex + 1, currentIndex) != currentIndex); body(currentIndex, value, info); } done:; }); } new CompositeTask(tasks).Run(); } }