文字列の結合 (C/C++, C#, Java, JavaScript)

program

各言語で文字列を結合する方法と、効率について検証しようと思います。

C++

+演算子では2つの string 文字列を連結して新しい string オブジェクトを作成します。
+=演算子や append 関数では既存の string オブジェクトの内部バッファに追記されるため、速い結果になりました。
内部バッファが足りない場合は新たにバッファが作成され、新しいバッファに既存文字列がコピーされます。結合後の長さが分かっているのであれば、あらかじめ reserve 関数で容量を確保しておけば、余分なコストを抑えられます。
VC では ostringstream は string::append より遅い結果となりました。

C言語の場合はバッファの確保などを自前でやる必要があるので面倒です。結合前後の文字列サイズが分かっていれば、memcpy を使って高速に結合できます。

	std::string str = "abcdefghij";

	// C++
	std::string dst;
	// + で結合
	dst = dst + str;
	// += で結合
	dst += str;
	// append で結合
	dst.append(str);
	// reserve で容量確保し、append で結合
	dst.reserve(str.length() * 10);
	dst.append(str);
	// ostringstream << で結合
	std::ostringstream os;
	os << str;

	// C
	const char* str2 = "abcdefghij";
	size_t str_len2 = strlen(str2);
	char* dst2 = (char*)malloc(str_len2 * 10 + 1);
	if (NULL == dst2) {
		printf("NULL == (char*)malloc(str_len2 * 10 + 1);");
		return 1;
	}
	dst2[0] = 0;
	// strcat で結合
	strcat(dst2, str2);
	// strcat_s で結合
	strcat_s(dst2, str_len2 * 10 + 1, str2);
	// memcpy で結合
	memcpy(dst2 + str_len2 * 2, str2, str_len2);
	free(dst2);

C#

C# の string はイミュータブルなので、+, +=演算子では2つの文字列を連結して新しい string オブジェクトを作成します。
StringBuilder, StringWriter では既存のオブジェクトの内部バッファに追記されるため、速い結果になりました。
内部バッファが足りない場合は新たにバッファが作成され、新しいバッファに既存文字列がコピーされます。StringBuilder.capacity 関数であらかじめ容量を確保しても、ほぼ速度は変わりませんでした。

		var str = "abcdefghij";

		// + で結合
		var dst = "";
		dst = dst + str;
		// += で結合
		dst += str;
		// Concat で結合
		dst = string.Concat(dst, str);
		// Join で結合
		dst = string.Join("", dst, str);
		// StringBuilder.Append で結合
		var sb = new StringBuilder();
		sb.Append(str);
		// capacity で容量確保し、StringBuilder.Append で結合
		sb = new (str.Length);
		// StringWriter.Write で結合
		var sw = new StringWriter();
		sw.Write(str);

Java

Java の String はイミュータブルなので、+, +=演算子では2つの文字列を連結して新しい String オブジェクトを作成します。
StringBuilder, StringWriter では既存のオブジェクトの内部バッファに追記されるため、速い結果になりました。
内部バッファが足りない場合は新たにバッファが作成され、新しいバッファに既存文字列がコピーされます。結合後の長さが分かっているのであれば、あらかじめ容量を確保しておけば、余分なコストを抑えられます。
10年くらい前(.NET 2.0 のころ)は最適化が働いているのか、+演算子のほうが StringBuilder より速かったのですが、今回は逆転していました。

		String str = "abcdefghij";

		// + で結合
		String dst = "";
		dst = dst + str;
		// += で結合
		dst += str;
		// concat で結合
		dst = dst.concat(str);
		// join で結合
		dst = String.join("", dst, str);
		// StringBuilder.append で結合
		StringBuilder sb = new StringBuilder();
		sb.append(str);
		// StringBuilder.ensureCapacity で容量確保し、append で結合
		sb.ensureCapacity(str.length() * 10);
		sb.append(str);
		// StringWriter.append で結合
		StringWriter sw = new StringWriter();
		sw.append(str);
		// new StringWriter で容量確保し、append で結合
		StringWriter sw2 = new StringWriter(str.length());
		sw2.append(str);

JavaScript

JavaScript の文字列はイミュータブルなので、文字列を連結して新しい文字列オブジェクトを作成します。
計測時間が安定しませんでしたが、おそらく同じような傾向だと思います。

	var str = "abcdefghij";

	// + で結合
	var dst = "";
	dst = dst + str;
	// += で結合
	dst += str;
	// concat で結合
	dst = dst.concat(str);

計測結果

今回の計測結果を示します。プログラムやコンパイラの実装によって結果が変わる可能性があるので注意してください。
例外はありますが、概して以下のことが言えると思います。
・+, += 演算子のようなイミュータブルの結合より、StringBuilder.append のような内部バッファを使う結合のほうが早い。
・最終的な長さが分かるのであれば、あらかじめ容量確保しておいたほうが良い(速度やメモリ使用量の面で)

文字列長10 * 1回 * 100,000ループ = 1,000,000文字連結

VCgccclangC#JavaJavaScript
+2.3145.4947.15925.8745.0100.00110
+=0.002300.0007060.0025030.2805.5240.002
concat29.3965.0430.00260
join29.7675.146
append0.001070.001790.004000.001160.00447
(reserve)0.0004230.0007150.001560.001170.00256
stream0.005990.0016410.003060.0008460.00603
(reserve)0.00361
strcat21.50732.721.148
strcat_s134.88633.461.075
memcpy0.00008680.0001060.0000674

文字列長10 * 10回 * 10,000ループ = 1,000,000文字連結

VCgccclangC#JavaJavaScript
+1.3840.4120.5212.9160.5300.000700
+=0.001760.001630.0019130.1485.0860.00189
concat2.9244.9760.00130
join2.9800.488
append0.0007850.001670.001560.0006870.00221
(reserve)0.0004040.0007400.0007310.0005630.00168
stream0.009270.001670.001650.0006280.00257
(reserve)0.00247
strcat21.8760.09981.078
strcat_s135.2580.1011.228
memcpy0.00007280.00005960.0000937

検証環境
windows11 Intel Core i7-8750H 6/12 2.2-4.1GHz
gcc/clang
ubuntu VMWare 4core Intel Core i7-8750H 6/12 2.2-4.1GHz

Microsoft Visual Studio Community 2022
Version 17.4.2
VisualStudio.17.Release/17.4.2+33122.133
Microsoft .NET Framework
Version 4.8.09032

インストールされているバージョン:Community

Visual C++ 2022 00482-90000-00000-AA226
Microsoft Visual C++ 2022

ubuntu VMWare 4core Intel Core i7-8750H 6/12 2.2-4.1GHz
$ g++ –version
g++ (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ clang++-14 –version
Ubuntu clang version 14.0.6-++20220827082222+f28c006a5895-1~exp1~20220827202233.158
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin

Microsoft Visual Studio Community 2022
Version 17.4.2
VisualStudio.17.Release/17.4.2+33122.133
Microsoft .NET Framework
Version 4.8.09032

インストールされているバージョン:Community

C# ツール 4.4.0-6.22565.8+53091686b435746d62a5df56abfab0e71203d83a
IDE で使用する C# コンポーネント。プロジェクトの種類や設定に応じて、異なるバージョンのコンパイラを使用できます。

java –version
java 19.0.1 2022-10-18
Java(TM) SE Runtime Environment (build 19.0.1+10-21)
Java HotSpot(TM) 64-Bit Server VM (build 19.0.1+10-21, mixed mode, sharing)

Chrome
バージョン: 108.0.5359.125(Official Build) (64 ビット)

今回作成したプログラムや詳細な測定結果をこちらで公開しています。
https://github.com/matsushima-terunao/samples/tree/main/test_string_concat

コメント

タイトルとURLをコピーしました