LoginSignup
8
8

More than 3 years have passed since last update.

[C# 9.0 Preview] デリゲートをFunction Pointerに置き換えたら速くなるのか実験した

Last updated at Posted at 2020-08-05

免責事項

  • 現時点ではPreviewの機能です。
  • Visual Studio Version 16.7.0 Preview 6.0(.NET Core 3.1.400-preview-015203)時点での結果です。
  • 効果は当然ですがソフトウェアに依ります。
  • 置き換え方が間違っていたらごめんなさい。

イントロ

C# 9.0から、unsafeコンテキスト限定ですがFunction Pointersが使用できるようになります。
(まだ多少構文が変わるそうですが。ところで日本語ではそのまま「関数ポインター」でいいのだろうか?)

従来からあるデリゲートは高機能ですが、オーバーヘッドが多少あります。[^要出典]
実際のソフトウェアで、Function Pointersに置き換えることで高速化されるか実験してみました。

対象ソフトウェア

最近私がPull Requestを送っていた Cysharp/ZStringというライブラリでは、値を文字列にする処理を、型毎にデリゲートで保持しています。
そのため、(JITで消え去りそうですけど)少なからずデリゲート呼び出しのコストがかかっているはずです。
Function Pointersにするにはうってつけ?のハズです。

先に結果だけ書くと、数パーセント速くなってそうでした。

計測

Nugetの最新版(2.2.0)と、それをベースにFunction Pointersに置き換えたコードをBenchmarkDotNetで比較します。
コードとベンチマークコードはGithubに一応置いておきますが、書き捨てるつもりなのでしばらくしたら消すかもしれません。
この記事の最後にpatchとして置いておきます。

ベンチマーク結果のMethodで、末尾に_が付いているのがリリース版(デリゲート版)、Nが付いているのが置き換えたコード(Function Pointer版)です。

なお、ZString にはUtf16用のクラスとUtf8用のクラスがありますが、面倒なのでUtf16用しか置き換えと計測をしていません。

ベンチマークコードはFormatBenchmark.csdotnet run -c Releaseで実行しています。

結果(1回目)

Ratio列に注目してください。
多少速くなっていそうな雰囲気があります。
どうしても計測時環境によりブレがありますので、もう一回測ってみます。

Method FormatString Mean Error StdDev Ratio RatioSD Code Size Gen 0 Gen 1 Gen 2 Allocated
Format_ This (...)orld. [62] 225.71 ns 1.708 ns 1.598 ns 1.00 0.00 3147 B 0.0181 - - 152 B
FormatN This (...)orld. [62] 192.91 ns 2.135 ns 1.997 ns 0.85 0.01 3458 B 0.0181 - - 152 B
Utf16PreparedFormat_ This (...)orld. [62] 85.97 ns 1.762 ns 2.097 ns 1.00 0.00 2597 B 0.0181 - - 152 B
Utf16PreparedFormatN This (...)orld. [62] 84.32 ns 1.686 ns 1.731 ns 0.98 0.02 2567 B 0.0181 - - 152 B
Utf16StringBuilderAppendFormat_ This (...)orld. [62] 201.26 ns 0.881 ns 0.824 ns 1.00 0.00 7425 B - - - -
Utf16StringBuilderAppendFormatN This (...)orld. [62] 177.24 ns 0.850 ns 0.795 ns 0.88 0.01 7924 B - - - -
Format_ x:{0}, y:{1} 137.85 ns 0.466 ns 0.436 ns 1.00 0.00 3147 B 0.0057 - - 48 B
FormatN x:{0}, y:{1} 135.39 ns 0.747 ns 0.699 ns 0.98 0.01 3458 B 0.0057 - - 48 B
Utf16PreparedFormat_ x:{0}, y:{1} 74.40 ns 0.694 ns 0.649 ns 1.00 0.00 2597 B 0.0057 - - 48 B
Utf16PreparedFormatN x:{0}, y:{1} 74.23 ns 0.627 ns 0.586 ns 1.00 0.01 2567 B 0.0057 - - 48 B
Utf16StringBuilderAppendFormat_ x:{0}, y:{1} 141.08 ns 0.671 ns 0.628 ns 1.00 0.00 7423 B - - - -
Utf16StringBuilderAppendFormatN x:{0}, y:{1} 131.62 ns 0.580 ns 0.543 ns 0.93 0.01 7922 B - - - -
BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19042
AMD Ryzen 7 3700X, 1 CPU, 16 logical and 8 physical cores
.NET Core SDK=3.1.400-preview-015203
  [Host]     : .NET Core 3.1.6 (CoreCLR 4.700.20.26901, CoreFX 4.700.20.31603), X64 RyuJIT
  DefaultJob : .NET Core 3.1.6 (CoreCLR 4.700.20.26901, CoreFX 4.700.20.31603), X64 RyuJIT

結果(2回目)

遅くなっているケースもありますが、傾向として速くなってそうです。

Method FormatString Mean Error StdDev Ratio RatioSD Code Size Gen 0 Gen 1 Gen 2 Allocated
Format_ This (...)orld. [62] 225.79 ns 1.438 ns 1.345 ns 1.00 0.00 3147 B 0.0181 - - 152 B
FormatN This (...)orld. [62] 192.97 ns 1.582 ns 1.321 ns 0.85 0.01 3458 B 0.0181 - - 152 B
Utf16PreparedFormat_ This (...)orld. [62] 85.96 ns 1.667 ns 1.784 ns 1.00 0.00 2597 B 0.0181 - - 152 B
Utf16PreparedFormatN This (...)orld. [62] 90.81 ns 1.874 ns 1.924 ns 1.06 0.02 2567 B 0.0181 - - 152 B
Utf16StringBuilderAppendFormat_ This (...)orld. [62] 191.97 ns 0.974 ns 0.911 ns 1.00 0.00 7276 B - - - -
Utf16StringBuilderAppendFormatN This (...)orld. [62] 176.76 ns 0.877 ns 0.821 ns 0.92 0.01 7924 B - - - -
Format_ x:{0}, y:{1} 137.57 ns 0.816 ns 0.763 ns 1.00 0.00 3147 B 0.0057 - - 48 B
FormatN x:{0}, y:{1} 137.22 ns 0.797 ns 0.746 ns 1.00 0.01 3458 B 0.0057 - - 48 B
Utf16PreparedFormat_ x:{0}, y:{1} 73.85 ns 0.731 ns 0.683 ns 1.00 0.00 2597 B 0.0057 - - 48 B
Utf16PreparedFormatN x:{0}, y:{1} 76.60 ns 0.685 ns 0.641 ns 1.04 0.01 2567 B 0.0057 - - 48 B
Utf16StringBuilderAppendFormat_ x:{0}, y:{1} 140.42 ns 0.659 ns 0.584 ns 1.00 0.00 7425 B - - - -
Utf16StringBuilderAppendFormatN x:{0}, y:{1} 125.47 ns 0.545 ns 0.510 ns 0.89 0.00 7922 B - - - -

結論

  • 対象は数パーセントは効果あり。
  • しかし、関数ポインターできるのは静的関数のみで、設計にかなり制約が出てくる。 (例えば、外からデリゲートを渡しづらくなる)

これよりもっとデリゲートを使っているとか、複雑さを犠牲にしてでも1%でも速くしたいとかで無ければ、置き換えるのは最後の手段にしておきましょう。

コードとベンチマークコード

追記:実はコードは少し前(Preview 4.0か5.0)に作っていて、その時はFunction Pointerとvoid*との相互変換ができませんでした。(ドキュメントでは許可されている)
そのため、ちょっと回りくどいコードになっています。
しかし、Preview 6.0ではvoid*との相互変換できるようになっていたため、今ならもう少しオリジナルのコードに近いものにできそうです。


0001-function-pointers.patch
From bab609ea44ed09d24537c279de043d8072615f5d Mon Sep 17 00:00:00 2001
From: udaken <u*********@gmail.com>
Date: Wed, 5 Aug 2020 23:12:23 +0900
Subject: [PATCH] function pointers

---
 global.json                                   |   6 +
 .../BenchmarkVsReleasedVersion.csproj         |   2 +-
 .../BuiltinTypesBenchmark.cs                  |  12 +-
 .../FormatBenchmark.cs                        |  12 +-
 sandbox/BenchmarkVsReleasedVersion/Program.cs |   3 +-
 .../Assets/Scripts/ZString/PreparedFormat.cs  |  64 +++---
 .../Utf16ValueStringBuilder.AppendFormat.cs   |   2 +-
 ...Utf16ValueStringBuilder.CreateFormatter.cs | 217 ++++++++++--------
 .../ZString/Utf16ValueStringBuilder.cs        |  77 ++++---
 src/ZString/PreparedFormat.cs                 |  64 +++---
 src/ZString/PreparedFormat.tt                 |   2 +-
 .../Utf16ValueStringBuilder.AppendFormat.cs   |   2 +-
 .../Utf16ValueStringBuilder.AppendFormat.tt   |   2 +-
 ...Utf16ValueStringBuilder.CreateFormatter.cs | 217 ++++++++++--------
 ...Utf16ValueStringBuilder.CreateFormatter.tt |  42 ++--
 src/ZString/Utf16ValueStringBuilder.cs        |  77 ++++---
 src/ZString/ZString.csproj                    |   2 +
 17 files changed, 437 insertions(+), 366 deletions(-)
 create mode 100644 global.json

diff --git a/global.json b/global.json
new file mode 100644
index 0000000..5679eb1
--- /dev/null
+++ b/global.json
@@ -0,0 +1,6 @@
+{
+  "sdk": {
+   "allowPrerelease" : true,
+   "version" : "3.1.400-preview-015203"
+  }
+}
diff --git a/sandbox/BenchmarkVsReleasedVersion/BenchmarkVsReleasedVersion.csproj b/sandbox/BenchmarkVsReleasedVersion/BenchmarkVsReleasedVersion.csproj
index 7e34c82..4e03240 100644
--- a/sandbox/BenchmarkVsReleasedVersion/BenchmarkVsReleasedVersion.csproj
+++ b/sandbox/BenchmarkVsReleasedVersion/BenchmarkVsReleasedVersion.csproj
@@ -9,7 +9,7 @@

     <ItemGroup>
       <PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.7.1" Condition="$(TargetFramework) != 'netstandard3.1'" />
-      <PackageReference Include="ZString" Version="2.1.3" />
+      <PackageReference Include="ZString" Version="2.2.0" />
       <PackageReference Include="BenchmarkDotNet" Version="0.12.1" />
     </ItemGroup>

diff --git a/sandbox/BenchmarkVsReleasedVersion/BuiltinTypesBenchmark.cs b/sandbox/BenchmarkVsReleasedVersion/BuiltinTypesBenchmark.cs
index 20b8ab4..5de265c 100644
--- a/sandbox/BenchmarkVsReleasedVersion/BuiltinTypesBenchmark.cs
+++ b/sandbox/BenchmarkVsReleasedVersion/BuiltinTypesBenchmark.cs
@@ -96,13 +96,13 @@ namespace BenchmarkVsReleasedVersion
                 _byte, _dt, _dto, _decimal, _double, _guid, _short, _float, _ts, _uint, _ulong, _null, _string, _bool, _enum, _char);
         }

