Since there's curiosity on how .Net 4 works with this in comments, here's that approach, sorry it's likely not an option for the OP. Disclaimer: This is not a highly scientific analysis, just showing that there's a clear performance benefit, based on hardware your mileage may very widely.
Here's a quick test (if you see a big mistake in this simple test, it's just an example, please comment and we can fix it to be more useful/accurate). For this, I just dropped 12,000 ~60kb files into a directory as a sample (fire up Linqpad you can play with it yourself, for free! - be sure to get LinqPad 4 though):
var files =
Directory.GetFiles("C:\\temp", "*.*", SearchOption.AllDirectories).ToList();
var sw = Stopwatch.StartNew(); //start timer
files.ForEach(f => File.ReadAllBytes(f).GetHashCode()); //do work - serial
sw.Stop(); //stop
sw.ElapsedMilliseconds.Dump("Run MS - Serial"); //display the duration
sw.Restart();
files.AsParallel().ForAll(f => File.ReadAllBytes(f).GetHashCode()); //parallel
sw.Stop();
sw.ElapsedMilliseconds.Dump("Run MS - Parallel");
Slightly changing you loop to parallelize the query is all that's needed in
most simple situations, by "simple" I mostly mean that the result of one action doesn't affect the next. Something to keep in mind most often is that some collections, for example our handy List<T>
is not thread safe, so using it in a parallel scenario isn't a good idea :) Luckily there were concurrent collections added in .Net 4 that are thread safe. Also keep in mind if you're using a locking collection, this may be a bottleneck as well, depending on the situation.
This uses the .AsParallel<T>(IEnumeable<T>)
and .ForAll<T>(ParallelQuery<T>)
extensions available in 4.0. The .AsParallel()
call wraps the IEnumerable<T>
in a ParallelEnumerableWrapper<T>
(internal class) which implements ParallelQuery<T>
. This now allows you to use the parallel extension methods, in this case we're using .ForAll()
.
.ForAll()
internally crates a ForAllOperator<T>(query, action)
and runs it synchronously. This handles the threading and merging of the threads after it's running...there's quite a bit going on in there, I'd suggest starting here if you want to learn more, including additional options.
The results (Computer 1 - Physical Hard Disk):
- Serial: 1288 - 1333ms
- Parallel: 461 - 503ms
Computer specs - for comparison:
The results (Computer 2 - Solid State Drive):
- Serial: 545 - 601ms
- Parallel: 248 - 278ms
Computer specs - for comparison:
- Quad Core 2 Quad Q9100 @ 2.26GHz
- 8GB RAM (DDR 1333)
- 120GB OCZ Vertex SSD (Standard Version - 1.4 Firmware)
I don't have links for the CPU/RAM this time, these came installed, this is a Dell M6400 Laptop (here's a link to the M6500...dell's own links to the 6400 are broken)
These numbers are from 10 runs, taking the min/max of the inner 8 results (removing the original min/max for each as possible outliers). We hit a I/O bottleneck here, especially on the physical drive, but think about what the serial method does, it reads, processes, reads, processes, rinse repeat. With the parallel approach, you are (even with a I/O bottleneck) reading and processing simultaneously, in the worst bottleneck situation, you're processing one file while reading the next, that alone (on any current computer!) should result in some performance gain. You can see that we can get a bit more than 1 going at a time in the results above, giving us a healthy boost.
Another disclaimer: Quad core + .Net 4 parallel isn't going to give you 4x performance, it doesn't scale linearly...there are other considerations and bottlenecks in play.
Hope this was on interest in showing the approach and possible benefits, feel free to criticize or improve...this answer exists solely for those curious as indicated in comments :)