【问题标题】:Does an empty implementation of an interface slow down execution? [duplicate]接口的空实现会减慢执行速度吗? [复制]
【发布时间】:2013-11-01 12:32:16
【问题描述】:

如果我在一个类中实现一个接口,它绝对什么都不做,它会减慢调用它的代码吗?例子2(NoLogger)对代码速度有影响吗,用在什么地方?

示例代码:

interface ILogger{
    void Write(string text);
}

class TextLogger : ILogger {
    public void Write(string text){
        using (var sw = new StreamWriter(@"C:\log.txt"))
        {
            sw.WriteLine(text);
        }
    }
}

class NoLogger : ILogger{
    public void Write(string text){
        //Do absolutely nothing
    }
}

实现一、TextLogger

void Main(){
        ILogger tl = new TextLogger();
        for (int i = 0; i < 100; i++)
        {
            tl.Write(i.ToString());
        }
 }

实现 2,NoLogger

void Main(){
        ILogger tl = new NoLogger();
        for (int i = 0; i < 100; i++)
        {
            tl.Write(i.ToString());
        }
 }

当然,示例 1(文本记录器)会减慢​​执行它的代码的速度,因为它实际上做了一些事情。

但是示例 2 呢?编译器是否足够智能,能够判断出即使实例化了一个类并调用了一个方法,也绝对没有代码可以在任何路径上执行任何操作,而只是在编译时忽略它?

【问题讨论】:

  • 如果您使用优化构建(发布模式),对Write 的调用可能会内联,因此堆栈不必处理额外的“框架” " 用于方法调用。当正文为空时,内联可能意味着什么都不做。但是,C# 编译器不会删除对方法的调用。

标签: c# .net compiler-construction


【解决方案1】:

认为我们可以将其概括为“JIT 是否会内联接口方法的虚拟调用”——我强烈怀疑答案是否定的(要做到这一点,它会需要证明所涉及的实现类型只能是一种具体类型,这比我预期的要多)。

当然,您可以在调用的情况下运行 500,000,000 次,而无需任何操作 - 然后您将有一个合理的起点作为答案。

另请注意:即使Write 什么都不做:仍然需要执行i.ToString(),因为可能会产生副作用

我怀疑您应该查看[Conditional("...")] 属性。这确实改变了一些事情。很多。例如:

public static class Logger
{
    [Conditional("TRACE")]
    public static void Write(string text)
    {
       // some stuff
    }
}

现在;如果我们编译这个没有定义的TRACE符号:

static void Main()
{
    for (int i = 0; i < 100; i++)
    {
        Logger.Write(i.ToString());
    }
 }

我们的代码被编译为:

static void Main()
{
    for (int i = 0; i < 100; i++)
    {
    }
 }

调用被删除,但也:任何参数评估(i.ToString()被删除。如果我们使用定义的 TRACE 符号进行编译 - 那么代码就存在了。

【讨论】:

  • 我不知道 .NET JIT 是否这样做,但许多 JIT 编译器,用于 Java 和动态语言,执行内联虚拟调用并执行其他有条件的优化,可能必须恢复稍后。
【解决方案2】:

编译器是否足够聪明,可以判断出,即使实例化了一个类并调用了一个方法,也绝对没有任何代码可以在任何路径上执行任何操作,而只是在编译时忽略它?

编译器无法合法地完全消除调用。至少,编译器必须评估您传递给被调用方法的所有表达式 - 具体而言,在您的示例中,i.ToString() 将被调用 100 次,无论实现实际做什么。编译器无法对其进行优化,否则在参数表达式有副作用的情况下可能会意外改变程序的语义。

【讨论】:

  • 与确定Write 调用没有任何作用相比,确定i.ToString() 没有副作用是一个简单的练习。你是对的,参数评估代码不能随便丢弃,但这也是代码,可以优化。
  • 要比较的事情是:我们知道Write 是一个空方法的代码for (int i = 0; i &lt; 100; i++) { tl.Write(i.ToString()); } 会比代码for (int i = 0; i &lt; 100; i++) { i.ToString(); } 运行得慢吗? Write 是接口方法还是真正的非虚拟方法有关系吗?
【解决方案3】:

有一个interesting blog entry here 描述了.net JIT 编译器进行动态分析以确定您是否一遍又一遍地调用相同的虚函数,然后对其进行去虚拟化并可能内联它。为此,在应用程序运行时对代码进行了修补。

因此它不会评估为无操作,但 JIT 编译器将消除与调用方法相关的大部分开销。

【讨论】:

    猜你喜欢
    • 2011-02-12
    • 2012-05-16
    • 2011-01-09
    • 1970-01-01
    • 2011-05-07
    • 1970-01-01
    • 2013-07-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多