【问题标题】:Do redundant casts get optimized?多余的演员表是否得到优化?
【发布时间】:2011-07-13 19:34:05
【问题描述】:

我正在更新一些旧代码,并且发现了几个实例,其中每次需要调用同一个对象的属性或方法之一时都会重复转换。示例:

if (recDate != null && recDate > ((System.Windows.Forms.DateTimePicker)ctrl).MinDate)
{
    ((System.Windows.Forms.DateTimePicker)ctrl).CustomFormat = "MM/dd/yyyy";
    ((System.Windows.Forms.DateTimePicker)ctrl).Value = recDate;
}
else
{
    (System.Windows.Forms.DateTimePicker)ctrl).CustomFormat = " ";
}
((System.Windows.Forms.DateTimePicker)ctrl).Format = DateTimePickerFormat.Custom;

我倾向于修复这个怪物,但鉴于我的时间有限,我不想打扰任何不影响功能或性能的事情。

所以我想知道的是,编译器会优化掉这些多余的强制转换吗?我尝试通过在一个简化的示例中使用 ildasm 自己来解决这个问题,但由于不熟悉 IL,我最终只会更加困惑。

更新

到目前为止,共识似乎是 a) 不,演员表没有优化,但是 b) 虽然结果可能会对性能造成一些小的影响,但不太可能很重要,c) 我应该考虑无论如何都要修复它们。如果我有时间的话,我已经决定有一天解决这些问题。同时,我不会担心他们。

谢谢大家!

【问题讨论】:

  • 确实是一个有趣的问题:但是,执行性能分析——我非常怀疑这种强制转换的使用会显着影响应用程序的性能——即使没有优化——仅仅基于可疑的调用计数(和因此,移除演员表只是“修复[ing]这个怪物”,尽管我确实喜欢对现实世界的约束表示赞同 ;-)
  • 在哪里检查 ctrl 是否是 DateTimePicker (或者只是假设是)?通常在这种情况下,您想要转换一次(DateTimePicker dtp = ctrl as DateTimePicker),然后检查结果是否不为空,然后从该点开始使用转换的对象。
  • @Brandon - 这是一个巨大的 if/else if 链,例如如果(ctrl 是 DateTimePicker)。不漂亮,但至少不危险。你提出的就是我想做的,但是因为时间限制而不愿意做(有几百个)。

标签: c# .net casting jit compiler-optimization


【解决方案1】:

对 Release 版本中生成的机器代码的抽查表明,x86 抖动并未优化投射。

不过,您必须在这里看大局。您正在分配控件的属性。它们有很多副作用。对于 DateTimePicker,分配会导致将消息发送到本机 Windows 控件。这反过来又会在消息中嘎吱作响。演员阵容的成本与副作用的成本相比可以忽略不计。重写作业永远不会在速度上产生明显的差异,你只会让它快一点点。

在一个慵懒的周五下午继续重写代码。但这仅仅是因为它对可读性造成了损害。可读性差的 C# 代码也会产生优化不佳的机器代码,这并非完全是巧合。

【讨论】:

  • 如果我可以标记多个答案,我会接受你的和 Nathan 的。谢谢!
  • 为异常生成了哪些操作码?我只希望检查它是否为空,然后检查类型 - 最多可能 10ns。
  • @Gabe - 生成了一堆代码,但它确实首先检查对象是否完全匹配。如果不是,它使用 CLR 辅助函数来检查类型层次结构。可能存在分支错误预测,但 OP 的特定情况在大多数情况下花费不到一纳秒。
  • 没有人问过这个问题,我猜是因为他们都知道,但是,您是如何执行“对生成的机器代码进行抽查”的?
  • @J M:您可以使用 Go To Disassembly 上下文菜单命令查看生成的机器代码。为了使其准确,请使用工具 + 选项、调试、常规,取消选中“在模块加载时抑制 JIT 优化”。
【解决方案2】:

在调试或发布版本中,它没有针对 IL 进行优化。

简单的 C# 测试:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace RedundantCastTest
{
    class Program
    {
        static object get()
        { return "asdf"; }

        static void Main(string[] args)
        {
            object obj = get();
            if ((string)obj == "asdf")
                Console.WriteLine("Equal: {0}, len: {1}", obj, ((string)obj).Length);
        }
    }
}

对应的IL(注意多个castclass指令):

