当我使用这样的东西时,是否有拳击操作[性能下降]
Console.WriteLine("The age of the person is : "+age.ToString());
否则,如果我使用它,就不会发生拳击,
Console.WriteLine("The age of the person is : {0}",age);
因为我需要避免哪怕是很小的性能下降,所以我想知道最好的选择。还给我内容的链接,以便我可以了解性能下降的陈述以及如何克服它们。
当我使用这样的东西时,是否有拳击操作[性能下降]
Console.WriteLine("The age of the person is : "+age.ToString());
否则,如果我使用它,就不会发生拳击,
Console.WriteLine("The age of the person is : {0}",age);
因为我需要避免哪怕是很小的性能下降,所以我想知道最好的选择。还给我内容的链接,以便我可以了解性能下降的陈述以及如何克服它们。
(假设年龄是一个 Int32 变量)。
.
L_0019: ldloca.s age
L_001b: call instance string [mscorlib]System.Int32::ToString()
L_0020: call string [mscorlib]System.String::Concat(string, string)
L_0025: call void [mscorlib]System.Console::WriteLine(string)
.
L_003b: ldstr "The age of the person is : {0}"
L_0040: ldloc.0
L_0041: box int32
L_0046: call void [mscorlib]System.Console::WriteLine(string, object)
检查您是否不确定的一个好方法是打开反射器并检查一个片段。在 IL 中查找类似于 L_0041的框指令。
然而,从我有限的基准测试来看,第二个版本似乎快了 10-15%。一如既往 - 在优化之前配置文件。
与其随意猜测(是的,我可能也对此感到内疚),不如我们来衡量它?在“发布”模式下编译以下代码(即启用优化):
class TestProgram
{
static void Main(string[] args)
{
int age = 32;
WriteWithConcat(age);
WriteWithFormat(age);
}
static void WriteWithConcat(int age)
{
Console.WriteLine("The age of the person is : " + age.ToString());
}
static void WriteWithFormat(int age)
{
Console.WriteLine("The age of the person is : {0}", age);
}
}
然后使用.NET Reflector或ILDASM之类的程序检查生成的 IL (为简洁起见,我省略了以下无趣的方法):
.method private hidebysig static void WriteWithConcat(int32 age) cil managed
{
.maxstack 8
L_0000: ldstr "The age of the person is : "
L_0005: ldarga.s age
L_0007: call instance string [mscorlib]System.Int32::ToString()
L_000c: call string [mscorlib]System.String::Concat(string, string)
L_0011: call void [mscorlib]System.Console::WriteLine(string)
L_0016: ret
}
.method private hidebysig static void WriteWithFormat(int32 age) cil managed
{
.maxstack 8
L_0000: ldstr "The age of the person is : {0}"
L_0005: ldarg.0
L_0006: box int32
L_000b: call void [mscorlib]System.Console::WriteLine(string, object)
L_0010: ret
}
让我们来看看有趣的部分,看看他们做了什么。对于第一种方法(对应问题中的第一行示例代码),会发生以下有趣的事情:
ToString()
方法在我们指定的整数上调用。String.Concat
方法来连接两个字符串实例。Console.WriteLine
以在控制台窗口中显示结果字符串。对于第二种方法(对应问题中的第二行示例代码),会发生以下有趣的事情:
Object
.Console.WriteLine
重载来格式化字符串值,然后在控制台窗口中显示它。string
object
所以,为了回答你的问题,拳击实际上发生在第二个版本,而不是第一个版本。
但是等等......这是否意味着性能必然更好,并且您应该总是更喜欢第一个版本而不是第二个版本?事实是,不一定。
只是为了好玩,我进行了一些速度测试,将上述每个方法中的单行代码循环了 100,000 次。事实证明,第二个版本实际上比第一个版本稍微小了一点。
我强调“非常轻微”,因为它在这里非常重要。你自己看:
00:00:00:0001676 // time for the first method
00:00:00:0001381 // time for the second method
这就是循环超过 100,000 次!这根本不可能成为任何应用程序的瓶颈。不仅如此,底层调用Console.WriteLine
始终是代码中最慢的部分,无论我们选择哪种方式都会被调用。
所以结果是你真的应该忘记我们上面谈到的一切。你需要学会信任你的编译器。这个问题是最好的过早优化。除非您明确知道这行代码会拖慢您的应用程序,否则不要浪费任何时间优化它!在绝大多数情况下,编译器足够聪明,可以生成最好的、最优化的 IL,无论您选择编写哪种等效语法。
作为一个必然结果,如果您不能信任您的编译器,请相信您的 JITter。JIT 编译器更加智能,并且执行了我什至无法在此答案中描述的大量优化。结果是浪费时间思考这样的问题是没有意义的,而这些时间本可以更好地用于编写代码。
为人类编写这样的代码,而不是为计算机编写。编写清晰、富有表现力且易于理解的代码。从长远来看,您将从中获得比任何“优化”更多的好处。
这两种情况都不会导致拳击。
内置值类型有它们自己的 实现ToString()
,无论如何你的第二行代码都会隐式调用它。
第一行包含字符串连接,因此可能比第二行稍慢。
在字符串中使用“+”连接是不可变的,而 {0} 格式不是不可变的,并且是避免在应用程序中留下额外内存的更好方法。使用 {0} 格式而不是“+”连接。