Custom StringBuilder Pool

In my last post I grumbled about ObjectPool being a separate package. That was essentially the single downside to use it. So, how hard is to implement our own StringBuilder pool?

Well, not that hard. The whole thing can be something like this:

internal static class StringBuilderPool {

    private static readonly ConcurrentQueue<StringBuilder> Pool = new();

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static StringBuilder Get() {
        return Pool.TryDequeue(out StringBuilder? sb) ? sb : new StringBuilder(4096);
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool Return(StringBuilder sb) {
        sb.Length = 0;
        Pool.Enqueue(sb);
        return true;
    }

}

In our Get method we check if we have any stored StringBuilder. If yes, we just return the same. If no, we create a new instance.

In the Return method we just add the returned instance to the queue.

Now, this is not exactly an ObjectPool equivalent. For example, it doesn’t limit the pool size. And it will keep large objects around forever. However, for my case it was good enough and unlikely to cause any problems.

And performance… Well, performance is promising, to say the least:

TestMeanErrorStdDevGen0Gen1Allocated
StringBuilder (small)15.762 ns0.3650 ns0.4057 ns0.0181-152 B
ObjectPool (small)17.257 ns0.0616 ns0.0576 ns0.0057-48 B
Custom pool (small)16.864 ns0.0192 ns0.0150 ns0.0057-48 B
Concatenation (small)9.716 ns0.1634 ns0.1528 ns0.0105-88 B
StringBuilder (medium)58.125 ns0.6429 ns0.6013 ns0.0526-440 B
ObjectPool (medium)23.226 ns0.0517 ns0.0484 ns0.0115-96 B
Custom pool (medium)23.660 ns0.2515 ns0.1963 ns0.0115-96 B
Concatenation (medium)66.353 ns1.3307 ns1.2447 ns0.0793-664 B
StringBuilder (large)190.293 ns0.7781 ns0.6498 ns0.24960.00102088 B
ObjectPool (large)92.556 ns0.9281 ns0.8228 ns0.0755-632 B
Custom pool (large)91.470 ns0.5478 ns0.5124 ns0.0755-632 B
Concatenation (large)1,430.599 ns11.5971 ns10.8479 ns4.01690.005733600 B

Pretty much its on-par with ObjectPool implementation. Honestly, results are close enough to be equivalent for all practical purposes.

So, if you don’t want to pull the whole Microsoft.Extensions.ObjectPool just for caching a few StringBuilder instances, consider rolling your own.