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>();
}
}
}