0

当我使用这样的东西时,是否有拳击操作[性能下降]

Console.WriteLine("The age of the person is : "+age.ToString());

否则,如果我使用它,就不会发生拳击,

Console.WriteLine("The age of the person is : {0}",age);

因为我需要避免哪怕是很小的性能下降,所以我想知道最好的选择。还给我内容的链接,以便我可以了解性能下降的陈述以及如何克服它们。

4

4 回答 4

4

(假设年龄是一个 Int32 变量)。

  • 第一个不应该因为 Int32 覆盖 ToString

.

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)
  • 第二个版本会装箱,因为它解析为 WriteLine(String, object)。

.

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%。一如既往 - 在优化之前配置文件。

于 2011-04-18T05:17:49.943 回答
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 ReflectorILDASM之类的程序检查生成的 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 
}

让我们来看看有趣的部分,看看他们做了什么。对于第一种方法(对应问题中的第一行示例代码),会发生以下有趣的事情:

  1. 在内存中创建一个字符串对象。
  2. ToString()方法在我们指定的整数上调用。
  3. 调用该String.Concat方法来连接两个字符串实例。
  4. 调用接受单个字符串类型参数的重载Console.WriteLine以在控制台窗口中显示结果字符串。

对于第二种方法(对应问题中的第二行示例代码),会发生以下有趣的事情:

  1. 在内存中创建一个字符串对象。
  2. 我们指定的整数被装箱Object.
  3. 调用接受两个参数(一个是 type ,另一个是 type )的Console.WriteLine重载来格式化字符串值,然后在控制台窗口中显示它。stringobject

所以,为了回答你的问题,拳击实际上发生在第二个版本,而不是第一个版本。

但是等等......这是否意味着性能必然更好,并且您应该总是更喜欢第一个版本而不是第二个版本?事实是,不一定。

只是为了好玩,我进行了一些速度测试,将上述每个方法中的单行代码循环了 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 编译器更加智能,并且执行了我什至无法在此答案中描述的大量优化。结果是浪费时间思考这样的问题是没有意义的,而这些时间本可以更好地用于编写代码。

为人类编写这样的代码,而不是为计算机编写。编写清晰、富有表现力且易于理解的代码。从长远来看,您将从中获得比任何“优化”更多的好处。

于 2011-04-18T06:08:09.570 回答
1

这两种情况都不会导致拳击。

内置值类型有它们自己的 实现ToString(),无论如何你的第二行代码都会隐式调用它。

第一行包含字符串连接,因此可能比第二行稍慢。

于 2011-04-18T05:11:38.067 回答
1

在字符串中使用“+”连接是不可变的,而 {0} 格式不是不可变的,并且是避免在应用程序中留下额外内存的更好方法。使用 {0} 格式而不是“+”连接。

于 2011-04-18T13:14:29.653 回答