-        [BenchmarkCategory("CreatePreparedFormat"), Benchmark(Baseline = true)]
+        //[BenchmarkCategory("CreatePreparedFormat"), Benchmark(Baseline = true)]
         public object CreatePreparedFormat_()
         {
             return new PF16(_format);
         }

-        [BenchmarkCategory("CreatePreparedFormat"), Benchmark]
+        //[BenchmarkCategory("CreatePreparedFormat"), Benchmark]
         public object CreatePreparedFormatN()
         {
             return new NPF16(_format);
@@ -122,14 +122,14 @@ namespace BenchmarkVsReleasedVersion
                 _byte, _dt, _dto, _decimal, _double, _guid, _short, _float, _ts, _uint, _ulong, _null, _string, _bool, _enum, _char);
         }

-        [BenchmarkCategory("Utf8PreparedFormat"), Benchmark(Baseline = true)]
+        //[BenchmarkCategory("Utf8PreparedFormat"), Benchmark(Baseline = true)]
         public string Utf8PreparedFormat_()
         {
             return _utf8preparedFormat_.Format(
                 _byte, _dt, _dto, _decimal, _double, _guid, _short, _float, _ts, _uint, _ulong, _null, _string, _bool, _enum, _char);
         }

-        [BenchmarkCategory("Utf8PreparedFormat"), Benchmark]
+        //[BenchmarkCategory("Utf8PreparedFormat"), Benchmark]
         public string Utf8PreparedFormatN()
         {
             return _utf8preparedFormatN.Format(
@@ -154,7 +154,7 @@ namespace BenchmarkVsReleasedVersion
             return zsh.Length;
         }

-        [BenchmarkCategory("Utf8StringBuilderAppendFormat"), Benchmark(Baseline = true)]
+        //[BenchmarkCategory("Utf8StringBuilderAppendFormat"), Benchmark(Baseline = true)]
         public int Utf8StringBuilderAppendFormat_()
         {
             using var zsh = ZString.CreateUtf8StringBuilder();
@@ -163,7 +163,7 @@ namespace BenchmarkVsReleasedVersion
             return zsh.Length;
         }

-        [BenchmarkCategory("Utf8StringBuilderAppendFormat"), Benchmark]
+        //[BenchmarkCategory("Utf8StringBuilderAppendFormat"), Benchmark]
         public int Utf8StringBuilderAppendFormatN()
         {
             using var zsh = NZString.CreateUtf8StringBuilder();
diff --git a/sandbox/BenchmarkVsReleasedVersion/FormatBenchmark.cs b/sandbox/BenchmarkVsReleasedVersion/FormatBenchmark.cs
index 14a49c2..a6326cb 100644
--- a/sandbox/BenchmarkVsReleasedVersion/FormatBenchmark.cs
+++ b/sandbox/BenchmarkVsReleasedVersion/FormatBenchmark.cs
@@ -61,13 +61,13 @@ namespace BenchmarkVsReleasedVersion
             return NZString.Format(FormatString, x, y);
         }

-        [BenchmarkCategory("CreatePreparedFormat"), Benchmark(Baseline = true)]
+        //[BenchmarkCategory("CreatePreparedFormat"), Benchmark(Baseline = true)]
         public object CreatePreparedFormat_()
         {
             return new Utf16PreparedFormat<int, int>(FormatString);
         }

-        [BenchmarkCategory("CreatePreparedFormat"), Benchmark]
+        //[BenchmarkCategory("CreatePreparedFormat"), Benchmark]
         public object CreatePreparedFormatN()
         {
             return new NewZString::Cysharp.Text.Utf16PreparedFormat<int, int>(FormatString);
@@ -85,13 +85,13 @@ namespace BenchmarkVsReleasedVersion
             return _utf16preparedFormatN.Format(x, y);
         }

-        [BenchmarkCategory("Utf8PreparedFormat"), Benchmark(Baseline = true)]
+        //[BenchmarkCategory("Utf8PreparedFormat"), Benchmark(Baseline = true)]
         public string Utf8PreparedFormat_()
         {
             return _utf8preparedFormat_.Format(x, y);
         }

-        [BenchmarkCategory("Utf8PreparedFormat"), Benchmark]
+        //[BenchmarkCategory("Utf8PreparedFormat"), Benchmark]
         public string Utf8PreparedFormatN()
         {
             return _utf8preparedFormatN.Format(x, y);
@@ -113,7 +113,7 @@ namespace BenchmarkVsReleasedVersion
             return zsh.Length;
         }

-        [BenchmarkCategory("Utf8StringBuilderAppendFormat"), Benchmark(Baseline = true)]
+        //[BenchmarkCategory("Utf8StringBuilderAppendFormat"), Benchmark(Baseline = true)]
         public int Utf8StringBuilderAppendFormat_()
         {
             using var zsh = ZString.CreateUtf8StringBuilder();
@@ -121,7 +121,7 @@ namespace BenchmarkVsReleasedVersion
             return zsh.Length;
         }

-        [BenchmarkCategory("Utf8StringBuilderAppendFormat"), Benchmark]
+        //[BenchmarkCategory("Utf8StringBuilderAppendFormat"), Benchmark]
         public int Utf8StringBuilderAppendFormatN()
         {
             using var zsh = NZString.CreateUtf8StringBuilder();
diff --git a/sandbox/BenchmarkVsReleasedVersion/Program.cs b/sandbox/BenchmarkVsReleasedVersion/Program.cs
index 45ee687..8574158 100644
--- a/sandbox/BenchmarkVsReleasedVersion/Program.cs
+++ b/sandbox/BenchmarkVsReleasedVersion/Program.cs
@@ -19,7 +19,8 @@ namespace BenchmarkVsReleasedVersion
         public BenchmarkConfig()
         {
             AddDiagnoser(MemoryDiagnoser.Default);
-            AddJob(Job.ShortRun.WithWarmupCount(1).WithIterationCount(1));
+            //AddJob(Job.ShortRun.WithWarmupCount(1).WithIterationCount(1));
+            AddJob(Job.Default);
         }
     }

diff --git a/src/ZString.Unity/Assets/Scripts/ZString/PreparedFormat.cs b/src/ZString.Unity/Assets/Scripts/ZString/PreparedFormat.cs
index c343a57..d7232bd 100644
--- a/src/ZString.Unity/Assets/Scripts/ZString/PreparedFormat.cs
+++ b/src/ZString.Unity/Assets/Scripts/ZString/PreparedFormat.cs
@@ -4,7 +4,7 @@ using System.Buffers;

 namespace Cysharp.Text
 {
-    public sealed partial class Utf16PreparedFormat<T1>
+    public sealed unsafe partial class Utf16PreparedFormat<T1>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -80,7 +80,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf16PreparedFormat<T1, T2>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -171,7 +171,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf16PreparedFormat<T1, T2, T3>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -277,7 +277,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf16PreparedFormat<T1, T2, T3, T4>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3, T4>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -398,7 +398,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf16PreparedFormat<T1, T2, T3, T4, T5>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3, T4, T5>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -534,7 +534,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -685,7 +685,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -851,7 +851,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -1032,7 +1032,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -1228,7 +1228,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -1439,7 +1439,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -1665,7 +1665,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -1906,7 +1906,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -2162,7 +2162,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -2433,7 +2433,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -2719,7 +2719,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -3020,7 +3020,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf8PreparedFormat<T1>
+    public sealed unsafe partial class Utf8PreparedFormat<T1>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -3097,7 +3097,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf8PreparedFormat<T1, T2>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -3189,7 +3189,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf8PreparedFormat<T1, T2, T3>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -3296,7 +3296,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf8PreparedFormat<T1, T2, T3, T4>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3, T4>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -3418,7 +3418,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf8PreparedFormat<T1, T2, T3, T4, T5>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3, T4, T5>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -3555,7 +3555,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -3707,7 +3707,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -3874,7 +3874,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -4056,7 +4056,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -4253,7 +4253,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -4465,7 +4465,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -4692,7 +4692,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -4934,7 +4934,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -5191,7 +5191,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -5463,7 +5463,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -5750,7 +5750,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16>
     {
         public string FormatString { get; }
         public int MinSize { get; }
diff --git a/src/ZString.Unity/Assets/Scripts/ZString/Utf16/Utf16ValueStringBuilder.AppendFormat.cs b/src/ZString.Unity/Assets/Scripts/ZString/Utf16/Utf16ValueStringBuilder.AppendFormat.cs
index 515186d..9afcc41 100644
--- a/src/ZString.Unity/Assets/Scripts/ZString/Utf16/Utf16ValueStringBuilder.AppendFormat.cs
+++ b/src/ZString.Unity/Assets/Scripts/ZString/Utf16/Utf16ValueStringBuilder.AppendFormat.cs
@@ -2,7 +2,7 @@

 namespace Cysharp.Text
 {
-    public partial struct Utf16ValueStringBuilder
+    public unsafe partial struct Utf16ValueStringBuilder
     {
         /// <summary>Appends the string returned by processing a composite format string, each format item is replaced by the string representation of arguments.</summary>
         public void AppendFormat<T1>(string format, T1 arg1)
diff --git a/src/ZString.Unity/Assets/Scripts/ZString/Utf16/Utf16ValueStringBuilder.CreateFormatter.cs b/src/ZString.Unity/Assets/Scripts/ZString/Utf16/Utf16ValueStringBuilder.CreateFormatter.cs
index 8d939c7..77b85ea 100644
--- a/src/ZString.Unity/Assets/Scripts/ZString/Utf16/Utf16ValueStringBuilder.CreateFormatter.cs
+++ b/src/ZString.Unity/Assets/Scripts/ZString/Utf16/Utf16ValueStringBuilder.CreateFormatter.cs
@@ -1,146 +1,167 @@
 ・ソusing System;
+using System.Runtime.CompilerServices;

 namespace Cysharp.Text
 {
     public partial struct Utf16ValueStringBuilder
     {
-        static object CreateFormatter(Type type)
+        static unsafe void RegisterPrimitives()
         {
-            if (type == typeof(System.SByte))
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool SByteFunc(System.SByte x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return new TryFormat<System.SByte>((System.SByte x, Span<char> dest, out int written, ReadOnlySpan<char> format) => format.Length == 0 ? FastNumberWriter.TryWriteInt64(dest, out written, x) : x.TryFormat(dest, out written, format));
+                return format.Length == 0 ? FastNumberWriter.TryWriteInt64(dest, out written, x) : x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.Int16))
-            {
-                return new TryFormat<System.Int16>((System.Int16 x, Span<char> dest, out int written, ReadOnlySpan<char> format) => format.Length == 0 ? FastNumberWriter.TryWriteInt64(dest, out written, x) : x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.Int32))
-            {
-                return new TryFormat<System.Int32>((System.Int32 x, Span<char> dest, out int written, ReadOnlySpan<char> format) => format.Length == 0 ? FastNumberWriter.TryWriteInt64(dest, out written, x) : x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.Int64))
-            {
-                return new TryFormat<System.Int64>((System.Int64 x, Span<char> dest, out int written, ReadOnlySpan<char> format) => format.Length == 0 ? FastNumberWriter.TryWriteInt64(dest, out written, x) : x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.Byte))
-            {
-                return new TryFormat<System.Byte>((System.Byte x, Span<char> dest, out int written, ReadOnlySpan<char> format) => format.Length == 0 ? FastNumberWriter.TryWriteUInt64(dest, out written, x) : x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.UInt16))
-            {
-                return new TryFormat<System.UInt16>((System.UInt16 x, Span<char> dest, out int written, ReadOnlySpan<char> format) => format.Length == 0 ? FastNumberWriter.TryWriteUInt64(dest, out written, x) : x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.UInt32))
-            {
-                return new TryFormat<System.UInt32>((System.UInt32 x, Span<char> dest, out int written, ReadOnlySpan<char> format) => format.Length == 0 ? FastNumberWriter.TryWriteUInt64(dest, out written, x) : x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.UInt64))
-            {
-                return new TryFormat<System.UInt64>((System.UInt64 x, Span<char> dest, out int written, ReadOnlySpan<char> format) => format.Length == 0 ? FastNumberWriter.TryWriteUInt64(dest, out written, x) : x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.Single))
-            {
-                return new TryFormat<System.Single>((System.Single x, Span<char> dest, out int written, ReadOnlySpan<char> format) => x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.Double))
-            {
-                return new TryFormat<System.Double>((System.Double x, Span<char> dest, out int written, ReadOnlySpan<char> format) => x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.TimeSpan))
-            {
-                return new TryFormat<System.TimeSpan>((System.TimeSpan x, Span<char> dest, out int written, ReadOnlySpan<char> format) => x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.DateTime))
-            {
-                return new TryFormat<System.DateTime>((System.DateTime x, Span<char> dest, out int written, ReadOnlySpan<char> format) => x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.DateTimeOffset))
-            {
-                return new TryFormat<System.DateTimeOffset>((System.DateTimeOffset x, Span<char> dest, out int written, ReadOnlySpan<char> format) => x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.Decimal))
-            {
-                return new TryFormat<System.Decimal>((System.Decimal x, Span<char> dest, out int written, ReadOnlySpan<char> format) => x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.Guid))
-            {
-                return new TryFormat<System.Guid>((System.Guid x, Span<char> dest, out int written, ReadOnlySpan<char> format) => x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.Byte?))
-            {
-                return CreateNullableFormatter<System.Byte>();
-            }
-            if (type == typeof(System.DateTime?))
+            FormatterCache<System.SByte>.TryFormatDelegate = &SByteFunc;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool Int16Func(System.Int16 x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.DateTime>();
+                return format.Length == 0 ? FastNumberWriter.TryWriteInt64(dest, out written, x) : x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.DateTimeOffset?))
+            FormatterCache<System.Int16>.TryFormatDelegate = &Int16Func;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool Int32Func(System.Int32 x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.DateTimeOffset>();
+                return format.Length == 0 ? FastNumberWriter.TryWriteInt64(dest, out written, x) : x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.Decimal?))
+            FormatterCache<System.Int32>.TryFormatDelegate = &Int32Func;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool Int64Func(System.Int64 x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.Decimal>();
+                return format.Length == 0 ? FastNumberWriter.TryWriteInt64(dest, out written, x) : x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.Double?))
+            FormatterCache<System.Int64>.TryFormatDelegate = &Int64Func;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool ByteFunc(System.Byte x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.Double>();
+                return format.Length == 0 ? FastNumberWriter.TryWriteUInt64(dest, out written, x) : x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.Int16?))
+            FormatterCache<System.Byte>.TryFormatDelegate = &ByteFunc;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool UInt16Func(System.UInt16 x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.Int16>();
+                return format.Length == 0 ? FastNumberWriter.TryWriteUInt64(dest, out written, x) : x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.Int32?))
+            FormatterCache<System.UInt16>.TryFormatDelegate = &UInt16Func;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool UInt32Func(System.UInt32 x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.Int32>();
+                return format.Length == 0 ? FastNumberWriter.TryWriteUInt64(dest, out written, x) : x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.Int64?))
+            FormatterCache<System.UInt32>.TryFormatDelegate = &UInt32Func;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool UInt64Func(System.UInt64 x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.Int64>();
+                return format.Length == 0 ? FastNumberWriter.TryWriteUInt64(dest, out written, x) : x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.SByte?))
+            FormatterCache<System.UInt64>.TryFormatDelegate = &UInt64Func;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool SingleFunc(System.Single x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.SByte>();
+                return x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.Single?))
+            FormatterCache<System.Single>.TryFormatDelegate = &SingleFunc;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool DoubleFunc(System.Double x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.Single>();
+                return x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.TimeSpan?))
+            FormatterCache<System.Double>.TryFormatDelegate = &DoubleFunc;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool TimeSpanFunc(System.TimeSpan x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.TimeSpan>();
+                return x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.UInt16?))
+            FormatterCache<System.TimeSpan>.TryFormatDelegate = &TimeSpanFunc;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool DateTimeFunc(System.DateTime x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.UInt16>();
+                return x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.UInt32?))
+            FormatterCache<System.DateTime>.TryFormatDelegate = &DateTimeFunc;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool DateTimeOffsetFunc(System.DateTimeOffset x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.UInt32>();
+                return x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.UInt64?))
+            FormatterCache<System.DateTimeOffset>.TryFormatDelegate = &DateTimeOffsetFunc;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool DecimalFunc(System.Decimal x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.UInt64>();
+                return x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.Guid?))
+            FormatterCache<System.Decimal>.TryFormatDelegate = &DecimalFunc;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool GuidFunc(System.Guid x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.Guid>();
+                return x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.IntPtr))
+            FormatterCache<System.Guid>.TryFormatDelegate = &GuidFunc;
+
+            FormatterCache<System.Byte?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.DateTime?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.DateTimeOffset?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.Decimal?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.Double?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.Int16?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.Int32?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.Int64?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.SByte?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.Single?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.TimeSpan?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.UInt16?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.UInt32?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.UInt64?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.Guid?>.TryFormatDelegate = &NullableFormat;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool IntPtrFunc(System.IntPtr x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
                 // ignore format
-                return new TryFormat<System.IntPtr>((System.IntPtr x, Span<char> dest, out int written, ReadOnlySpan<char> _) => System.IntPtr.Size == 4
+                return  System.IntPtr.Size == 4
                     ? x.ToInt32().TryFormat(dest, out written, default)
-                    : x.ToInt64().TryFormat(dest, out written, default));
+                    : x.ToInt64().TryFormat(dest, out written, default);
             }
