Code Snippets for the ArrayList Analysis Post

This post contains the code referenced by the Boxing Performance in C# – Analysis and Benchmark article, the second in a 3-article set that discusses boxing, ArrayList and List<T>.

BoxingWithArrayList

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace BoxingWithArrayList
{

    class Program
    {
        static void Main(string[] args)
        {
            var summary = BenchmarkRunner.Run<Benchmarking>();            
        }
    }

    [DisassemblyDiagnoser(printAsm: true, printSource: true)]
    [MemoryDiagnoser]
    public class Benchmarking {
        public const int noNumbers = 10000000;  // 10 mil
        [Benchmark]
        public void ArrayListAddFunction() {
            ArrayList numbers = new ArrayList();
            Random random = new Random(1);  // use the same seed as to make
                                            //  benchmarking consistent

            for (int i = 0; i < noNumbers; i++)
            {    
                int currentNumber = random.Next(10);  // generate a non-negative
                                                      //  random number less than 10
                object o = currentNumber;  // BOXING occurs here

                numbers.Add(o); 
            }
        }

        [Benchmark]
        public void ListAddFunction()
        {
            List<int> numbers = new List<int>();
            Random random = new Random(1);  // use the same seed as to make
                                            //  benchmarking consistent

            for (int i = 0; i < noNumbers; i++)
            {
                int currentNumber = random.Next(10);  // generate a non-negative
                                                      //  random number less than 10

                numbers.Add(currentNumber);  // NO boxing here
            }
        }

    }
}

IndividualLoopBenchmark

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Running;

namespace IndividualLoopBenchmark
{
    [DisassemblyDiagnoser(printAsm: true, printSource: true)]
    public class UnderTest
    {
        private const int noNumbers = 10000000;   // 10 mil
        [Benchmark]
        [MethodImpl(MethodImplOptions.NoOptimization)]
        public void NonOptimizedLoop()
        {
            for (int i = 0; i < noNumbers; i++)
            {
            }

        }
        [Benchmark]
        public void OptimizedLoop()
        {
            for (int i = 0; i < noNumbers; i++)
            {
            }

        }

    }
    class Program
    {
        static void Main(string[] args)
        {
            var summary = BenchmarkRunner.Run<UnderTest>();
        }
    }
}

IndividualRandomNumberGeneratorBenchmark

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

namespace IndividualRandomNumberGeneratorBenchmark
{
    [MemoryDiagnoser]
    [DisassemblyDiagnoser(printAsm: true, printSource: true)]
    public class UnderTest
    {
        [Benchmark]
        public void GenerateRandomNumbers()
        {
            const int noNumbers = 10000000;  // 10 mil
            Random random = new Random(1);  // use the same seed as to make
                                            //  benchmarking consistent

            int currentNumber = 0;
            for (int i = 0; i < noNumbers; i++)
            {
                currentNumber = random.Next(10);  // generate a non-negative
                                                      //  random number less than 10
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var summary = BenchmarkRunner.Run<UnderTest>();
        }
    }
}

IndividualBoxingBenchmark

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections;
using BenchmarkDotNet.Running;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Engines;

namespace IndividualBoxingBenchmark
{
    [SimpleJob(launchCount: 1, warmupCount: 1, targetCount: 10)]
    [MemoryDiagnoser]
    [DisassemblyDiagnoser(printAsm: true, printSource: true)]
    public class UnderTest
    {
        [Benchmark]
        public void BoxObjects()
        {
            const int noNumbers = 10000000;  // 10 mil

            object o = null;
            for (int i = 0; i < noNumbers; i++)
            {
                o = i;  
            }
            int v = (int)o;
        }
    }

    public class Program
    {

        static void Main(string[] args)
        {
            var summary = BenchmarkRunner.Run<UnderTest>();
        }
    }
}

IndividualAddingToArrayListBenchmark

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Collections;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Configs;
using System.Threading;

namespace IndividualAddingToArrayListBenchmark
{
    [Config(typeof(Config))]
    [MemoryDiagnoser]
    [DisassemblyDiagnoser(printAsm: true, printSource: true)]
    public class UnderTest
    {
        private class Config : ManualConfig
        {
            // Force just one iteration, with 1 ops/iteration
            public Config()
            {
                Add(Job.Default.WithInvocationCount(1).WithUnrollFactor(1).WithIterationCount(20));
            }
        }
        
