Reputation: 95
How can a progress bar be added to the third loop shown below?
Experimental evidence has shown that of the following three loops the third is fastest. The best performance is when the thread count is the same as the logical processors of the CPU. I think this is due to reduced time spent allocating and releasing thread resources.
This is code to generate a noise map. Sometimes the processing time is long enough that a progress bar is needed.
for (int j = 0; j < data.Length; j++)
{
var x = (location.X + (j % width));
var y = (location.Y + (j / width));
Vector3 p = new Vector3(x, y, frame);
p *= zoom;
float val = noise.GetNoise(p);
data[j] += val;
min = Math.Min(min, val);
max = Math.Max(max, val);
}
Parallel.For(0, data.Length, (i) => {
var x = (location.X + (i % width));
var y = (location.Y + (i / width));
Vector3 p = new Vector3(x, y, frame);
p *= zoom;
float val = noise.GetNoise(p);
data[i] += val;
min = Math.Min(min, val);
max = Math.Max(max, val);
});
Parallel.For(0, threads, (i) =>
{
int from = i * data.Length / threads;
int to = from + data.Length / threads;
if (i == threads - 1) to = data.Length - 1;
for (int j = from; j < to; j++)
{
var x = (location.X + (j % width));
var y = (location.Y + (j / width));
Vector3 p = new Vector3(x, y, frame);
p *= zoom;
float val = noise.GetNoise(p);
data[j] += val;
min = Math.Min(min, val);
max = Math.Max(max, val);
}
}
);
A progress bar that has a limited update rate to a few times a second so as to not waist time drawing a progress bar too often would be the best.
Adding IProgress I have arrived here and this almost works. The problem is that the progress bar updates after the parallel.for has completely finished.
private async Task<int> FillDataParallelAsync(int threads, IProgress<int> progress)
{
int precent = 0;
/// parallel loop - easy and fast.
Parallel.For(0, threads, (i) =>
{
int from = i * data.Length / threads;
int to = from + data.Length / threads;
if (i == threads - 1) to = data.Length - 1;
for (int j = from; j < to; j++)
{
var x = (location.X + (j % width));
var y = (location.Y + (j / width));
Vector3 p = new Vector3(x, y, frame);
p *= zoom;
float val = noise.GetNoise(p);
data[j] += val;
min = Math.Min(min, val);
max = Math.Max(max, val);
if(j%(data.Length / 100) ==0)
{
if (progress != null)
{
progress.Report(precent);
}
Interlocked.Increment(ref precent);
}
}
}
);
return 0;
}
After working on this for too long it looks like this now.
private Boolean FillDataParallel3D(int threads, CancellationToken token, IProgress<int> progress)
{
int precent = 0;
Vector3 imageCenter = location;
imageCenter.X -= width / 2;
imageCenter.Y -= height / 2;
ParallelOptions options = new ParallelOptions { CancellationToken = token };
/// parallel loop - easy and fast.
try
{
ParallelLoopResult result =
Parallel.For(0, threads, options, (i, loopState) =>
{
int from = i * data.Length / threads;
int to = from + data.Length / threads;
if (i == threads - 1) to = data.Length - 1;
for (int j = from; j < to; j++)
{
if (loopState.ShouldExitCurrentIteration) break;
Vector3 p = imageCenter;
p.X += (j % width);
p.Y += (j / width);
p *= zoom;
float val = noise.GetNoise(p);
data[j] += val;
min = Math.Min(min, val);
max = Math.Max(max, val);
if (j % (data.Length / 100) == 0)
{
try { if (progress != null) progress.Report(precent); }
catch { }
Interlocked.Increment(ref precent);
}
}
}
);
return result.IsCompleted;
}
catch { }
return false;
}
The progress is incremented in part from each thread up to a total of 100 times. It still has some latency in updating the progress bar but it seems to be unavoidable. For example if the progress bar increments 100 times in less than the time to draw 100 updates the progress seems to que up and continues to progress after the method returns. Suppressing the display of progress for a second after calling the method works well enough. The progress bar is only really useful when the method takes so long that you wonder if anything is happening.
Full project at https://github.com/David-Marsh/Designer
Upvotes: 3
Views: 723
Reputation: 343
you may want to take a look at the IProgress at MSDN. IProgress was introduced as a standard way for displaying progress. This interface exposes a Report(T) method, which the async task calls to report progress. You expose this interface in the signature of the async method, and the caller must provide an object that implements this interface.
EDIT:
What the threads should report depends on how fine-grained you need your reporting. The easiest approach would be to report progress after each iteration. I'm intentionally writing iteration because the Parallel.For method does not necessarily execute each iteration on a separate thread.
Your progress, probably in percent, is something that is shared by all threads. So calculating current progress in percent and calling the Report method will most likely require locking. Be aware that this will have some performance impacts.
As for calculating the current progress, you know how many iterations you have. You can calculate how much one iteration is compared to the overall work. At the beginning or at the end of each iteration, you simply add the difference to the overall progress.
Here is an example that might helps you solve your problem:
public void ParallelForProgressExample(IProgress<int> progress = null)
{
int percent = 0;
...
var result = Parallel.For(fromInclusive, toExclusive, (i, state) =>
{
// do your work
lock (lockObject)
{
// caluclate percentage
// ...
// update progress
progress?.Report(percent);
}
});
}
As progress, you can either use the System.Progress class or implement the IProgress interface yourself.
Upvotes: 2