-            if (type == typeof(System.UIntPtr))
+            FormatterCache<System.IntPtr>.TryFormatDelegate = &IntPtrFunc;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool UIntPtrFunc(System.UIntPtr x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
                 // ignore format
-                return new TryFormat<System.UIntPtr>((System.UIntPtr x, Span<char> dest, out int written, ReadOnlySpan<char> _) => System.UIntPtr.Size == 4
+                return  System.UIntPtr.Size == 4
                     ? x.ToUInt32().TryFormat(dest, out written, default)
-                    : x.ToUInt64().TryFormat(dest, out written, default));
+                    : x.ToUInt64().TryFormat(dest, out written, default);
             }
-            return null;
+            FormatterCache<System.UIntPtr>.TryFormatDelegate = &UIntPtrFunc;
+
         }
     }
 }
\ No newline at end of file
diff --git a/src/ZString.Unity/Assets/Scripts/ZString/Utf16ValueStringBuilder.cs b/src/ZString.Unity/Assets/Scripts/ZString/Utf16ValueStringBuilder.cs
index 7a4368a..1b9ad83 100644
--- a/src/ZString.Unity/Assets/Scripts/ZString/Utf16ValueStringBuilder.cs
+++ b/src/ZString.Unity/Assets/Scripts/ZString/Utf16ValueStringBuilder.cs
@@ -5,7 +5,7 @@ using System.Runtime.CompilerServices;

 namespace Cysharp.Text
 {
-    public partial struct Utf16ValueStringBuilder : IDisposable, IBufferWriter<char>, IResettableBufferWriter<char>
+    public unsafe partial struct Utf16ValueStringBuilder : IDisposable, IBufferWriter<char>, IResettableBufferWriter<char>
     {
         public delegate bool TryFormat<T>(T value, Span<char> destination, out int charsWritten, ReadOnlySpan<char> format);

@@ -32,6 +32,7 @@ namespace Cysharp.Text
                 newLine2 = newLine[1];
                 crlf = true;
             }
+            RegisterPrimitives();
         }

         [ThreadStatic]
