Unityでの文字列連結のパフォーマンスについては、いつもお世話になっているコガネブログさんがたびたび記事を上げてくださっています。
ただ、いろんな方法があるので結局どれを使えばいいのか分からないな~となることが度々ありました。
そこで、今回は自分が見たことある、思いつく文字列連結の処理の速度とGCを比較してみました。
比べた文字連結方法は以下の通りです。
string s = "a" + "b";
一番ノーマルなパターンです。
StringBuilder builder = new StringBuilder();
builder.Append(”a”).Append("b");
string s = builder.ToString();
毎回StringBuilderのインスタンスを作成します。
StringBuilder builder = new StringBuilder();
for(int i = 0; i < n; ++i)
{
builder.Clear();
builder.Append(”a”).Append("b");
string s = builder.ToString();
}
あらかじめStringBuilderのインスタンスを作成しておきます。
string s = string.Concat("a", "b");
string.Concatで連結します。
string s = string.Format("{0}{1}", "a", "b");
string.Formatで連結します。
string a = "a", b = "b";
string s = $"{a}{b}";
C#6以降の文字列補完で連結します。
string str = StrOpe.i +"a" + "b";
こちらのブログの方法です。
【Unity】string の連結を StringBuilder に置き換えてパフォーマンスを改善できる「StringBuilderTemporary」紹介
FastString fast = new FastString(64);
fast.Append("a").Append("b");
string str = fast.ToString();
こちらのブログの方法です。毎回インスタンスを作成します。
【Unity】string や StringBuilder よりもメモリ割り当てが少なく高速な文字列クラス「FastString」紹介
FastString fast = new FastString(64);
for(int i = 0; i < n; ++i)
{
fast.Clear();
fast.Append(”a”).Append("b");
string s = fast.ToString();
}
No.8の方法で、あらかじめインスタンスを生成しておきます。
string str = StringUtils.Format("{0}{1}", "a", "b");
こちらのブログの方法です。
【Unity】ボックス化をなるべく回避して GC の発生を抑える string.Format「StringUtils」
using System;
using System.Text;
using UnityEngine;
using UnityEngine.Profiling;
using StrOpe = StringOperationUtil.OptimizedStringOperation;
public class Test : MonoBehaviour
{
const int repeat = 10000;
const string tmp1 = "a";
const string tmp2 = "b";
const string tmp3 = "c";
const string tmp4 = "d";
const string tmp5 = "e";
const string tmp6 = "f";
const string tmp7 = "g";
const string tmp8 = "h";
const string tmp9 = "i";
const string tmp10 = "j";
StringBuilder _builder;
FastString _fast = new FastString(64);
void Start()
{
_builder = new StringBuilder();
PlayTest(Plus, "+");
PlayTest(StringBuilderNew, "StringBuilder(New)");
PlayTest(StringBuilderRepeat, "StringBuilder(Repeat)");
PlayTest(Concat, "Concat");
PlayTest(Format, "Format");
PlayTest(Interpolation, "Interpolation");
PlayTest(StringBuilderTemporary, "StringBuilderTemporary");
PlayTest(FastStringNew, "FastString(New)");
PlayTest(FastStringRepeat, "FastString(Repeat)");
PlayTest(UtilsFormat, "UtilsFormat");
}
void PlayTest(Func<string> func, string title)
{
Profiler.BeginSample(title);
string s = string.Empty;
for (int i = 0; i < repeat; ++i)
{
s = func();
}
Profiler.EndSample();
}
string Plus()
{
string str = tmp1 + tmp2 + tmp3 + tmp4 + tmp5 + tmp6 + tmp7 + tmp8 + tmp9;
return str;
}
string StringBuilderNew()
{
StringBuilder builder = new StringBuilder();
builder.Append(tmp1).Append(tmp2).Append(tmp3).Append(tmp4).Append(tmp5).Append(tmp6).Append(tmp7).Append(tmp8).Append(tmp9);
string str = builder.ToString();
return str;
}
string StringBuilderRepeat()
{
_builder.Clear();
_builder.Append(tmp1).Append(tmp2).Append(tmp3).Append(tmp4).Append(tmp5).Append(tmp6).Append(tmp7).Append(tmp8).Append(tmp9);
string str = _builder.ToString();
return str;
}
string Concat()
{
string str = string.Concat(tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9);
return str;
}
string Format()
{
string str = string.Format("{0}{1}{2}{3}{4}{5}{6}{7}{8}", tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9);
return str;
}
string Interpolation()
{
string str = $"{tmp1}{tmp2}{tmp3}{tmp4}{tmp5}{tmp6}{tmp7}{tmp8}{tmp9}";
return str;
}
string StringBuilderTemporary()
{
string str = StrOpe.i + tmp1 + tmp2 + tmp3 + tmp4 + tmp5 + tmp6 + tmp7 + tmp8 + tmp9;
return str;
}
string FastStringNew()
{
FastString fast = new FastString(64);
fast.Append(tmp1).Append(tmp2).Append(tmp3).Append(tmp4).Append(tmp5).Append(tmp6).Append(tmp7).Append(tmp8).Append(tmp9);
string str = fast.ToString();
return str;
}
string FastStringRepeat()
{
_fast.Clear();
_fast.Append(tmp1).Append(tmp2).Append(tmp3).Append(tmp4).Append(tmp5).Append(tmp6).Append(tmp7).Append(tmp8).Append(tmp9);
string str = _fast.ToString();
return str;
}
string UtilsFormat()
{
string str = StringUtils.Format("{0}{1}{2}{3}{4}{5}{6}{7}{8}", tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9);
return str;
}
「+」 と 「$""] が速度、GC両方ともに優れているという結果になりました。なんで?
どのサイトを見ても、+で連結するよりStringBuilder使った方がいいよって書いてあるのになんでこうなった?
using System;
using System.Text;
using UnityEngine;
using UnityEngine.Profiling;
using StrOpe = StringOperationUtil.OptimizedStringOperation;
public class Test : MonoBehaviour
{
const int repeat = 100000;
const string tmp1 = "a";
const string tmp2 = "b";
const string tmp3 = "c";
const string tmp4 = "d";
const string tmp5 = "e";
const string tmp6 = "f";
const string tmp7 = "g";
const string tmp8 = "h";
const string tmp9 = "i";
const string tmp10 = "j";
StringBuilder _builder;
FastString _fast = new FastString(repeat + 1);
void Start()
{
_builder = new StringBuilder();
PlayTest(Plus, "+");
PlayTest(StringBuilderNew, "StringBuilder(New)");
PlayTest(StringBuilderRepeat, "StringBuilder(Repeat)");
PlayTest(Concat, "Concat");
PlayTest(Format, "Format");
PlayTest(Interpolation, "$\"\"");
PlayTest(StringBuilderTemporary, "StringBuilderTemporary");
PlayTest(FastStringNew, "FastString(New)");
PlayTest(FastStringRepeat, "FastString(Repeat)");
PlayTest(UtilsFormat, "UtilsFormat");
}
void PlayTest(Func<string> func, string title)
{
Profiler.BeginSample(title);
string s = func();
Profiler.EndSample();
}
string Plus()
{
string str = string.Empty;
for (int i = 0; i < repeat; ++i)
{
str += tmp1;
}
return str;
}
string StringBuilderNew()
{
StringBuilder builder = new StringBuilder();
for (int i = 0; i < repeat; ++i)
{
builder.Append(tmp1);
}
string str = builder.ToString();
return str;
}
string StringBuilderRepeat()
{
_builder.Clear();
for (int i = 0; i < repeat; ++i)
{
_builder.Append(tmp1);
}
string str = _builder.ToString();
return str;
}
string Concat()
{
string str = string.Empty;
for (int i = 0; i < repeat; ++i)
{
str = string.Concat(str, tmp1);
}
return str;
}
string Format()
{
string str = string.Empty;
for (int i = 0; i < repeat; ++i)
{
str = string.Format("{0}{1}", str, tmp1);
}
return str;
}
string Interpolation()
{
string str = string.Empty;
for (int i = 0; i < repeat; ++i)
{
str = $"{str}{tmp1}";
}
return str;
}
string StringBuilderTemporary()
{
string str = string.Empty;
for (int i = 0; i < repeat; ++i)
{
str = StrOpe.i + str + tmp1;
}
return str;
}
string FastStringNew()
{
FastString fast = new FastString(repeat + 1);
for (int i = 0; i < repeat; ++i)
{
fast.Append(tmp1);
}
string str = fast.ToString();
return str;
}
string FastStringRepeat()
{
for (int i = 0; i < repeat; ++i)
{
_fast.Append(tmp1);
}
string str = _fast.ToString();
return str;
}
string UtilsFormat()
{
string str = string.Empty;
for (int i = 0; i < repeat; ++i)
{
str = StringUtils.Format("{0}{1}", str, tmp1);
}
return str;
}
}
今回は 「FastString」 と 「StringBuilder」 の2強です。
なるほど、1つのstringに繰り返し連結させる場合はStringBuilderの方が優れているんですね。
10数個の文字列を一度に連結して、複数の文字列を生成する場合は 「$""」 か 「+」 を(可読性を考えるなら「$""」でしょう)、
1つのstringに繰り返し連結して長文を作りたいときは 「FastString」 か 「StringBuilder」 で連結するのがよさそうです。
「$""」が一番使う機会が多そうですね。
Crieitは誰でも投稿できるサービスです。 是非記事の投稿をお願いします。どんな軽い内容でも投稿できます。
また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!
こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください。
ボードとは?
コメント