public static void DemoOutVsReturn() { Benchmarker.Benchmark(() => { StopwatchWrapper.Time(() => { }, out var sw1); }, "Out parameter", _count); Benchmarker.Benchmark(() => { var sw2 = StopwatchWrapper.Time(() => { }); }, "Return", _count); Console.WriteLine(); }
public static void InjectingTestScheduler_AndManipulatingVirtualTime_HappensInstantly() { Benchmarker.Benchmark(() => { var expectedValues = new long[] { 0, 1, 2, 3, 4 }; var actualValues = new List <long>(); var scheduler = new TestScheduler(); var subscription = Observable .Interval(TimeSpan.FromSeconds(1), scheduler) .Take(5) .Subscribe(actualValues.Add); scheduler.Start(); CollectionAssert.AreEqual(expectedValues, actualValues); Console.WriteLine("Collection was as expected."); }); }
public static void Count() { List <int> list = DataGenerator.GetRandomIntArray(10).ToList(); int c = 0; Benchmarker.Benchmark(() => { //With extension method (the wrong way): c = list.Count(); }, "Using extension method Count()", _count); Benchmarker.Benchmark(() => { //TODO 1.10: With property (the right way): c = list.Count(); }, "Using property Count", _count); Console.WriteLine(); }
public static void ExecuteSubstringVsAppendOverload(string s) { var builder = new StringBuilder(); Benchmarker.Benchmark(() => { builder.Clear(); string temp = s.Substring(5, 5); builder.Append(temp); }, "Substring append", _count); Benchmarker.Benchmark(() => { builder.Clear(); //TODO 1.2: use append overload builder.Append(s); }, "Append overload", _count); Console.WriteLine(); }
public static void DemoLookupTableToLower() { string _lookupStringL = "--------------------------------------&-()*+,-./----------:;<=>?@abcdefghijklmnopqrstuvwxyz[-]^_`abcdefghijklmnopqrstuvwxyz{|}~-"; char y = 'Y'; char result; Benchmarker.Benchmark(() => { result = char.ToLower(y); }, "Char.ToLower()", _count); Benchmarker.Benchmark(() => { result = _lookupStringL[y]; }, "Char to lower with lookup table", _count); Console.WriteLine(); //A lookup table is a lot faster than the regular ToLower(), but using this alternative could cause localization issues }
public static void DemoMemoization() { List <string> s = DataGenerator.GetRandomStringArray(10).ToList(); string result = string.Empty; Dictionary <string, string> _upperCaseTempDictionary = new Dictionary <string, string>(); Benchmarker.Benchmark(() => { foreach (var item in s) { result = item.ToUpper(); } }, "ToUpper()", _count); Benchmarker.Benchmark(() => { foreach (var item in s) { if (_upperCaseTempDictionary.TryGetValue(item, out string lookupValue)) { result = lookupValue; continue; } lookupValue = item.ToUpper(); _upperCaseTempDictionary[item] = lookupValue; } }, "ToUpper() with lookup dictionary (memoization)", _count); /* * Memoization is a way of caching used to optimize a function. * Known results are stored in memory, so the computation only runs once * * NOTE: the heavier a computation, the more useful this gets. * Don't forget that Dictionaries will become slower as they increase in size! * The following example would cause a performance hit when using a very big stringarray * * Alternative: inject IMemoryCache */ Console.WriteLine(); }
public static void DemoRecursiveInline() { Benchmarker.Benchmark(() => { List<int> list = new List<int>(); ExecuteRecursiveMethod(list, 0); }, "Recursive", _count); Benchmarker.Benchmark(() => { List<int> list = new List<int>(); if (list.Count < 10) { list.Add(0); ExecuteRecursiveMethod(list, 0 + 1); } }, "Recursive inline", _count); Console.WriteLine(); //Summary: deeper call stack = less performant }
public static void DemoJaggedTemporalLocality() { int height = 50; int width = 50; var jaggedArray = DataGenerator.GetJaggedArray(height, width); int result; Benchmarker.Benchmark(() => { for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { result = jaggedArray[j][i]; } } }, "Low temporal locality", _count); Benchmarker.Benchmark(() => { for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { result = jaggedArray[i][j]; } } }, "High temporal locality", _count); Console.WriteLine(); /* * https://stackoverflow.com/questions/763262/how-does-one-write-code-that-best-utilizes-the-cpu-cache-to-improve-performance * The reason this is cache inefficient is because modern CPUs will load the cache line with "near" memory addresses * from main memory when you access a single memory address. * We are iterating through the "j"(outer) rows in the array in the inner loop, * so for each trip through the inner loop, * the cache line will cause to be flushed and loaded with a line of addresses that are near to the[j][i] entry */ }
public static void DetermineStringBuilderPerformanceVsConcat(string s, int cnt) { var capacity = cnt * s.Length; Console.WriteLine("String count: " + cnt); Benchmarker.Benchmark(() => { var str = string.Empty; for (int i = 1; i < cnt; i++) { str += s; } }, "String Concatenation", 1, cnt); Benchmarker.Benchmark(() => { var builder = new StringBuilder(); for (int i = 1; i < cnt; i++) { builder.Append(s); } var str = builder.ToString(); }, "Default StringBuilder", 1, cnt); Benchmarker.Benchmark(() => { //TODO 1.1: add stringbuilder capacity var builder = new StringBuilder(); for (int i = 1; i < cnt; i++) { builder.Append(s); } var str = builder.ToString(); }, "StringBuilder with fixed capacity", 1, cnt); Console.WriteLine(); }
public static void ShowStringBuilderConcatenationPerformance() { string[] stringArray = DataGenerator.GetRandomStringArray(5); Benchmarker.Benchmark(() => { StringBuilder builder = _builder; builder.Clear(); foreach (string value in stringArray) { builder.Append($"ABC {_count}" + "DEF" + string.Concat("GHI", "XYZ") + value); } var str = builder.ToString(); }, "With concats in append", _count); string a = string.Empty; Benchmarker.Benchmark(() => { StringBuilder builder = _builder; builder.Clear(); foreach (string value in stringArray) { a = $"ABC {_count}" + "DEF" + string.Concat("GHI", "XYZ") + value; builder.Append(a); } var str = builder.ToString(); }, "Without concats in appends", _count); Console.WriteLine(); }
public static void DemoTryGetValueVsContainsKey() { var counts = new Dictionary <string, int> { { "key", DataGenerator.GetRandomInt() } }; int result; Benchmarker.Benchmark(() => { if (counts.ContainsKey("key")) { result = counts["key"]; } }, "ContainsKey()", _count); Benchmarker.Benchmark(() => { if (counts.TryGetValue("key", out result)) { } }, "TryGetValue()", _count); //Summary: use TryGetValue over ContainsKey, since ContainsKey causes an unnecessary lookup }
public static void DemoJaggedArray() { int width = 50; int height = 50; int result; // 2D array. Benchmarker.Benchmark(() => { DataGenerator.Get2DArray(height, width); }, "2D array creation", _count); // Jagged array. Benchmarker.Benchmark(() => { DataGenerator.GetJaggedArray(height, width); }, "Jagged array creation", _count); var twoDimensionalArray = DataGenerator.Get2DArray(height, width); var jaggedArray = DataGenerator.GetJaggedArray(height, width); // 2D array. Benchmarker.Benchmark(() => { for (int j = 0; j < height; j++) { for (int a = 0; a < width; a++) { result = twoDimensionalArray[j, a]; } } }, "2D array element access", _count); // Jagged array. Benchmarker.Benchmark(() => { for (int j = 0; j < height; j++) { for (int a = 0; a < width; a++) { result = jaggedArray[a][j]; } } }, "Jagged array element access with low temporal locality", _count); // Jagged array. Benchmarker.Benchmark(() => { for (int j = 0; j < height; j++) { for (int a = 0; a < width; a++) { result = jaggedArray[j][a]; } } }, "Jagged array element access with high temporal locality", _count); /* * Summary: 2D arrays are faster to allocate, but a lot slower to access than jagged arrays. * Since element access count is usually a lot greater than array allocation count (1), * using jagged arrays should still result in faster code, but be careful with memory usage * * Jagged array allocation slowdown explained: * - 2D array only needs 1 allocation * - Jagged array needs length * width + 1 allocations * => this means garbage collection will have to do a lot more work when using jagged arrays * * Jagged array element access with imperformant index ordering => See DemoJaggedTemporalLocality explanation * * NOTE: jagged arrays don't necessarily have to be allocated at the start, u could do this lazily! * */ Console.WriteLine(); }
public static void DemoArrayFlattening() { var height = DataGenerator.GetRandomInt(5, 5); var width = DataGenerator.GetRandomInt(5, 5); int result; int[,] twoDimensional = new int[height, width]; int[] oneDimensional = new int[width * height]; //2D //Assign values twoDimensional[0, 1] = 4; twoDimensional[1, 2] = 5; twoDimensional[2, 3] = 6; twoDimensional[3, 4] = 7; //Display array for (int i = 0; i < height; i++) { for (int a = 0; a < width; a++) { Console.Write(twoDimensional[i, a]); } Console.WriteLine(); } Console.WriteLine(); //1D //Assign values oneDimensional[1] = 4; oneDimensional[1 * width + 2] = 5; oneDimensional[2 * width + 3] = 6; oneDimensional[3 * width + 4] = 7; //Display array for (int i = 0; i < height; i++) { for (int a = 0; a < width; a++) { Console.Write(oneDimensional[i * width + a]); } Console.WriteLine(); } Console.WriteLine(); Benchmarker.Benchmark(() => { twoDimensional[0, 1] = 4; twoDimensional[1, 2] = 5; twoDimensional[2, 3] = 6; twoDimensional[3, 4] = 7; for (int j = 0; j < height; j++) { for (int a = 0; a < width; a++) { result = twoDimensional[j, a]; } } }, "2D array (assign & read)", _count); Benchmarker.Benchmark(() => { oneDimensional[1] = 4; oneDimensional[1 * width + 2] = 5; oneDimensional[2 * width + 3] = 6; oneDimensional[3 * width + 4] = 7; for (int j = 0; j < height; j++) { for (int a = 0; a < width; a++) { result = oneDimensional[j * width + a]; } } }, "1D array (assign & read)", _count); Console.WriteLine(); }
public static void CompareDataTypes() { const int nrOfAppends = 100; Console.WriteLine("Int vs. char: "); Benchmarker.Benchmark(() => { StringBuilder s = new StringBuilder(1000); for (int v = 0; v < nrOfAppends; v++) { s.Append(1); } }, "Int", _count, _count * nrOfAppends); Benchmarker.Benchmark(() => { StringBuilder s = new StringBuilder(1000); for (int v = 0; v < nrOfAppends; v++) { s.Append('1'); } }, "Char", _count, _count * nrOfAppends); //Char is faster (string is an array of chars) Console.WriteLine(); Console.WriteLine("Bool vs. string: "); Benchmarker.Benchmark(() => { StringBuilder s = new StringBuilder(4000); for (int v = 0; v < nrOfAppends; v++) { s.Append(true); } }, "Bool", _count, _count * nrOfAppends); Benchmarker.Benchmark(() => { StringBuilder s = new StringBuilder(4000); for (int v = 0; v < nrOfAppends; v++) { s.Append("True"); } }, "String", _count, _count * nrOfAppends); //String is faster (bool is a value type, and has to be converted to a string, which is a reference type //=> boxing occurs => performance hit) Console.WriteLine(); Console.WriteLine("String vs. char: "); Benchmarker.Benchmark(() => { StringBuilder s = new StringBuilder(1000); for (int v = 0; v < nrOfAppends; v++) { s.Append("a"); } }, "String", _count, _count * nrOfAppends); Benchmarker.Benchmark(() => { StringBuilder s = new StringBuilder(1000); for (int v = 0; v < nrOfAppends; v++) { s.Append('a'); } }, "Char", _count, _count * nrOfAppends); //Char is faster (string is a char array + char is a value type and string is a reference type) Console.WriteLine(); }
public static void DemoClassVsStruct() { string str = DataGenerator.GetRandomString(); int i = DataGenerator.GetRandomInt(); Console.WriteLine("Initialization:"); Benchmarker.Benchmark(() => { var a = new SomeClassWithStrings { A = str }; }, "Class with string", _count); Benchmarker.Benchmark(() => { SomeStructWithStrings a = new SomeStructWithStrings { A = str }; }, "Struct with string", _count); Benchmarker.Benchmark(() => { var a = new SomeClassWithInts { A = i }; }, "Class with int", _count); Benchmarker.Benchmark(() => { SomeStructWithInts a = new SomeStructWithInts { A = i }; }, "Struct with int", _count); Console.WriteLine(); Console.WriteLine("As a method parameter:"); var pair1 = new KeyValuePair <string, SomeClassWithStrings>("key", new SomeClassWithStrings()); var pair2 = new KeyValuePair <string, SomeStructWithStrings>("key", new SomeStructWithStrings()); Benchmarker.Benchmark(() => { var a = pair2.Value; }, "Struct as parameter", _count); Benchmarker.Benchmark(() => { var a = pair1.Value; }, "Class as parameter", _count); /* * Summary: Allocating structs is generally faster than allocating classes, * however it's not recommended to use structs with a lot of fields, * since the struct and all of it's fields will be copied onto the function stack when using structs as a parameter */ }
public static void DemoCommonSenseIfOrdering() { int count = 9999; int amountOfDigits = 0; Benchmarker.Benchmark(() => { for (int c = 0; c < count; c++) { if (c < 10) { amountOfDigits = 1; } else if (c < 100) { amountOfDigits = 2; } else if (c < 1000) { amountOfDigits = 3; } else { amountOfDigits = 4; } } }, "Regular if"); Benchmarker.Benchmark(() => { for (int c = 0; c < count; c++) { //TODO 1.27: reorder if statement so amountOfDigits = 4 is in the first if statement if (c < 10) { amountOfDigits = 1; } else if (c < 100) { amountOfDigits = 2; } else if (c < 1000) { amountOfDigits = 3; } else { amountOfDigits = 4; } } }, "Reordered if"); Console.WriteLine(); /* * Summary: * Since our count is 9999, the else statement in the 1st example would result true more frequently than the other cases * In the 2nd example, our first if statement will evaluate to true the most (9000 of our 9999 items are greater than 999) * Reordening if statements so more frequent cases are at the top op the if-else statement improves performance, since less * if statements have to be evaluated. */ }
public static void DemoIfVsSwitch() { int testValue1 = 100; Benchmarker.Benchmark(() => { if (testValue1 == 0) { } else if (testValue1 == 1) { } else if (testValue1 == 2) { } else if (testValue1 == 3) { } else if (testValue1 == 4) { } else if (testValue1 == 5) { } else if (testValue1 == 6) { } else { } }, "If-else chain of 7 statements", _count); Benchmarker.Benchmark(() => { switch (testValue1) { case 0: break; case 1: break; case 2: break; case 3: break; case 4: break; case 5: break; case 6: break; default: break; } }, "Switch 7 cases", _count); Benchmarker.Benchmark(() => { switch (testValue1) { case 0: break; case 1: break; default: break; } }, "Switch 3 cases", _count); Benchmarker.Benchmark(() => { if (testValue1 == 0) { } else if (testValue1 == 1) { } else { } }, "If-else chain of 3 statements", _count); Console.WriteLine(); /* * Summary:Switch statements are recommended for 7 cases & more * However, if the input is almost always a specific value, then using an if-statement to test for that value may be faster. */ }