@@ -232,7 +233,7 @@ namespace Cysharp.Text
         }

         /// <summary>Appends the string representation of a specified value to this instance.</summary>
-        public void Append<T>(T value)
+        public unsafe void Append<T>(T value)
         {
             if (!FormatterCache<T>.TryFormatDelegate(value, buffer.AsSpan(index), out var written, default))
             {
@@ -361,7 +362,7 @@ namespace Cysharp.Text
         /// are removed from this builder.
         /// </remarks>
         public void Replace(string oldValue, string newValue) => Replace(oldValue, newValue, 0, Length);
-        
+
         public void Replace(ReadOnlySpan<char> oldValue, ReadOnlySpan<char> newValue) => Replace(oldValue, newValue, 0, Length);

         /// <summary>
@@ -578,58 +579,62 @@ namespace Cysharp.Text
             index += written;
         }

+        static class CustomTryFormat<T>
+        {
+            public static TryFormat<T> formatMethod;
+            public static bool Dispatch(T value, Span<char> dest, out int written, ReadOnlySpan<char> format)
+            {
+                return formatMethod(value, dest, out written, format);
+            }
+        }
+
         /// <summary>
         /// Register custom formatter
         /// </summary>
         public static void RegisterTryFormat<T>(TryFormat<T> formatMethod)
         {
-            FormatterCache<T>.TryFormatDelegate = formatMethod;
+            CustomTryFormat<T>.formatMethod = formatMethod;
+            FormatterCache<T>.TryFormatDelegate = &CustomTryFormat<T>.Dispatch;
         }

-        static TryFormat<T?> CreateNullableFormatter<T>() where T : struct
+        static unsafe bool NullableFormat<T>(T? x, Span<char> dest, out int written, ReadOnlySpan<char> format) where T : struct
         {
-            return new TryFormat<T?>((T? x, Span<char> dest, out int written, ReadOnlySpan<char> format) =>
+            if (x == null)
             {
-                if (x == null)
-                {
-                    written = 0;
-                    return true;
-                }
-                return FormatterCache<T>.TryFormatDelegate(x.Value, dest, out written, format);
-            });
+                written = 0;
+                return true;
+            }
+            return FormatterCache<T>.TryFormatDelegate(x.Value, dest, out written, format);
         }

