Console apps and fact-based decision making.

Sometimes a programming task can be accomplished in one of several different ways. At some point, performance and efficacy become important enough and you consider very carefully which way you will do something. Is it the right approach? Is it the most efficient? The most performant?

In this blog post, I will share something simple and yet valuable, which I learned from a colleague early in my career, a useful principle that I think when applied, makes one a better software engineer. The principle is simply:

When you want to know which way is the fastest way to do something, or the most efficient way, write an app for that! 

A console app to be precise. Actually, there were no “apps for that” back when I picked this up, handheld devices were still called PDA’s and a cellphone was still just a cellphone, and clunky at that. Even so, although much has changed, and unit test frameworks have come a long way, console apps haven’t changed much, and yet they are still just as useful for this sort of thing as they ever were.

As an example, building a string in memory is a good one. Firstly there are several ways to build a string in memory, each one more or less quick, and each one using more or less stack/heap-space, and/or causing more or less frequent garbage collections to occur. Here are a few ways a string might be built up or accumulated in memory:

  • The ‘+’ operator.
  • The StringBuilder class.
  • The string.Join method.

So, the contestants are established. Now to have them race against each other on the same racetrack, we’ll need a console app. The app will use each of the three string-building methods above to build a distinct copy of the same string as output, while a timer will be used to show runtimes for each of the methods.

A few things to note about the code:

  • Each method is repeated three times to allow us to gain an average.
  • One might consider repeating each method in a seperate app.
  • The ‘+’ operator was only run 50K times on each repeat, for brevity 🙂

Here is the code for the console app:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace TestStringBuilding
{
class Program
{
static void Main(string[] args)
{
Stopwatch timer = new Stopwatch();

Console.WriteLine(" STRING BUILDER ");
timer.Start();
StringBuilder builder1 = new StringBuilder();
for (int i = 0; i < 100000; i++) { builder1.Append("Iteration_"); builder1.Append(i.ToString()); } Console.WriteLine(builder1.ToString().Length); timer.Stop(); Console.WriteLine(timer.Elapsed.TotalSeconds); Console.WriteLine(); timer.Start(); StringBuilder builder2 = new StringBuilder(); for (int i = 0; i < 100000; i++) { builder2.Append("Iteration_"); builder2.Append(i.ToString()); } Console.WriteLine(builder2.ToString().Length); timer.Stop(); Console.WriteLine(timer.Elapsed.TotalSeconds); Console.WriteLine(); timer.Start(); StringBuilder builder3 = new StringBuilder(); for (int i = 0; i < 100000; i++) { builder3.Append("Iteration_"); builder3.Append(i.ToString()); } Console.WriteLine(builder3.ToString().Length); timer.Stop(); Console.WriteLine(timer.Elapsed.TotalSeconds); Console.WriteLine(); Console.WriteLine(" LIST - string.JOIN ");
timer.Start();
List listStr1 = new List();
for (int i = 0; i < 100000; i++) { listStr1.Add("Iteration_"); listStr1.Add(i.ToString()); } Console.WriteLine(string.Join("", listStr1.ToArray()).Length); timer.Stop(); Console.WriteLine(timer.Elapsed.TotalSeconds); Console.WriteLine(); timer.Start(); List listStr2 = new List();
for (int i = 0; i < 100000; i++) { listStr2.Add("Iteration_"); listStr2.Add(i.ToString()); } Console.WriteLine(string.Join("", listStr2.ToArray()).Length); timer.Stop(); Console.WriteLine(timer.Elapsed.TotalSeconds); Console.WriteLine(); timer.Start(); List listStr3 = new List();
for (int i = 0; i < 100000; i++) { listStr3.Add("Iteration_"); listStr3.Add(i.ToString()); } Console.WriteLine(string.Join("", listStr3.ToArray()).Length); timer.Stop(); Console.WriteLine(timer.Elapsed.TotalSeconds); Console.WriteLine(); Console.WriteLine(" PLUS OPERATOR "); timer.Start(); string s1 = string.Empty; for (int i = 0; i < 50000; i++) { s1 = s1 + "Iteration_" + i.ToString(); } Console.WriteLine(s1.Length); timer.Stop(); Console.WriteLine(timer.Elapsed.TotalSeconds); timer.Start(); s1 = string.Empty; for (int i = 0; i < 50000; i++) { s1 = s1 + "Iteration_" + i.ToString(); } Console.WriteLine(s1.Length); timer.Stop(); Console.WriteLine(timer.Elapsed.TotalSeconds); Console.WriteLine(); timer.Start(); s1 = string.Empty; for (int i = 0; i < 50000; i++) { s1 = s1 + "Iteration_" + i.ToString(); } Console.WriteLine(s1.Length); timer.Stop(); Console.WriteLine(timer.Elapsed.TotalSeconds); Console.WriteLine(); Console.ReadLine(); } } }

Here is the output:

String Building - Three methods compared in a console app.

String Building - Three methods compared in a console app.

A few things to note:

The plus operator sucks bad.

The StringBuilder class is the fastest.
The join method is a close runner-up!
What we have not covered with this test is the memory usage. A good memory profiler, or even just a few performance counters of your own would quickly reveal which is the winner in this area, and demonstrate to you, something about how the memory is used when you do one thing or another.
So, we get an average runtime, an overall memory consumption average and voila! We have proven several things and seen them proven first-hand.
Now, armed with that knowledge, we can assert which is best to use, without talking through our hat!
Happy coding!
May the work of your hands prosper.