byte[]をstringへ高速に変換する

生のバイト配列を文字列に変換したい場面は割とある。ログを残したいときとか。いくつもの方法があるようだが、単純に変換した場合と速度にこだわった実装とでどれくらいの差があるのか実際に確かめてみた。

Method Mean Error StdDev Gen 0/1k Op Gen 1/1k Op Gen 2/1k Op Allocated Memory/Op
StringJoin 88.245 us 10.5631 us 0.5790 us 10.9863 - - 33.79 KB
StringBuilder1 88.994 us 3.9169 us 0.2147 us 10.9863 - - 33.82 KB
StringBuilder2 69.993 us 10.3178 us 0.5656 us 10.9863 - - 33.76 KB
StringBuilderFormat 149.418 us 20.2449 us 1.1097 us 43.9453 - - 135.32 KB
Lookup 2.355 us 0.2481 us 0.0136 us 2.5482 - - 7.85 KB
Lookup_Separator 2.743 us 0.1258 us 0.0069 us 3.8147 - - 11.75 KB

前3つが単純なやり方、後ろ2つが速度重視の実装だ。文字列変換処理に時間がかかるので、速度重視の実装では変換処理を行わないようにしている。byte程度なら256通りしかないわけで予め全部変換しておいて、そこから引き出すのが究極に速い。ユーティリティとして用意しておくのが良いだろう。

StringJoin(string.Join(",", m_values.Select((x) => x.ToString("X2"))))が最も単純と思われる方法で私は普通はこう書く。string.Join()では内部でStringBuilderが使われるためStringBuilder1/2と大きい違いはない。

StringBuilder1と2は明示的にStringBuilderで組み上げてみたもの。ちょっと横道にそれて、Select()で変換してからforeach回すのと、配列をforeachで回してから変換するのの違いを一応確認したが、やはり後者の方が速い。

StringBuilderFormatは.ToString("X2")ではなくStringBuilder.AppendFormat("{0:x2},")で変換してみたもの。フォーマットはかなり遅い。

Lookupは区切り文字なし、Lookup_Separatorは区切り文字ありだがあまり差はなくどちらもとても速い。

参考

stackoverflow.com