-        /// <summary>
-        /// Supports the Nullable type for a given struct type.
-        /// </summary>
-        public static void EnableNullableFormat<T>() where T : struct
+        public unsafe static class FormatterCache<T>
         {
-            RegisterTryFormat<T?>(CreateNullableFormatter<T>());
-        }
+            private static unsafe delegate*<T, Span<char>, out int, ReadOnlySpan<char>, bool> _TryFormatDelegate;

-        public static class FormatterCache<T>
-        {
-            public static TryFormat<T> TryFormatDelegate;
-            static FormatterCache()
+            public static delegate*<T , Span<char> , out int , ReadOnlySpan<char> , bool> TryFormatDelegate
             {
-                var formatter = (TryFormat<T>)CreateFormatter(typeof(T));
-                if (formatter == null)
+                get
                 {
-                    if (typeof(T).IsEnum)
+                    if (_TryFormatDelegate == null)
                     {
-                        formatter = new TryFormat<T>(EnumUtil<T>.TryFormatUtf16);
-                    }
-                    else if (typeof(T) == typeof(string))
-                    {
-                        formatter = new TryFormat<T>(TryFormatString);
-                    }
-                    else
-                    {
-                        formatter = new TryFormat<T>(TryFormatDefault);
+                        if (typeof(T).IsEnum)
+                        {
+                            _TryFormatDelegate = &EnumUtil<T>.TryFormatUtf16;
+                        }
+                        else if (typeof(T) == typeof(string))
+                        {
+                            _TryFormatDelegate = &TryFormatString;
+                        }
+                        else
+                        {
+                            _TryFormatDelegate = &TryFormatDefault;
+                        }
                     }
+
+                    return _TryFormatDelegate;
                 }

-                TryFormatDelegate = formatter;
+                set => _TryFormatDelegate = value;
             }

             static bool TryFormatString(T value, Span<char> dest, out int written, ReadOnlySpan<char> format)
diff --git a/src/ZString/PreparedFormat.cs b/src/ZString/PreparedFormat.cs
index c343a57..d7232bd 100644
--- a/src/ZString/PreparedFormat.cs
+++ b/src/ZString/PreparedFormat.cs
@@ -4,7 +4,7 @@ using System.Buffers;

 namespace Cysharp.Text
 {
-    public sealed partial class Utf16PreparedFormat<T1>
+    public sealed unsafe partial class Utf16PreparedFormat<T1>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -80,7 +80,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf16PreparedFormat<T1, T2>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -171,7 +171,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf16PreparedFormat<T1, T2, T3>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -277,7 +277,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf16PreparedFormat<T1, T2, T3, T4>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3, T4>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -398,7 +398,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf16PreparedFormat<T1, T2, T3, T4, T5>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3, T4, T5>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -534,7 +534,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -685,7 +685,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -851,7 +851,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -1032,7 +1032,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -1228,7 +1228,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -1439,7 +1439,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -1665,7 +1665,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -1906,7 +1906,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -2162,7 +2162,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -2433,7 +2433,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -2719,7 +2719,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -3020,7 +3020,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf8PreparedFormat<T1>
+    public sealed unsafe partial class Utf8PreparedFormat<T1>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -3097,7 +3097,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf8PreparedFormat<T1, T2>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -3189,7 +3189,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf8PreparedFormat<T1, T2, T3>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -3296,7 +3296,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf8PreparedFormat<T1, T2, T3, T4>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3, T4>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -3418,7 +3418,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf8PreparedFormat<T1, T2, T3, T4, T5>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3, T4, T5>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -3555,7 +3555,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -3707,7 +3707,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -3874,7 +3874,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -4056,7 +4056,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -4253,7 +4253,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -4465,7 +4465,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -4692,7 +4692,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -4934,7 +4934,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -5191,7 +5191,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -5463,7 +5463,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -5750,7 +5750,7 @@ namespace Cysharp.Text
         }
     }

