はじめに
今回はstring.Format
とDefaultInterpolatedStringHandler
で処理速度・Allocationの優劣を調べてみようと思います。
結論から言うとDefaultInterpolatedStringHandler
が優秀です。(C#10
から補完文字列(interpolated string
)でも利用されだしているので当然ですが)
string.Format
string.Format
の欠点は引数がobject
型なことです。Stack上のメモリに確保されていた値はHeap上にコピーされてしまいます。(いわゆるボックス化)
ボックス化 - C# によるプログラミング入門 | ++C++; // 未確認飛行 C
// 一部抜粋 public static string Format (string format, object? arg0); public static string Format (string format, object? arg0, object? arg1, object? arg2); public static string Format (string format, params object?[] args);
String.Format メソッド (System) | Microsoft Learn
BenchmarkDotNet
というベンチマーク用ライブラリを用いて、処理速度・メモリ確保量を調べてみます。
github.com
private const int a = 10; private const int b = 20; private const int c = 30; private const string x = "10"; private const string y = "20"; private const string z = "30"; [Benchmark] public void StringFormatTest1() { // Mean : 56.09 ns // GC.Alloc : 120 B // int -> object : ボックス化(Boxing allocation: conversion from 'int' to 'object' requires boxing of the value typ) _ = string.Format("{0}, {1}, {2}", a, b, c); } [Benchmark] public void StringFormatTest2() { // Mean : 52.10 ns // GC.Alloc : 48 B _ = string.Format("{0}, {1}, {2}", x, y, z); }
int
からobject
への変換が行われたことによるボックス化で、単純にstring
を扱う場合に比べてAllocationが多くなってしまっています。
DefaultInterpolatedStringHandler
C#10から登場したDefaultInterpolatedStringHandler
を利用して、同様の処理を実現してみたいと思います。
ufcpp.net
private const int a = 10; private const int b = 20; private const int c = 30; private const string x = "10"; private const string y = "20"; private const string z = "30"; [Benchmark] public void DefaultInterpolatedStringHandlerTest1() { // Mean : 26.49 ns // GC.Alloc : 48 B // literalLength : 「{}」を除いた部分の文字列長 // formattedCount : 「{}」の個数 var handler = new DefaultInterpolatedStringHandler(4, 3); handler.AppendFormatted(a); handler.AppendLiteral(", "); handler.AppendFormatted(b); handler.AppendLiteral(", "); handler.AppendFormatted(c); _ = handler.ToStringAndClear(); } [Benchmark] public void DefaultInterpolatedStringHandlerTest2() { // Mean : 21.92 ns // GC.Alloc : 48 B // literalLength : 「{}」を除いた部分の文字列長 // formattedCount : 「{}」の個数 var handler = new DefaultInterpolatedStringHandler(4, 3); handler.AppendFormatted(x); handler.AppendLiteral(", "); handler.AppendFormatted(y); handler.AppendLiteral(", "); handler.AppendFormatted(z); _ = handler.ToStringAndClear(); }
こちらはボックス化が働いていないため、同じAllocation量になっていました。
計測結果まとめ
処理速度・メモリ使用量ともに、DefaultInterpolatedStringHandler
が優れているという結果になりました。
Method | Mean | Error | StdDev | Allocated |
---|---|---|---|---|
StringFormatTest1(int) | 56.09 ns | 0.160 ns | 0.141 ns | 120 B |
StringFormatTest2(string) | 52.10 ns | 0.286 ns | 0.239 ns | 48 B |
DefaultInterpolatedStringHandlerTest1(int) | 26.49 ns | 0.302 ns | 0.268 ns | 48 B |
DefaultInterpolatedStringHandlerTest2(string) | 21.92 ns | 0.085 ns | 0.075 ns | 48 B |
計測で利用したコード
public class Program { public static void Main(string[] args) { BenchmarkRunner.Run<TestClass>(); } [MemoryDiagnoser(false)] public class TestClass { private const int a = 10; private const int b = 20; private const int c = 30; private const string x = "10"; private const string y = "20"; private const string z = "30"; [Benchmark] public void StringFormatTest1() { // GC.Alloc : 120 B // int -> object : ボックス化(Boxing allocation: conversion from 'int' to 'object' requires boxing of the value typ) _ = string.Format("{0}, {1}, {2}", a, b, c); } [Benchmark] public void StringFormatTest2() { // GC.Alloc : 48 B _ = string.Format("{0}, {1}, {2}", x, y, z); } [Benchmark] public void DefaultInterpolatedStringHandlerTest1() { // GC.Alloc : 48 B // literalLength : 「{}」を除いた部分の文字列長 // formattedCount : 「{}」の個数 var handler = new DefaultInterpolatedStringHandler(4, 3); handler.AppendFormatted(a); handler.AppendLiteral(", "); handler.AppendFormatted(b); handler.AppendLiteral(", "); handler.AppendFormatted(c); _ = handler.ToStringAndClear(); } [Benchmark] public void DefaultInterpolatedStringHandlerTest2() { // GC.Alloc : 48 B // literalLength : 「{}」を除いた部分の文字列長 // formattedCount : 「{}」の個数 var handler = new DefaultInterpolatedStringHandler(4, 3); handler.AppendFormatted(x); handler.AppendLiteral(", "); handler.AppendFormatted(y); handler.AppendLiteral(", "); handler.AppendFormatted(z); _ = handler.ToStringAndClear(); } } }