+を使用して単一の文字列値を割り当てる場合のパフォーマンスコストはいくらですか
-
03-07-2019 - |
質問
これをよく疑問に思います。文字列に複数の行を分割して、最初に値を文字列に割り当てるときに読みやすくするためのパフォーマンスコストがあるのでしょうか。文字列は不変であるため、毎回新しい文字列を作成する必要があることを知っています。また、今日の非常に高速なハードウェアのおかげで、パフォーマンスコストは実際には無関係です(悪魔的なループに陥っている場合を除く)。例えば:
String newString = "This is a really long long long long long" +
" long long long long long long long long long long long long " +
" long long long long long long long long long string for example.";
JVMまたは.Netのコンパイラおよびその他の最適化はこれをどのように処理しますか。単一の文字列を作成しますか?または、1つの文字列を作成してから、新しい値を連結してから、別の文字列を再度値を連結しますか?
これは私自身の好奇心のためです。
解決
これは、コンパイル時の定数であるため、単一のリテラルで文字列を作成することと同一であることがC#仕様によって保証されています。 C#3仕様のセクション7.18から:
式が満たすときはいつでも 上記の要件、 式はで評価されます コンパイル時。これは、 式は、の部分式です を含むより大きな式 非定数構造。
(「上記の要件」の正確な詳細については、仕様を参照してください:)
Java言語仕様では、の下部近くで指定されています。セクション3.10.5 :
定数によって計算された文字列 式(§ 15.28)はで計算されます コンパイル時間とその後のように扱われます それらはリテラルでした。
他のヒント
実際、Javaでは、コンパイラーは String
を定数に変換します。
class LongLongString
{
public LongLongString()
{
String newString = "This is a really long long long long long" +
" long long long long long long long long long long long long " +
" long long long long long long long long long string for example.";
}
public static void main(String[] args)
{
new LongLongString();
}
}
コンパイル先:
Compiled from "LongLongString.java"
class LongLongString extends java.lang.Object{
public LongLongString();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: ldc #2; //String This is a really long long long long long long long long long long long long long long long long long long long long long long long long long long string for example.
6: astore_1
7: return
public static void main(java.lang.String[]);
Code:
0: new #3; //class LongLongString
3: dup
4: invokespecial #4; //Method "<init>":()V
7: pop
8: return
}
ご覧のとおり、複数の String
インスタンスが読み込まれるのではなく、1行が4行目に読み込まれます。
編集:ソースファイルは、 javac
バージョン1.6.0_06を使用してコンパイルされました。 Java言語仕様、第3版をご覧ください。 (および Jon Skeetの回答)、コンパイラが複数行の String
を単一の String
、したがって、この動作はおそらくコンパイラの実装に固有です。
自分でテストしてください。 C#コード(同等のJavaも動作します):
string x = "A" + "B" + "C";
string y = "ABC";
bool same = object.ReferenceEquals(x, y); // true
結果は true
であることがわかります。
さておき、文字列はランタイムの文字列プールにもインターンされていることがわかります:
bool interned = object.ReferenceEquals(x, string.Intern(x)); // true
パフォーマンスのトレードオフはありません。コンパイラの最適化は、それを単一の文字列にマージします(少なくともJavaでは)。
覚えている限りでは、これにより複数の文字列は作成されず、1つだけが作成されます。
C#コードの場合:
string s = "This is a really long long long long long" +
" long long long long long long long long long long long long " +
" long long long long long long long long long string for example.";
Console.WriteLine(s);
デバッグコンパイルは以下を生成します。
.method public hidebysig static void Main(string[] args) cil managed
{
.custom instance void [mscorlib]System.STAThreadAttribute::.ctor()
.maxstack 1
.locals init (
[0] string str)
L_0000: ldstr "This is a really long long long long long long long long long long long long long long long long long long long long long long long long long long string for example."
L_0005: stloc.0
L_0006: ldloc.0
L_0007: call void [mscorlib]System.Console::WriteLine(string)
L_000c: ret
}
つまり、ご覧のとおり、1つの文字列です。
すべての文字列が一定である限り(例のように)、Java(およびC#を想像します)では、コンパイラはこれを単一の文字列に変換します。
パフォーマンスの問題が発生するのは、ループなどで多くの動的な文字列を連結する場合にのみ+を使用します。この場合、StringBuilderまたはStringBufferを使用します。
免責事項:これはJavaに当てはまります。私はC#については本当だと思います
javacは単一の文字列を作成するだけでなく、JVMは同じテキストを含む他のすべての文字列に対して1つの文字列を使用します。
String a = "He" + "llo th"+ "ere";
String b = "Hell" + "o the"+ "re";
String c = "Hello" +" "+"there";
assert a == b; // these are the same String object.
assert a == c; // these are the same String object.
注:異なるコンパイラによってコンパイルされた、異なるJARの異なるクラスにある場合でも、実行時に同じStringオブジェクトになります。