-    public sealed partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16>
     {
         public string FormatString { get; }
         public int MinSize { get; }
diff --git a/src/ZString/PreparedFormat.tt b/src/ZString/PreparedFormat.tt
index 4087bea..e3af3be 100644
--- a/src/ZString/PreparedFormat.tt
+++ b/src/ZString/PreparedFormat.tt
@@ -13,7 +13,7 @@ namespace Cysharp.Text
 {
 <# foreach(var utf in utfTypes) { var isUtf16 = (utf == "Utf16"); #>
 <# for(var i = 1; i <= TypeParamMax; i++) { #>
-    public sealed partial class <#= utf #>PreparedFormat<<#= CreateTypeArgument(i) #>>
+    public sealed unsafe partial class <#= utf #>PreparedFormat<<#= CreateTypeArgument(i) #>>
     {
         public string FormatString { get; }
         public int MinSize { get; }
diff --git a/src/ZString/Utf16/Utf16ValueStringBuilder.AppendFormat.cs b/src/ZString/Utf16/Utf16ValueStringBuilder.AppendFormat.cs
index 515186d..9afcc41 100644
--- a/src/ZString/Utf16/Utf16ValueStringBuilder.AppendFormat.cs
+++ b/src/ZString/Utf16/Utf16ValueStringBuilder.AppendFormat.cs
@@ -2,7 +2,7 @@

 namespace Cysharp.Text
 {
-    public partial struct Utf16ValueStringBuilder
+    public unsafe partial struct Utf16ValueStringBuilder
     {
         /// <summary>Appends the string returned by processing a composite format string, each format item is replaced by the string representation of arguments.</summary>
         public void AppendFormat<T1>(string format, T1 arg1)
diff --git a/src/ZString/Utf16/Utf16ValueStringBuilder.AppendFormat.tt b/src/ZString/Utf16/Utf16ValueStringBuilder.AppendFormat.tt
index 12b53b5..474d8b0 100644
--- a/src/ZString/Utf16/Utf16ValueStringBuilder.AppendFormat.tt
+++ b/src/ZString/Utf16/Utf16ValueStringBuilder.AppendFormat.tt
@@ -9,7 +9,7 @@ using System;

 namespace Cysharp.Text
 {
-    public partial struct Utf16ValueStringBuilder
+    public unsafe partial struct Utf16ValueStringBuilder
     {
 <# for(var i = 1; i <= TypeParamMax; i++) { #>
         /// <summary>Appends the string returned by processing a composite format string, each format item is replaced by the string representation of arguments.</summary>
diff --git a/src/ZString/Utf16/Utf16ValueStringBuilder.CreateFormatter.cs b/src/ZString/Utf16/Utf16ValueStringBuilder.CreateFormatter.cs
index 8d939c7..77b85ea 100644
--- a/src/ZString/Utf16/Utf16ValueStringBuilder.CreateFormatter.cs
+++ b/src/ZString/Utf16/Utf16ValueStringBuilder.CreateFormatter.cs
@@ -1,146 +1,167 @@
 ・ソusing System;
+using System.Runtime.CompilerServices;

 namespace Cysharp.Text
 {
     public partial struct Utf16ValueStringBuilder
     {
-        static object CreateFormatter(Type type)
+        static unsafe void RegisterPrimitives()
         {
-            if (type == typeof(System.SByte))
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool SByteFunc(System.SByte x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return new TryFormat<System.SByte>((System.SByte x, Span<char> dest, out int written, ReadOnlySpan<char> format) => format.Length == 0 ? FastNumberWriter.TryWriteInt64(dest, out written, x) : x.TryFormat(dest, out written, format));
+                return format.Length == 0 ? FastNumberWriter.TryWriteInt64(dest, out written, x) : x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.Int16))
-            {
-                return new TryFormat<System.Int16>((System.Int16 x, Span<char> dest, out int written, ReadOnlySpan<char> format) => format.Length == 0 ? FastNumberWriter.TryWriteInt64(dest, out written, x) : x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.Int32))
-            {
-                return new TryFormat<System.Int32>((System.Int32 x, Span<char> dest, out int written, ReadOnlySpan<char> format) => format.Length == 0 ? FastNumberWriter.TryWriteInt64(dest, out written, x) : x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.Int64))
-            {
-                return new TryFormat<System.Int64>((System.Int64 x, Span<char> dest, out int written, ReadOnlySpan<char> format) => format.Length == 0 ? FastNumberWriter.TryWriteInt64(dest, out written, x) : x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.Byte))
-            {
-                return new TryFormat<System.Byte>((System.Byte x, Span<char> dest, out int written, ReadOnlySpan<char> format) => format.Length == 0 ? FastNumberWriter.TryWriteUInt64(dest, out written, x) : x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.UInt16))
-            {
-                return new TryFormat<System.UInt16>((System.UInt16 x, Span<char> dest, out int written, ReadOnlySpan<char> format) => format.Length == 0 ? FastNumberWriter.TryWriteUInt64(dest, out written, x) : x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.UInt32))
-            {
-                return new TryFormat<System.UInt32>((System.UInt32 x, Span<char> dest, out int written, ReadOnlySpan<char> format) => format.Length == 0 ? FastNumberWriter.TryWriteUInt64(dest, out written, x) : x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.UInt64))
-            {
-                return new TryFormat<System.UInt64>((System.UInt64 x, Span<char> dest, out int written, ReadOnlySpan<char> format) => format.Length == 0 ? FastNumberWriter.TryWriteUInt64(dest, out written, x) : x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.Single))
-            {
-                return new TryFormat<System.Single>((System.Single x, Span<char> dest, out int written, ReadOnlySpan<char> format) => x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.Double))
-            {
-                return new TryFormat<System.Double>((System.Double x, Span<char> dest, out int written, ReadOnlySpan<char> format) => x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.TimeSpan))
-            {
-                return new TryFormat<System.TimeSpan>((System.TimeSpan x, Span<char> dest, out int written, ReadOnlySpan<char> format) => x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.DateTime))
-            {
-                return new TryFormat<System.DateTime>((System.DateTime x, Span<char> dest, out int written, ReadOnlySpan<char> format) => x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.DateTimeOffset))
-            {
-                return new TryFormat<System.DateTimeOffset>((System.DateTimeOffset x, Span<char> dest, out int written, ReadOnlySpan<char> format) => x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.Decimal))
-            {
-                return new TryFormat<System.Decimal>((System.Decimal x, Span<char> dest, out int written, ReadOnlySpan<char> format) => x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.Guid))
-            {
-                return new TryFormat<System.Guid>((System.Guid x, Span<char> dest, out int written, ReadOnlySpan<char> format) => x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.Byte?))
-            {
-                return CreateNullableFormatter<System.Byte>();
-            }
-            if (type == typeof(System.DateTime?))
+            FormatterCache<System.SByte>.TryFormatDelegate = &SByteFunc;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool Int16Func(System.Int16 x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.DateTime>();
+                return format.Length == 0 ? FastNumberWriter.TryWriteInt64(dest, out written, x) : x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.DateTimeOffset?))
+            FormatterCache<System.Int16>.TryFormatDelegate = &Int16Func;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool Int32Func(System.Int32 x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.DateTimeOffset>();
+                return format.Length == 0 ? FastNumberWriter.TryWriteInt64(dest, out written, x) : x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.Decimal?))
+            FormatterCache<System.Int32>.TryFormatDelegate = &Int32Func;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool Int64Func(System.Int64 x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.Decimal>();
+                return format.Length == 0 ? FastNumberWriter.TryWriteInt64(dest, out written, x) : x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.Double?))
+            FormatterCache<System.Int64>.TryFormatDelegate = &Int64Func;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool ByteFunc(System.Byte x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.Double>();
+                return format.Length == 0 ? FastNumberWriter.TryWriteUInt64(dest, out written, x) : x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.Int16?))
+            FormatterCache<System.Byte>.TryFormatDelegate = &ByteFunc;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool UInt16Func(System.UInt16 x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.Int16>();
+                return format.Length == 0 ? FastNumberWriter.TryWriteUInt64(dest, out written, x) : x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.Int32?))
+            FormatterCache<System.UInt16>.TryFormatDelegate = &UInt16Func;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool UInt32Func(System.UInt32 x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.Int32>();
+                return format.Length == 0 ? FastNumberWriter.TryWriteUInt64(dest, out written, x) : x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.Int64?))
+            FormatterCache<System.UInt32>.TryFormatDelegate = &UInt32Func;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool UInt64Func(System.UInt64 x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.Int64>();
+                return format.Length == 0 ? FastNumberWriter.TryWriteUInt64(dest, out written, x) : x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.SByte?))
+            FormatterCache<System.UInt64>.TryFormatDelegate = &UInt64Func;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool SingleFunc(System.Single x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.SByte>();
+                return x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.Single?))
+            FormatterCache<System.Single>.TryFormatDelegate = &SingleFunc;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool DoubleFunc(System.Double x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.Single>();
+                return x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.TimeSpan?))
+            FormatterCache<System.Double>.TryFormatDelegate = &DoubleFunc;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool TimeSpanFunc(System.TimeSpan x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.TimeSpan>();
+                return x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.UInt16?))
+            FormatterCache<System.TimeSpan>.TryFormatDelegate = &TimeSpanFunc;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool DateTimeFunc(System.DateTime x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.UInt16>();
+                return x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.UInt32?))
+            FormatterCache<System.DateTime>.TryFormatDelegate = &DateTimeFunc;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool DateTimeOffsetFunc(System.DateTimeOffset x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.UInt32>();
+                return x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.UInt64?))
+            FormatterCache<System.DateTimeOffset>.TryFormatDelegate = &DateTimeOffsetFunc;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool DecimalFunc(System.Decimal x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.UInt64>();
+                return x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.Guid?))
+            FormatterCache<System.Decimal>.TryFormatDelegate = &DecimalFunc;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool GuidFunc(System.Guid x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.Guid>();
+                return x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.IntPtr))
+            FormatterCache<System.Guid>.TryFormatDelegate = &GuidFunc;
+
+            FormatterCache<System.Byte?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.DateTime?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.DateTimeOffset?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.Decimal?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.Double?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.Int16?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.Int32?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.Int64?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.SByte?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.Single?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.TimeSpan?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.UInt16?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.UInt32?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.UInt64?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.Guid?>.TryFormatDelegate = &NullableFormat;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool IntPtrFunc(System.IntPtr x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
                 // ignore format
-                return new TryFormat<System.IntPtr>((System.IntPtr x, Span<char> dest, out int written, ReadOnlySpan<char> _) => System.IntPtr.Size == 4
+                return  System.IntPtr.Size == 4
                     ? x.ToInt32().TryFormat(dest, out written, default)
-                    : x.ToInt64().TryFormat(dest, out written, default));
+                    : x.ToInt64().TryFormat(dest, out written, default);
             }
