当我使用像这样的东西时,是否有拳击操作[性能下降]
Console.WriteLine("The age of the person is : "+age.ToString());
否则,如果我使用它,就不会发生拳击,
Console.WriteLine("The age of the person is : {0}",age);
由于我需要避免轻微的性能下降,我想知道最好的选择。还给我链接内容,以便我可以了解性能恶化声明以及如何克服它们。
答案 0 :(得分:4)
(假设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%。一如既往 - 优化之前的配置文件。
答案 1 :(得分:2)
不是随意猜测(是的,我也可能犯了这个错误),我们如何衡量呢?在"发布"中编译以下代码模式(即启用了优化):
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()
method。String.Concat
method来连接两个字符串实例。Console.WriteLine
overload以在控制台窗口中显示结果字符串。对于第二种方法(对应于问题中的第二行示例代码),会发生以下有趣的事情:
Object
。string
和类型object
中的一个)的Console.WriteLine
overload来格式化字符串值,然后在控制台窗口中显示它。 所以,为了回答你的问题,拳击实际发生在第二个版本,而不是第一个版本。
但是等等......这是否意味着性能必然更好,你应该总是更喜欢第一个版本而不是第二个版本?事实并非如此。
为了好玩,我进行了一些速度测试,将上述每种方法中的单行代码循环100,000次。事实证明,第二个版本实际上略微超出了第一个版本 我强调"曾经如此轻微地#34;因为它在这里非常重要。亲眼看看:
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编译器甚至更智能,并且执行了一系列优化,我甚至无法在此答案中开始描述。结果是浪费时间思考像这样的问题是毫无意义的,并且花在编写代码上的时间更长。
为人类编写这样的代码,而不是为计算机编写代码。编写清晰,富有表现力且易于理解的代码。从长远来看,您将获得 lot 更多的好处,而不是任何"优化"。
答案 2 :(得分:1)
两种情况都不会导致拳击。
内置值类型有自己的ToString()
实现,无论如何都会被第二行代码隐式调用。
第一行包含字符串连接,因此可能比第二行略慢。
答案 3 :(得分:1)
在字符串中使用“+”连接是不可变的,而{0}格式不是不可变的,更好的方法是避免在应用程序中留下额外的内存批次。转到{0}格式而不是“+”串联。