.method private hidebysig static void Main(string[] args) cil managed
{
    .entrypoint
    .maxstack 3
    .locals init (
        [0] object obj,
        [1] bool CS$4$0000)
    L_0000: nop 
    L_0001: call object RedundantCastTest.Program::get()
    L_0006: stloc.0 
    L_0007: ldloc.0 
    L_0008: castclass string
    L_000d: ldstr "asdf"
    L_0012: call bool [mscorlib]System.String::op_Equality(string, string)
    L_0017: ldc.i4.0 
    L_0018: ceq 
    L_001a: stloc.1 
    L_001b: ldloc.1 
    L_001c: brtrue.s L_003a
    L_001e: ldstr "Equal: {0}, len: {1}"
    L_0023: ldloc.0 
    L_0024: ldloc.0 
    L_0025: castclass string
    L_002a: callvirt instance int32 [mscorlib]System.String::get_Length()
    L_002f: box int32
    L_0034: call void [mscorlib]System.Console::WriteLine(string, object, object)
    L_0039: nop 
    L_003a: ret 
}

它也没有从发布版本中的 IL 优化:

.method private hidebysig static void Main(string[] args) cil managed
{
    .entrypoint
    .maxstack 3
    .locals init (
        [0] object obj)
    L_0000: call object RedundantCastTest.Program::get()
    L_0005: stloc.0 
    L_0006: ldloc.0 
    L_0007: castclass string
    L_000c: ldstr "asdf"
    L_0011: call bool [mscorlib]System.String::op_Equality(string, string)
    L_0016: brfalse.s L_0033
    L_0018: ldstr "Equal: {0}, len: {1}"
    L_001d: ldloc.0 
    L_001e: ldloc.0 
    L_001f: castclass string
    L_0024: callvirt instance int32 [mscorlib]System.String::get_Length()
    L_0029: box int32
    L_002e: call void [mscorlib]System.Console::WriteLine(string, object, object)
    L_0033: ret 
}

这两种情况都意味着在生成本机代码时转换没有得到优化 - 您需要查看那里的实际机器组件。即通过运行 ngen 和反汇编。如果它没有被优化掉,我会感到非常惊讶。

无论如何,我将引用The Pragmatic Programmer 和破窗定理:当您看到破窗时,请修复它。

【讨论】:

  • 感谢您的回答。尽管对 The Pragmatic Programmer 给予了应有的尊重,但这个窗口可能并没有真正被打破。它可能只是丑陋的门面装饰:p
  • 即使它没有在 IL 之外进行优化,但这并不意味着它会减慢执行速度。
  • 传统观点(据我所知)是演员很贵。我活在过去吗? (另外,请注意,我不认为性能受限于速度。内存是一个问题)
  • @allonym - 演员阵容相当昂贵。不过,没有动态调度那么糟糕。从宏观上看,它可能不会成为您应用程序的瓶颈。尽管如此,我还是很想修复它。
  • 内存呢?注意到整数类型的装箱了吗?现在使用 c# 6.0,我们有字符串插值并调用 int 类型的 ToString() 方法,这是多余的,可以避免这种装箱和拆箱,还是我错了?
【解决方案3】:

没有; FxCop 将此标记为性能警告。在此处查看信息:http://msdn.microsoft.com/en-us/library/ms182271.aspx

如果您想找到要修复的问题,我建议您在代码上运行它。

【讨论】:

    【解决方案4】:

    我从未听说过或见过 CLR 上的冗余强制转换优化。让我们尝试一个人为的例子

    object number = 5;
    int iterations = 10000000;
    int[] storage = new int[iterations];
    
    var sw = Stopwatch.StartNew();
    for (int i = 0; i < iterations; i++) {
        storage[i] = ((int)number) + 1;
        storage[i] = ((int)number) + 2;
        storage[i] = ((int)number) + 3;
    }
    Console.WriteLine(sw.ElapsedTicks);
    
    storage = new int[iterations];
    
    sw = Stopwatch.StartNew();
    for (int i = 0; i < iterations; i++) {
        var j = (int)number;
        storage[i] = j + 1;
        storage[i] = j + 2;
        storage[i] = j + 3;
    }
    Console.WriteLine(sw.ElapsedTicks);
    Console.ReadLine();
    

    在我的机器上,在发行版下运行,我始终获得大约 350k 滴答作冗余冗余和 280k 滴答作自我优化。所以不,看起来 CLR 没有为此优化。

    【讨论】:

    • 这很有趣。不过,它与 StackOverflowException 的观察不符。
    • 你的例子太做作了。您正在从object 转换为执行unbox 操作的int。 OP 的示例使用了castclass 操作,这是完全不同的。
    • 这固然是人为的,但重点仍然存在,冗余转换没有针对任何类型进行优化。我还测试了引用类型并看到了相同的结果,只是差异较小。重点仍然存在。
    猜你喜欢
    • 2016-03-06
    • 2015-10-23
    • 1970-01-01
    • 2018-07-14
    • 1970-01-01
    • 2015-01-03
    • 2020-08-23
    • 1970-01-01
    • 2015-02-08
    相关资源
    最近更新 更多