-            if (type == typeof(System.UIntPtr))
+            FormatterCache<System.IntPtr>.TryFormatDelegate = &IntPtrFunc;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool UIntPtrFunc(System.UIntPtr x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
                 // ignore format
-                return new TryFormat<System.UIntPtr>((System.UIntPtr x, Span<char> dest, out int written, ReadOnlySpan<char> _) => System.UIntPtr.Size == 4
+                return  System.UIntPtr.Size == 4
                     ? x.ToUInt32().TryFormat(dest, out written, default)
-                    : x.ToUInt64().TryFormat(dest, out written, default));
+                    : x.ToUInt64().TryFormat(dest, out written, default);
             }
-            return null;
+            FormatterCache<System.UIntPtr>.TryFormatDelegate = &UIntPtrFunc;
+
         }
     }
 }
\ No newline at end of file
diff --git a/src/ZString/Utf16/Utf16ValueStringBuilder.CreateFormatter.tt b/src/ZString/Utf16/Utf16ValueStringBuilder.CreateFormatter.tt
index 0ccd705..3684c0f 100644
--- a/src/ZString/Utf16/Utf16ValueStringBuilder.CreateFormatter.tt
+++ b/src/ZString/Utf16/Utf16ValueStringBuilder.CreateFormatter.tt
@@ -32,47 +32,57 @@
     };
 #>
 using System;