        public UnderTest()
        {
            // Build an array storing the powers of 2 that interest us in term of inner
            //  array length,  so no time is wasted within the benchmarked methods
            //  themselves for computing this
            
            // The largest array generated must be able to store our target number of numbers
            int LargestPowerOfTwoNeededToFit = (int)Math.Ceiling(Math.Log(noNumbers, 2));
            int[] powersOfTwo = new int[LargestPowerOfTwoNeededToFit-1];

            for(int i=2;i<=LargestPowerOfTwoNeededToFit;i++)
            {
                powersOfTwo[i - 2] = (int)Math.Pow(2, i);
                Console.WriteLine("{0}: {1}", i - 1, powersOfTwo[i - 2]);
            }
            
            PowersOfTwo = powersOfTwo;
            NumberOfArraysRequiredForNoNumbers = LargestPowerOfTwoNeededToFit - 1;

            Random random = new Random(1);
            
            // Force the gen buffer for LOH to be big, so we don't have GCs within
            //  the iterations, by allocating a large enough ArrayList instance
            //  at the class level. The GC time will be larger, of course, but we
            //  don't care for just the sample of allocating the ArrayLists, since
            //  the GCs occur in-between iterations. It'll serve a double purpose:
            //  a source for the copying-to-arrays benchmark
            masterSourceArray = new object[2 * noNumbers];


            for (int i = 0; i < 2 * noNumbers; i++)
            {
                masterSourceArray[i] = random.Next(10);
            }
            
            // Give time for attaching dotTrace to the running process
            //Thread.Sleep(20000);
        }

        object[] masterSourceArray = null;
        const int noNumbers = 10000000; // 10 mil
        private int[] PowersOfTwo = null;
        private int NumberOfArraysRequiredForNoNumbers = 0;

        
        [Benchmark]
        public void OnlyDefineOneArrayList()
        {
            ArrayList numbers = new ArrayList(noNumbers);
        }
        
        
        [Benchmark]
        public void DefineAllArrayListsEnRoute()
        {
            ArrayList numbers = null;

            for (int i=0;i<NumberOfArraysRequiredForNoNumbers;i++)
            {
                //Console.WriteLine("Creating an array of length: {0}", PowersOfTwo[i]);
                numbers = new ArrayList(PowersOfTwo[i]);
            }
            
            // Keep a reference so that DCE doesn't kick in
            int dummy = numbers.Count;
        }
        

        [Benchmark]
        public void DefineObjectArraysEnRouteAndCopyDataToThem()
        {
            // Create an object[] array that is double the previous number of elements
            //  and copy those elements across. Start from the array with 8 elements,
            //  therefore from i=1
            
            object[] numbers = null;

            for (int i = 1; i < NumberOfArraysRequiredForNoNumbers; i++)
            {
                //Console.WriteLine("Create an object[] array of size: {0}", PowersOfTwo[i]);
                numbers = new object[PowersOfTwo[i]];
                //Console.WriteLine("Copy across: {0} no of elements", PowersOfTwo[i-1]);
                Array.Copy(masterSourceArray, 0, numbers, 0, PowersOfTwo[i-1]);
            }

            // Keep a reference so that DCE doesn't kick in
            int dummy = numbers.Length;
        }
        

        [Benchmark]
        public void DefineObjectArraysEnRouteCopyDataToThemAndFillRestWithSameElement()
        {
            // Create an object[] array that is double the previous number of elements
            //  and copy them across. For the other half of the new array, simply fill
            //  it with references to the same pre-boxed int. This will simulate the
            //  behavior of the parameterless-constructed ArrayList and adding the same
            //  element, allowing us to compare performance of the same opertaion. Start
            //  from the array with 8 elements, therefore from i=1

            object[] numbers = null;
            object boxedInt = 1;

            for (int i = 1; i < NumberOfArraysRequiredForNoNumbers; i++)
            {
                //Console.WriteLine("Create an object[] array of size: {0}", PowersOfTwo[i]);
                numbers = new object[PowersOfTwo[i]];
                //Console.WriteLine("Copy across: {0} no of elements", PowersOfTwo[i-1]);
                Array.Copy(masterSourceArray, 0, numbers, 0, PowersOfTwo[i-1]);
                // Use the array's explicit length in order to help BCE (bound check elimination)
                int maxBracket = numbers.Length < noNumbers ? numbers.Length : noNumbers;
                //Console.WriteLine("Fill the 2nd half of the new object[] array: {0} no of elements", maxBracket-PowersOfTwo[i-1]);
                for (int j=PowersOfTwo[i-1];j<maxBracket;j++)
                {
                    numbers[j] = boxedInt;
                }
            }

            // Keep a reference so that DCE doesn't kick in
            int dummy = numbers.Length;
        }
        

        [Benchmark]
        public void AddSameElementToArrayListWithParameterlessConstructor()
        {
            ArrayList numbers = new ArrayList();
            object boxedInt = 1;
            
            for(int i=0;i<noNumbers;i++)
            {
                numbers.Add(boxedInt);
            }
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            var summary = BenchmarkRunner.Run<UnderTest>();
        }
    }
}