4

I have the following code in which I compare the performance of the ArrayList and List through adding 10000000 integer numbers. In ArrayList I have boxing and unboxing, so in the first loop the time of adding integer numbers is much greater than in List but the time of assignig later the integer number to elements form list and arrayList is similar even though in arrayLIst I have to do unboxing. Why? Is it because boxing and unboxing of integers in ArrayList occurs before second loop?

int numbers = 10000000;
ArrayList integerArrayList = new ArrayList();
Stopwatch timer = new Stopwatch();
timer.Start();

for (int i = 0; i < numbers; i++)
{
    integerArrayList.Add(i); //niejawna konwersja na object
}
timer.Stop();
var timeTestingIntegerArrayList = timer.ElapsedMilliseconds;
Console.WriteLine($"The time for adding {numbers} numbers to Arraylist is: {timeTestingIntegerArrayList} ms");
timer.Reset();

timer.Start();

for (int i = 0; i < numbers; i++)
{

    int number = (int)integerArrayList[i]; //unboxing
}
timer.Stop();
var arrayListWriteTime = timer.ElapsedMilliseconds;
Console.WriteLine($"The time for writting {numbers} numbers from Arraylist is: {arrayListWriteTime} ms");
timer.Reset();

Console.WriteLine($"The whole time for adding and writting {numbers} numbers from Arraylist is: {arrayListWriteTime + timeTestingIntegerArrayList} ms");` 

int numbers = 10000000;

List<int> integerList = new List<int>();
Stopwatch timer = new Stopwatch();

timer.Start();
for (int i = 0; i < numbers; i++)
{
    integerList.Add(i);
}
timer.Stop();   
var timeTestingIntegerList = timer.ElapsedMilliseconds;
Console.WriteLine($"The time for adding {numbers} numbers to list is: {timeTestingIntegerList} ms");
timer.Reset();

timer.Start();
for (var i = 0; i < numbers; i++)
{

    int number = integerList[i];

}
timer.Stop();
var listWriteTime = timer.ElapsedMilliseconds;
Console.WriteLine($"The time for assigning {numbers} numbers from list is: {listWriteTime} ms");
timer.Reset();


Console.WriteLine($"The whole time for adding and writting the integer numbers is: {timeTestingIntegerList + listWriteTime}");
NightOwl888
  • 55,572
  • 24
  • 139
  • 212
ElConrado
  • 1,477
  • 4
  • 20
  • 46
  • 1
    The arraylist read loop does have to unbox, but unboxing is much faster than boxing: https://stackoverflow.com/questions/26687544/why-unboxing-is-100-time-faster-than-boxing – Alex K. Feb 10 '18 at 14:26
  • You may want to actually _add_ the benchmark results you got. – Nyerguds Feb 10 '18 at 14:42
  • @Nyerguds The actual results are: adding integers to list - 140ms assigning the numbers from list - 56ms, adding integers to ArrayList - 1454 ms, assigning - 69ms. So it means that assigning the numbers in list and arrayList is very similar even though in arrayList is unboxing and in list there is no unboxing (we take values directly form stack) – ElConrado Feb 10 '18 at 14:57
  • Code supplied doesn't compile. – Stephen Kennedy Feb 10 '18 at 15:50

2 Answers2

4

Your program generates similar output

The time for adding 10000000 numbers to Arraylist is: 1824 ms
The time for writting 10000000 numbers from Arraylist is: 100 ms
The whole time for adding and writting 10000000 numbers from Arraylist is: 1924 ms
The time for adding 10000000 numbers to list is: 111 ms
The time for assigning 10000000 numbers from list is: 77 ms
The whole time for adding and writting the integer numbers is: 188

but the time of assignig later the integer number to elements form list and arrayList is similar even though in arrayLIst I have to do unboxing

No,list assignment is comparatively faster than arraylist assignment. If you compare the IL generated code ,the difference between ArrayList and List assignment is just a single instruction almost.

ArrayList

L_0083: ldloc.s num8
    L_0085: callvirt instance object [mscorlib]System.Collections.ArrayList::get_Item(int32)
    L_008a: unbox.any int32
    L_008f: stloc.s num9
    L_0091: nop 
    L_0092: ldloc.s num8
    L_0094: stloc.s num7
    L_0096: ldloc.s num7
    L_0098: ldc.i4.1 
    L_0099: add 
    L_009a: stloc.s num8
    L_009c: ldloc.s num8
    L_009e: ldloc.0 

List

L_017d: callvirt instance !0 [mscorlib]System.Collections.Generic.List`1<int32>::get_Item(int32)
L_0182: stloc.s num12
L_0184: nop 
L_0185: ldloc.s num11
L_0187: stloc.s num7
L_0189: ldloc.s num7
L_018b: ldc.i4.1 
L_018c: add 
L_018d: stloc.s num11
L_018f: ldloc.s num11
L_0191: ldloc.0 

L_008a: unbox.any int32 is the only single instruction that occurs in array list as a result there is a few extra clock ticks in Array List compared to compare to normal list. Therefore as expected arraylist is a little slow compared to list due to unbox,but however unboxing is more more faster compared to boxing.

Is it because boxing and unboxing of integers in ArrayList occurs before second loop?

NO!!.Both the loops are independent of each other ,no shared variable nor instances will result in your assumption.

Hameed Syed
  • 3,939
  • 2
  • 21
  • 31
2

The StopWatch class is definitely a good choice for quick and dirty profiling of some code. You can use it when there's a large difference between two different sections or to get a general feel of how two pieces of code compare to each other. However, if you want to get a truly accurate comparison, you need to use an actual benchmarking setup. The main difference is that a benchmark program will execute the test many times in a row instead of just a few runs manually. It will then average the results together to smooth out noise that occur during individual runs for any number of reasons.

A common choice in .NET is to make use of BenchmarkDotNet. It is the one I went with when setting up my benchmarks and you can find my full code here. Hopefully when setting this up I haven't made a glaring error. :)

Methodology

That said, my methodology is simple enough. I create three different collections. An int[], an ArrayList, and a List<int>. I added the int[] to serve as a baseline for all of the comparisons since it theoretically should be the fastest.

I then ran two sets of benchmarks against each: retrieving the first item from all three collections, and retrieving all of the items for the collections. I added the single retrieve to try and get a feel for roughly how long the unboxing takes.

Results

You can view my results here.

Summary

LoopOverArrayList: 27,418,009.3179 ns

LoopOverList: 6,741,538.0712 ns

SingleArrayListAccess: 2.5473 ns

SingleListAccess: 1.2240 ns

From the results, we can see that unboxing does take extra time than accessing straight from the List<T>. However, it's not like it's slow either. So your results are most likely just from some noise that was getting mixed in with your individual runs that is getting smoothed out through the multiple runs in the benchmark. I encourage you to execute the benchmark above yourself and see if you get similar results. Most likely they will fall within a similar range.

So, yes the unboxing occurs on every ArrayList access and it does add up but not massively so.

Community
  • 1
  • 1
Scott Baldric
  • 465
  • 3
  • 10