1

I have a .net 6 project and write some logs. The usual log looks:

int profileId = 100; //Any number
_logger.LogInformation("profileId = {ProfileId}", profileId);

Here, the boxing happens (int -> object). So, I decided to replace the code with:

int profileId = 100; //Any number
string profileIdString = profileId.ToString();
_logger.LogInformation("profileId = {ProfileId}", profileIdString);

However, I decided to run the benchmark and compare both options. Surprisingly, the results are not the ones I expected. Here is the code.

[MemoryDiagnoser]
public class Benchmark
{
    private const int N = 1000;

    [Benchmark]
    public void Boxing()
    {
        for (var i = 0; i < N; i++)
        {
            var s = string.Format("Test: {0}", i);
        }
    }

    [Benchmark]
    public void CastToString()
    {
        for (var i = 0; i < N; i++)
        {
            var s = string.Format("Test: {0}", i.ToString());
        }
    }
}

public class Program
{
    public static void Main(string[] args)
    {
        var summary = BenchmarkRunner.Run(typeof(Program).Assembly);
    }
}

Output:

|       Method |     Mean |    Error |   StdDev |  Gen 0 | Allocated |
|------------- |---------:|---------:|---------:|-------:|----------:|
|       Boxing | 26.41 us | 0.236 us | 0.221 us | 4.8828 |     62 KB |
| CastToString | 32.91 us | 0.297 us | 0.278 us | 5.4626 |     70 KB |

It looks like the boxing approach works better: It runs faster and allocates less memory. My question is why does it happen? Does it mean that it is better to use the boxing approach in cases like this? Or did I write the performance test incorrectly?

Grigory Zhadko
  • 1,484
  • 1
  • 19
  • 33
  • 1
    Your benchmark is for calling `string.Format`, but your real code is calling `LogInformation`. There's no guarantee that they'll have the same performance characteristics. If the logging aspect isn't particularly relevant to the question, I'd remove it. (Or maybe just move a brief mention of it to the bottom as context for the motivation of the question, but without the code.) – Jon Skeet Mar 25 '22 at 07:26
  • @JonSkeet you're right, there is no guarantee that the performance characteristics are the same. However, does it really matter here? In both scenarios, the boxing happens if I don't use ToString(). – Grigory Zhadko Mar 25 '22 at 07:37
  • your add a extra tostring method call in 2nd. when they say boxing overhead, they often imply there's unboxing later for calculations for example. in your case, you want convert number to string, it(string.format method) will convert(box) to object no matter if you explicitely convert first. so i think your case is always boxing. not real common boxing unboxing we usually talk about. – Lei Yang Mar 25 '22 at 07:41
  • 2
    @LeiYang: No, the version using `ToString()` does *not* perform boxing. And unboxing is cheaper than boxing: it doesn't require any memory allocation. – Jon Skeet Mar 25 '22 at 07:48
  • 1
    ok. so i'm also curious how to explain the version with boxing is faster? – Lei Yang Mar 25 '22 at 07:58
  • Boxing creates a new object - but so does calling `ToString()`. If the code in `string.Format` is able to format an `int` (after unboxing) *without* creating a separate string for it (and that's definitely feasible) that could explain it. – Jon Skeet Mar 25 '22 at 08:51
  • @JonSkeet I tried to replace `i` in loop body with a constant and the results are the same. The boxing approach is still better. – Grigory Zhadko Mar 25 '22 at 09:02
  • 1
    @GrigoryZhadko: Even with a constant, you'd still be creating a new string on each iteration. If you move the ToString call outside the loop, and pass that string in, then I'd expect to see it be faster (and with less allocation). I'd also rename the test to "ConvertToString" - you're not casting at all. – Jon Skeet Mar 25 '22 at 09:19

0 Answers0