I recently saw some interesting performance characteristics when dealing with List. I had 300 methods queued to the thread pool. Each worker item used a local copy of List for some processing. At first, I cloned this list for each worker thread. Then I realized that since the data were read-only, I decided to just pass a reference to the original list. The logic was that the cloning operation was potentially expensive depending on the size of the list(in this case, 22000 items long), and that by merely passing a reference I wouldn’t need to clone the list any more.
So I changed the code, and performance plummeted. I used my high performance timer object to track certain blocks of code, and sure enough, removing the cloning operation sped that portion up considerably. But the elapsed time for each worker thread execution increased by an order of magnitude.
My guess is that internally the list serializes access to its contents, which caused an indeterminate number of worker threads to block, over and over again.
I’ll demonstrate through some sample code. The first bit is the code that clones the original list:
List universe = AcquireRecordIds(); //this returns a list of approximately 22,000 integers
//fire off each bucket into the thread pool; approximately 300 buckets
foreach( Bucket in buckets )
{
//clone the list for each bucket
List bucketUniverse = new List( universe );
//send off the bucket to the thread pool
EnqeueuBucketWorkerItem( bucketUniverse , bucket );
} //END bucket loop
In this case of of this bit of pseudocode, the cost of cloning the list was ~40 milliseconds on my laptop. Total execution time for all 300 worker items was ~100 milliseconds.
Now here’s some sample pseudocode that just used an object reference for the list
List universe = AcquireRecordIds(); //this returns a list of approximately 22,000 integers
//fire off each bucket into the thread pool; approximately 300 buckets
foreach( Bucket in buckets )
{
//send off the bucket to the thread pool
EnqeueuBucketWorkerItem( universe , bucket );
} //END bucket loop
In this case, there was zero time for cloning because that operation was removed. However, total execution time for all 300 worker items approached 2000 milliseconds.
I have to assume this is because of internal locking on behalf of the List. In my scenario, I was simply reading data from the list(each bucket read different, but potentially overlapping portions) from it’s copy of the list. The more often the list reference was accessed, the more often threads would be blocked. Basically all I mean is that your mileage may vary, so to speak.
In summary, the cloning operation cost me ~40 milliseconds, but saved me ~1900 milliseconds when a bunch of background threads were processed.