+using System.Runtime.CompilerServices;

 namespace Cysharp.Text
 {
     public partial struct Utf16ValueStringBuilder
     {
-        static object CreateFormatter(Type type)
+        static unsafe void RegisterPrimitives()
         {
 <# foreach(var t in spanFormattablesA) { #>
-            if (type == typeof(<#= t.FullName #>))
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool <#= t.Name #>Func(<#= t.FullName #> x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return new TryFormat<<#= t.FullName #>>((<#= t.FullName #> x, Span<char> dest, out int written, ReadOnlySpan<char> format) => format.Length == 0 ? FastNumberWriter.TryWriteInt64(dest, out written, x) : x.TryFormat(dest, out written, format));
+                return format.Length == 0 ? FastNumberWriter.TryWriteInt64(dest, out written, x) : x.TryFormat(dest, out written, format);
             }
+            FormatterCache<<#= t.FullName #>>.TryFormatDelegate = &<#= t.Name #>Func;
+
 <# } #>
 <# foreach(var t in spanFormattablesB) { #>
-            if (type == typeof(<#= t.FullName #>))
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool <#= t.Name #>Func(<#= t.FullName #> x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return new TryFormat<<#= t.FullName #>>((<#= t.FullName #> x, Span<char> dest, out int written, ReadOnlySpan<char> format) => format.Length == 0 ? FastNumberWriter.TryWriteUInt64(dest, out written, x) : x.TryFormat(dest, out written, format));
+                return format.Length == 0 ? FastNumberWriter.TryWriteUInt64(dest, out written, x) : x.TryFormat(dest, out written, format);
             }
+            FormatterCache<<#= t.FullName #>>.TryFormatDelegate = &<#= t.Name #>Func;
+
 <# } #>
 <# foreach(var t in spanFormattablesC) { #>
-            if (type == typeof(<#= t.FullName #>))
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool <#= t.Name #>Func(<#= t.FullName #> x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return new TryFormat<<#= t.FullName #>>((<#= t.FullName #> x, Span<char> dest, out int written, ReadOnlySpan<char> format) => x.TryFormat(dest, out written, format));
+                return x.TryFormat(dest, out written, format);
             }
+            FormatterCache<<#= t.FullName #>>.TryFormatDelegate = &<#= t.Name #>Func;
+
 <# } #>
 <# foreach(var t in spanFormattables) { #>
-            if (type == typeof(<#= t.FullName #>?))
-            {
-                return CreateNullableFormatter<<#= t.FullName #>>();
-            }
+            FormatterCache<<#= t.FullName #>?>.TryFormatDelegate = &NullableFormat;
+
 <# } #>
-<# foreach(var t in new [] {typeof(IntPtr), typeof(UIntPtr)}) { var u = t == typeof(UIntPtr);  #>
-            if (type == typeof(<#= t.FullName #>))
+<# foreach(var t in new [] {typeof(IntPtr), typeof(UIntPtr)} ) { var u = t == typeof(UIntPtr);  #>
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool <#= t.Name #>Func(<#= t.FullName #> x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
                 // ignore format
-                return new TryFormat<<#= t.FullName #>>((<#= t.FullName #> x, Span<char> dest, out int written, ReadOnlySpan<char> _) => <#= t #>.Size == 4
+                return  <#= t #>.Size == 4
                     ? x.To<#= u ? "U" : "" #>Int32().TryFormat(dest, out written, default)
-                    : x.To<#= u ? "U" : "" #>Int64().TryFormat(dest, out written, default));
+                    : x.To<#= u ? "U" : "" #>Int64().TryFormat(dest, out written, default);
             }
+            FormatterCache<<#= t.FullName #>>.TryFormatDelegate = &<#= t.Name #>Func;
+
 <# } #>
-            return null;
         }
     }
 }
\ No newline at end of file
diff --git a/src/ZString/Utf16ValueStringBuilder.cs b/src/ZString/Utf16ValueStringBuilder.cs
index 7a4368a..1b9ad83 100644
--- a/src/ZString/Utf16ValueStringBuilder.cs
+++ b/src/ZString/Utf16ValueStringBuilder.cs
@@ -5,7 +5,7 @@ using System.Runtime.CompilerServices;

 namespace Cysharp.Text
 {
-    public partial struct Utf16ValueStringBuilder : IDisposable, IBufferWriter<char>, IResettableBufferWriter<char>
+    public unsafe partial struct Utf16ValueStringBuilder : IDisposable, IBufferWriter<char>, IResettableBufferWriter<char>
     {
         public delegate bool TryFormat<T>(T value, Span<char> destination, out int charsWritten, ReadOnlySpan<char> format);

@@ -32,6 +32,7 @@ namespace Cysharp.Text
                 newLine2 = newLine[1];
                 crlf = true;
             }
+            RegisterPrimitives();
         }

         [ThreadStatic]
@@ -232,7 +233,7 @@ namespace Cysharp.Text
         }

         /// <summary>Appends the string representation of a specified value to this instance.</summary>
-        public void Append<T>(T value)
+        public unsafe void Append<T>(T value)
         {
             if (!FormatterCache<T>.TryFormatDelegate(value, buffer.AsSpan(index), out var written, default))
             {
@@ -361,7 +362,7 @@ namespace Cysharp.Text
         /// are removed from this builder.
         /// </remarks>
         public void Replace(string oldValue, string newValue) => Replace(oldValue, newValue, 0, Length);
-        
+
         public void Replace(ReadOnlySpan<char> oldValue, ReadOnlySpan<char> newValue) => Replace(oldValue, newValue, 0, Length);

         /// <summary>
@@ -578,58 +579,62 @@ namespace Cysharp.Text
             index += written;
         }

+        static class CustomTryFormat<T>
+        {
+            public static TryFormat<T> formatMethod;
+            public static bool Dispatch(T value, Span<char> dest, out int written, ReadOnlySpan<char> format)
+            {
+                return formatMethod(value, dest, out written, format);
+            }
+        }
+
         /// <summary>
         /// Register custom formatter
         /// </summary>
         public static void RegisterTryFormat<T>(TryFormat<T> formatMethod)
         {
-            FormatterCache<T>.TryFormatDelegate = formatMethod;
+            CustomTryFormat<T>.formatMethod = formatMethod;
+            FormatterCache<T>.TryFormatDelegate = &CustomTryFormat<T>.Dispatch;
         }

-        static TryFormat<T?> CreateNullableFormatter<T>() where T : struct
+        static unsafe bool NullableFormat<T>(T? x, Span<char> dest, out int written, ReadOnlySpan<char> format) where T : struct
         {
-            return new TryFormat<T?>((T? x, Span<char> dest, out int written, ReadOnlySpan<char> format) =>
+            if (x == null)
             {
-                if (x == null)
-                {
-                    written = 0;
-                    return true;
-                }
-                return FormatterCache<T>.TryFormatDelegate(x.Value, dest, out written, format);
-            });
+                written = 0;
+                return true;
+            }
+            return FormatterCache<T>.TryFormatDelegate(x.Value, dest, out written, format);
         }

-        /// <summary>
-        /// Supports the Nullable type for a given struct type.
-        /// </summary>
-        public static void EnableNullableFormat<T>() where T : struct
+        public unsafe static class FormatterCache<T>
         {
-            RegisterTryFormat<T?>(CreateNullableFormatter<T>());
-        }
+            private static unsafe delegate*<T, Span<char>, out int, ReadOnlySpan<char>, bool> _TryFormatDelegate;

-        public static class FormatterCache<T>
-        {
-            public static TryFormat<T> TryFormatDelegate;
-            static FormatterCache()
+            public static delegate*<T , Span<char> , out int , ReadOnlySpan<char> , bool> TryFormatDelegate
             {
-                var formatter = (TryFormat<T>)CreateFormatter(typeof(T));
-                if (formatter == null)
+                get
                 {
-                    if (typeof(T).IsEnum)
+                    if (_TryFormatDelegate == null)
                     {
-                        formatter = new TryFormat<T>(EnumUtil<T>.TryFormatUtf16);
-                    }
-                    else if (typeof(T) == typeof(string))
-                    {
-                        formatter = new TryFormat<T>(TryFormatString);
-                    }
-                    else
-                    {
-                        formatter = new TryFormat<T>(TryFormatDefault);
+                        if (typeof(T).IsEnum)
+                        {
+                            _TryFormatDelegate = &EnumUtil<T>.TryFormatUtf16;
+                        }
+                        else if (typeof(T) == typeof(string))
+                        {
+                            _TryFormatDelegate = &TryFormatString;
+                        }
+                        else
+                        {
+                            _TryFormatDelegate = &TryFormatDefault;
+                        }
                     }
+
+                    return _TryFormatDelegate;
                 }

-                TryFormatDelegate = formatter;
+                set => _TryFormatDelegate = value;
             }

             static bool TryFormatString(T value, Span<char> dest, out int written, ReadOnlySpan<char> format)
diff --git a/src/ZString/ZString.csproj b/src/ZString/ZString.csproj
index eab6ce5..bb765cb 100644
--- a/src/ZString/ZString.csproj
+++ b/src/ZString/ZString.csproj
@@ -18,6 +18,7 @@
         <RepositoryUrl>$(PackageProjectUrl)</RepositoryUrl>
         <RepositoryType>git</RepositoryType>
         <PackageLicenseExpression>MIT</PackageLicenseExpression>
+      <LangVersion>9.0</LangVersion>
     </PropertyGroup>

     <ItemGroup>
@@ -187,6 +188,7 @@
     <!-- Copy files for Unity -->
     <PropertyGroup>
         <DestinationRoot>$(MSBuildProjectDirectory)\..\ZString.Unity\Assets\Scripts\ZString\</DestinationRoot>
+        <AssemblyName>NewZString</AssemblyName>
     </PropertyGroup>
     <ItemGroup>
         <TargetFiles1 Include="$(MSBuildProjectDirectory)\**\*.cs" Exclude="**\bin\**\*.*;**\obj\**\*.*;_InternalVisibleTo.cs" />
-- 
2.27.0.windows.1


8
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
8