I am just getting into functional programming and trying to understand when a class/property should be made mutable.
When working with a significant number of string concatenations, we know that it’s better to use StringBuilder, for instance:
using System;
using System.Diagnostics;
using System.Text;
namespace ConsoleApplication3
{
internal class Program
{
private static string myStr;
private static readonly StringBuilder mySb = new StringBuilder();
private static void Main(string[] args)
{
Profile("+", 100000, () => myStr = myStr + "a"); // Takes 2236 ms
Profile("SB", 100000, () => mySb.Append("a")); // Takes 1 ms
}
private static void Profile(string description, int iterations, Action func)
{
// clean up
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
// warm up
func();
Stopwatch watch = Stopwatch.StartNew();
for (int i = 0; i < iterations; i++)
{
func();
}
watch.Stop();
Console.Write(description);
Console.WriteLine(" Time Elapsed {0} ms", watch.ElapsedMilliseconds);
}
}
}
This is the commonly known case in which it’s significantly more performant to concatenate strings via StringBuilder versus the + operator. My assumption is that StringBuilder achieves better performance by creating fewer strings.
Is there a balance between performance and immutability, or is this case an exception for some reason?
Joining strings efficiently is less about mutable vs immutable structures, and more about picking the right data structure and evaluation strategy to support O(1) append.
Typically, trees of various sorts are used to support fast append, which maximise sharing, and minimize copying. Example structures include ropes and finger trees.
Lazy evaluation can also help in some cases (e.g. if concatenation involves copying, that can be delayed to if and when the tail of the string is actually needed). A strict data structure might incur additional copying overhead in such a case, doing more work than is necessary.
In your case, I suspect
+involves a strict copy of the arguments (i.e. O(n+m)) work, while the string builder can avoid some of the work by amortizing the reallocation of the string buffer (giving you tree-like performance, at the cost of requiring linear use of the structure, and losing thread safety).