When operating on large data sets in .Net, there is frequently a need to cancel the operations.
We have already determined the steady-state performance considerations of basic loop structures, but adding a cancellation check may change that.
As determined before, Arrays are generally the faster structure except when using .Net's MoveNext()
.
for
loops are the fastest, followed closely by foreach
.
This quick study hopes to see if there is any change in which to select based on the use of a cancellation token.
Let's try a few approaches to implementation that will demonstrate the differences:
/// <summary>
/// Runs a test of each Loop given a number of items
/// </summary>
/// <param name="numberOfItems">The number of items</param>
/// <returns>Returns an array of results</returns>
public static long[] Run(int numberOfItems)
{
var ret = new long[6];
var sw = new System.Diagnostics.Stopwatch();
var testList = MakeTestList(numberOfItems);
var token = default(CancellationToken);
// for loop on List
var current = 0;
sw.Start();
for (int i = 0; !token.IsCancellationRequested && i < numberOfItems; i++)
{
current += testList[i];
}
sw.Stop();
ret[0] = sw.Elapsed.Ticks;
// foreach loop on List
current = 0;
sw.Restart();
foreach (var val in testList)
{
if (token.IsCancellationRequested) break;
current += val;
}
sw.Stop();
ret[1] = sw.Elapsed.Ticks;
// enumerator MoveNext on List
current = 0;
sw.Restart();
var enumerator = testList.GetEnumerator();
while (!token.IsCancellationRequested && enumerator.MoveNext())
{
current += enumerator.Current;
}
sw.Stop();
ret[2] = sw.Elapsed.Ticks;
var testArray = MakeTestArray(numberOfItems);
current = 0;
// for loop on Array
sw.Restart();
for (int i = 0; !token.IsCancellationRequested && i < numberOfItems; i++)
{
current += testArray[i];
}
sw.Stop();
ret[3] = sw.Elapsed.Ticks;
current = 0;
// foreach loop on Array
sw.Restart();
foreach (var val in testArray)
{
if (token.IsCancellationRequested) break;
current += val;
}
sw.Stop();
ret[4] = sw.Elapsed.Ticks;
// enumerator MoveNext on Array
current = 0;
sw.Restart();
var aenumerator = testArray.GetEnumerator();
while (!token.IsCancellationRequested && aenumerator.MoveNext())
{
current += (int)aenumerator.Current;
}
sw.Stop();
ret[5] = sw.Elapsed.Ticks;
return ret;
}
Here is the average of 30 runs (all times in Ticks):
Average Time (in ticks) to access N number of items
items | for (List) | foreach (List) | enumerator (List) | for (Array) | foreach (Array) | enumerator (Array) |
---|---|---|---|---|---|---|
1 | 2.03361344537815 | 3.15966386554621 | 1.66666666666666 | 1.6 | 1.36666666666666 | 8.45378151260504 |
2 | 2.23333333333333 | 3.85 | 1.94166666666666 | 1.96666666666666 | 1.73333333333333 | 7.06666666666666 |
4 | 2.00833333333333 | 3.99166666666666 | 2.41666666666666 | 2.2 | 1.43333333333333 | 8.025 |
8 | 2.83333333333333 | 4.59166666666666 | 2.57627118644067 | 2.34166666666666 | 1.83333333333333 | 13.2083333333333 |
16 | 3.75 | 5.74166666666666 | 3.46666666666666 | 2.56666666666666 | 2.63333333333333 | 21.8547008547008 |
32 | 5.81666666666666 | 8.56666666666666 | 7.225 | 4.74166666666666 | 4.83333333333333 | 51.798319327731 |
64 | 9.54166666666666 | 12.575 | 10.7333333333333 | 6.98333333333333 | 7.28333333333333 | 73.054054054054 |
128 | 13.8083333333333 | 19.8916666666666 | 18.9166666666666 | 10.5916666666666 | 11.3333333333333 | 131.552631578947 |
256 | 25.1826086956521 | 36.6333333333333 | 37.1333333333333 | 21.3 | 22.5 | 267.306306306306 |
512 | 49.895652173913 | 65.099099099099 | 68.6271186440677 | 38.2068965517241 | 42.0086206896551 | 586.798319327731 |
1024 | 97.6521739130434 | 130.149122807017 | 135.701754385964 | 75.5 | 82.9652173913043 | 1082.64912280701 |
2048 | 196.310344827586 | 257.173913043478 | 271.163793103448 | 150.700854700854 | 166.871794871794 | 2137.36936936936 |
4096 | 392.625 | 507.410714285714 | 540.9375 | 296.6 | 334.060344827586 | 4226.71028037383 |
8192 | 792.304347826087 | 1023.66371681415 | 1087.60526315789 | 602.438596491228 | 661.859649122807 | 8677.02970297029 |
16384 | 1533.7610619469 | 2037.64347826086 | 2184.00869565217 | 1194.5625 | 1327.34513274336 | 18045.4594594594 |
32768 | 3134.68965517241 | 4098.40517241379 | 4372.20175438596 | 2411.18018018018 | 2658.22123893805 | 35156.2432432432 |
65536 | 6259.08620689655 | 8147.83478260869 | 8693.79646017699 | 4785.67272727272 | 5292.78181818181 | 69850.7663551402 |
131072 | 12254.4385964912 | 16153.4545454545 | 17381.8 | 9782.63063063063 | 10798.0982142857 | 142121.089285714 |
262144 | 25424.6902654867 | 33068.6666666666 | 35065.9082568807 | 19155.1666666666 | 21315.0091743119 | 288966.622807017 |
524288 | 50529.0982142857 | 65797.5575221239 | 70636.6194690265 | 39647.0683760683 | 43569.3063063063 | 577622.45045045 |
1048576 | 101330.371681415 | 131997.855855855 | 140286.254716981 | 77389.3008849557 | 85306.7207207207 | 1148081.91452991 |
2097152 | 204322.308411214 | 267888.166666666 | 281648.897196261 | 155159.216216216 | 173218.116071428 | 2301453.94642857 |
Now, normalize the times to ticks per item.
Normalized Average Time (with respect to ticks / item)
items | for (List) | foreach (List) | enumerator (List) | for (Array) | foreach (Array) | enumerator (Array) |
---|---|---|---|---|---|---|
4 | 0.508403361344537 | 0.789915966386552 | 0.416666666666665 | 0.4 | 0.341666666666665 | 2.11344537815126 |
8 | 0.279166666666666 | 0.48125 | 0.242708333333332 | 0.245833333333332 | 0.216666666666666 | 0.883333333333333 |
16 | 0.125520833333333 | 0.249479166666666 | 0.151041666666666 | 0.1375 | 0.0895833333333331 | 0.5015625 |
32 | 0.0885416666666666 | 0.143489583333333 | 0.0805084745762709 | 0.0731770833333331 | 0.0572916666666666 | 0.412760416666666 |
64 | 0.05859375 | 0.0897135416666666 | 0.0541666666666666 | 0.0401041666666666 | 0.0411458333333333 | 0.3414797008547 |
128 | 0.0454427083333333 | 0.0669270833333333 | 0.0564453125 | 0.0370442708333333 | 0.0377604166666666 | 0.404674369747898 |
256 | 0.0372721354166666 | 0.04912109375 | 0.0419270833333332 | 0.0272786458333333 | 0.0284505208333333 | 0.285367398648648 |
512 | 0.0269694010416666 | 0.0388509114583332 | 0.0369466145833332 | 0.0206868489583332 | 0.0221354166666666 | 0.256938733552631 |
1024 | 0.0245923913043478 | 0.0357747395833333 | 0.0362630208333333 | 0.02080078125 | 0.02197265625 | 0.261041314752252 |
2048 | 0.0243631114130435 | 0.0317866694819819 | 0.0335093352754237 | 0.0186557112068965 | 0.0205120218211207 | 0.286522616859244 |
4096 | 0.0238408627717391 | 0.0317746881853069 | 0.0331303111293857 | 0.0184326171875 | 0.0202551800271739 | 0.264318633497805 |
8192 | 0.0239636651400862 | 0.0313932999320652 | 0.0331010489628232 | 0.0183961004273503 | 0.0203700921474358 | 0.260909346846846 |
16384 | 0.0239639282226562 | 0.0309698922293527 | 0.0330162048339844 | 0.01810302734375 | 0.0203894253434806 | 0.257977922386098 |
32768 | 0.0241792098335598 | 0.0312397374516037 | 0.0331910785875821 | 0.0183849669339364 | 0.0201983535498904 | 0.264801931853341 |
65536 | 0.0234033365165237 | 0.0310919720193613 | 0.0333253279976222 | 0.0182275772094727 | 0.020253679393667 | 0.275351859427786 |
131072 | 0.0239157841123383 | 0.0312683500092605 | 0.0333572521544339 | 0.0183958448805251 | 0.0202806185832066 | 0.268220849939294 |
262144 | 0.0238765190387594 | 0.0310815230659816 | 0.0331642015845375 | 0.0182558926669034 | 0.0201903603293679 | 0.266459527416764 |
524288 | 0.0233734867029022 | 0.0308102694424715 | 0.0331531524658203 | 0.018658887158643 | 0.0205957378659929 | 0.27107446534293 |
1048576 | 0.0242468741087787 | 0.0315367380777994 | 0.033441456086045 | 0.0182677904764811 | 0.0203275768035048 | 0.275580046469705 |
2097152 | 0.0240941515990666 | 0.0313747203455562 | 0.0336821648926861 | 0.0189051954155294 | 0.0207754642039806 | 0.275431847787118 |
4194304 | 0.0241590432361162 | 0.0314707412376058 | 0.0334468495171025 | 0.0184510471546544 | 0.020338707142048 | 0.273724058754423 |
8388608 | 0.0243571172250764 | 0.0319347580273945 | 0.0335751649375273 | 0.0184964199323912 | 0.0206492085541997 | 0.274354689887592 |
Show the results on a log2 - log2 plot...
At first, we see that adding the cancellation check has also added ~20 nanoseconds (there are 100 nanoseconds / tick).
As expected, for
and foreach
loops on Arrays are fastest.
Using the enumerator's MoveNext()
on the List is slower than calling foreach
, but not by much - and this gap is narrower then when not checking for a cancellation.
The enumerator with MoveNext()
as it is always slower.
The Array enumerator is still the slowest.
In conclusion, adding a cancellation check does not change the selection of a loop type.
It is still recommended to use for
loops, then foreach
loops, then enumerators MoveNext()
.