冗余字符串插值和性能
Redundant string interpolation and performance
我一直在对我的 C# 项目进行一些代码重构。
我收到 Resharper 代码分析警告:
"Redundant string interpolation"
这发生在以下场景中:
void someFunction(string param)
{
...
}
someFunction($"some string");
我读过字符串插值在编译时被重写为 string.Format
。然后我尝试了以下操作:
someFunction(string.Format("some string"));
这次我得到:
Redundant string.Format call.
我的问题是:除了代码整洁度之外,这些冗余调用是否影响 运行 时间性能,或者性能是否相同:
someFunction($"some string")
someFunction("some string")
someFunction(string.Format("some string"))
即使格式字符串后没有格式参数,调用 string.Format("some string")
也会做很多事情。
由于没有只有一个参数的 string.Format
重载,运行时将首先需要实例化一个空参数数组 (new object[0]
) 以将其传递给方法。然后它将从池中获取一个内部 StringBuilder
实例,并开始解析格式字符串,寻找占位符。如果格式字符串中有占位符但没有参数,则会抛出异常,因此该方法始终解析格式字符串。
最后,StringBulder
必须实例化一个新的 string
并将其内容复制到那里,然后再返回到池中。
根据@Dmitry 的分析,使用 "empty" FormattableString
,编译器可能足够聪明,可以跳过整个过程并只传递字符串,因为字符串的内容在编译时间将内插文字转换为 FormattableString
实例。
I've heard string interpolation is rewritten to string.Format
at compile time
这取决于;如果您的函数改为接受 FormattableString
参数,则编译器将创建一个派生自 FormattableString
的 class 实例,其中包含格式字符串和参数数组。这对于条件调试之类的东西很有用,因为如果不需要,您无需支付格式化字符串的费用,即:
public void Log_A(string input)
{
if (Log.IsDebugEnabled)
Log.Debug(input);
}
public void Log_B(FormattableString input)
{
if (Log.IsDebugEnabled)
Log.Debug(input.ToString());
}
// string.Format is called before entering Log_A
Log_A($"Something happened with {x} and {y}");
// if Log.IsDebugEnabled is false, string.Format will not be called
Log_B($"Something happened with {x} and {y}");
好吧,让我们执行一个基准测试:
private static long someFunction(string value) {
return value.Length;
}
...
Stopwatch sw = new Stopwatch();
int n = 100_000_000;
long sum = 0;
sw.Start();
for (int i = 0; i < n; ++i) {
// sum += someFunction("some string");
// sum += someFunction($"some string");
sum += someFunction(string.Format("some string"));
}
sw.Stop();
Console.Write(sw.ElapsedMilliseconds);
结果(.Net 4.8 IA-64 版本),平均结果:
224 // "some string"
225 // $"some string"
8900 // string.Format("some string")
所以我们可以看到,编译器删除了不需要的 $
但执行了 string.Format
这浪费了时间来理解我们没有任何格式
public void XYZ()
{
Stopwatch sw = new Stopwatch();
sw.Start();
for (int i = 0; i < 1000000; i++)
{
someFunction("some string");
}
sw.Stop();
Debug.WriteLine(sw.ElapsedMilliseconds);
sw.Reset();
sw.Start();
for (int i = 0; i < 1000000; i++)
{
someFunction("$some string");
}
sw.Stop();
Debug.WriteLine(sw.ElapsedMilliseconds);
sw.Reset();
sw.Start();
for (int i = 0; i < 1000000; i++)
{
someFunction(string.Format("some string"));
}
sw.Stop();
Debug.WriteLine(sw.ElapsedMilliseconds);
sw.Reset();
}
private void someFunction(string param)
{
}
给我
3
3
210
因此,如果不需要,请不要使用 string.Format():-)
作为 C# 编译器中 that specific optimization 的作者,我可以确认 $"some string"
已被 C# 编译器优化为 "some string"
。这是一个常量,因此几乎不需要在运行时执行代码来计算它。
另一方面,string.Format("some string")
是方法调用,必须在运行时调用该方法。显然,调用会产生相关费用。它当然不会做任何有用的事情,因此警告 "Redundant string.Format call."
更新:事实上,没有填充的插值一直被编译器优化为结果字符串。它所做的只是将 {{
转义为 {
以及将 }}
转义为 }
。我的更改是优化插值,其中所有填充都是字符串,没有格式化为字符串连接。
我一直在对我的 C# 项目进行一些代码重构。 我收到 Resharper 代码分析警告:
"Redundant string interpolation"
这发生在以下场景中:
void someFunction(string param)
{
...
}
someFunction($"some string");
我读过字符串插值在编译时被重写为 string.Format
。然后我尝试了以下操作:
someFunction(string.Format("some string"));
这次我得到:
Redundant string.Format call.
我的问题是:除了代码整洁度之外,这些冗余调用是否影响 运行 时间性能,或者性能是否相同:
someFunction($"some string")
someFunction("some string")
someFunction(string.Format("some string"))
即使格式字符串后没有格式参数,调用 string.Format("some string")
也会做很多事情。
由于没有只有一个参数的 string.Format
重载,运行时将首先需要实例化一个空参数数组 (new object[0]
) 以将其传递给方法。然后它将从池中获取一个内部 StringBuilder
实例,并开始解析格式字符串,寻找占位符。如果格式字符串中有占位符但没有参数,则会抛出异常,因此该方法始终解析格式字符串。
最后,StringBulder
必须实例化一个新的 string
并将其内容复制到那里,然后再返回到池中。
根据@Dmitry 的分析,使用 "empty" FormattableString
,编译器可能足够聪明,可以跳过整个过程并只传递字符串,因为字符串的内容在编译时间将内插文字转换为 FormattableString
实例。
I've heard string interpolation is rewritten to
string.Format
at compile time
这取决于;如果您的函数改为接受 FormattableString
参数,则编译器将创建一个派生自 FormattableString
的 class 实例,其中包含格式字符串和参数数组。这对于条件调试之类的东西很有用,因为如果不需要,您无需支付格式化字符串的费用,即:
public void Log_A(string input)
{
if (Log.IsDebugEnabled)
Log.Debug(input);
}
public void Log_B(FormattableString input)
{
if (Log.IsDebugEnabled)
Log.Debug(input.ToString());
}
// string.Format is called before entering Log_A
Log_A($"Something happened with {x} and {y}");
// if Log.IsDebugEnabled is false, string.Format will not be called
Log_B($"Something happened with {x} and {y}");
好吧,让我们执行一个基准测试:
private static long someFunction(string value) {
return value.Length;
}
...
Stopwatch sw = new Stopwatch();
int n = 100_000_000;
long sum = 0;
sw.Start();
for (int i = 0; i < n; ++i) {
// sum += someFunction("some string");
// sum += someFunction($"some string");
sum += someFunction(string.Format("some string"));
}
sw.Stop();
Console.Write(sw.ElapsedMilliseconds);
结果(.Net 4.8 IA-64 版本),平均结果:
224 // "some string"
225 // $"some string"
8900 // string.Format("some string")
所以我们可以看到,编译器删除了不需要的 $
但执行了 string.Format
这浪费了时间来理解我们没有任何格式
public void XYZ()
{
Stopwatch sw = new Stopwatch();
sw.Start();
for (int i = 0; i < 1000000; i++)
{
someFunction("some string");
}
sw.Stop();
Debug.WriteLine(sw.ElapsedMilliseconds);
sw.Reset();
sw.Start();
for (int i = 0; i < 1000000; i++)
{
someFunction("$some string");
}
sw.Stop();
Debug.WriteLine(sw.ElapsedMilliseconds);
sw.Reset();
sw.Start();
for (int i = 0; i < 1000000; i++)
{
someFunction(string.Format("some string"));
}
sw.Stop();
Debug.WriteLine(sw.ElapsedMilliseconds);
sw.Reset();
}
private void someFunction(string param)
{
}
给我
3
3
210
因此,如果不需要,请不要使用 string.Format():-)
作为 C# 编译器中 that specific optimization 的作者,我可以确认 $"some string"
已被 C# 编译器优化为 "some string"
。这是一个常量,因此几乎不需要在运行时执行代码来计算它。
另一方面,string.Format("some string")
是方法调用,必须在运行时调用该方法。显然,调用会产生相关费用。它当然不会做任何有用的事情,因此警告 "Redundant string.Format call."
更新:事实上,没有填充的插值一直被编译器优化为结果字符串。它所做的只是将 {{
转义为 {
以及将 }}
转义为 }
。我的更改是优化插值,其中所有填充都是字符串,没有格式化为字符串连接。