代码之家  ›  专栏  ›  技术社区  ›  Wernight

为什么“使用”能提高C性能?

  •  13
  • Wernight  · 技术社区  · 15 年前

    似乎在大多数情况下,C编译器可以调用 Dispose() 自动地。像大多数案件一样 使用 图案如下:

    public void SomeMethod()
    {
        ...
    
        using (var foo = new Foo())
        {
            ...
        }
    
        // Foo isn't use after here (obviously).
        ...
    }
    

    自从 foo 没有使用(这是一个非常简单的检测),而且由于它没有作为另一个方法的参数提供(这是一个适用于许多用例并且可以扩展的假设),编译器可以 自动和立即 呼叫 处置() 没有开发人员需要这样做。

    这意味着在大多数情况下 using 如果编译器做了一些聪明的工作,那就没用了。 IDisposable 似乎 低水平 足以让我被编译器考虑在内。

    为什么不完成呢?这不会提高性能吗(如果显影剂是… 肮脏的 )

    11 回复  |  直到 15 年前
        1
  •  21
  •   Adam Robinson    15 年前

    以下几点:

    打电话 Dispose 不会提高性能。 IDisposable 是为使用运行时无法计算的有限和/或非托管资源的情况而设计的。

    对于编译器如何处理,没有明确而明显的机制。 不可分的 代码中的对象。是什么让它成为自动处理的候选者,而不是什么?如果实例被(或可能)暴露在方法之外?没有什么可以说的,只是因为我将一个对象传递给了另一个函数或类,我希望它在方法范围之外可用。

    例如,考虑采用 Stream 并反序列化类的实例。

    public class Foo
    {
        public static Foo FromStream(System.IO.Stream stream) { ... }
    }
    

    我称之为:

    Stream stream = new FileStream(path);
    
    Foo foo = Foo.FromStream(stream);
    

    现在,我可能想要也可能不想要 河流 在方法退出时释放。如果 Foo 的工厂从 河流 不再需要它了,我希望它被处理掉。如果 对象必须保留流并在其生命周期中使用它,那么我不希望它被释放。

    同样,对于从构造函数以外的东西中检索到的实例,比如 Control.CreateGraphics() . 这些实例 能够 存在于代码之外,因此编译器不会自动处理它们。

    提供用户控制权(并提供类似 using block)使用户的意图更清晰,并且更容易发现 不可分的 未正确处置实例。如果编译器要自动释放 一些 实例,那么调试就要困难得多,因为开发人员必须破译自动处理规则是如何应用于使用 不可分的 对象。

    最后,按照惯例,有两个原因可以实施 不可分的 一种类型。

    1. 您使用的是非托管资源(这意味着您正在进行一个p/invoke调用,该调用返回类似于必须由其他p/invoke调用释放的句柄的内容)
    2. 您的类型具有的实例 不可分的 当此对象的生存期结束时,应该处理它。

    在第一种情况下,所有此类类型都应该实现调用 处置 并释放所有 非受管的 如果开发人员未能做到这一点(这是为了防止内存和处理泄漏)。

        2
  •  16
  •   Justin Niessner    15 年前

    垃圾收集(虽然与IDisposable没有直接关系,但清理未使用对象的功能)并不是那么简单。

    让我再说一遍。自动呼叫 Dispose() 不是很简单吗?它也不会直接提高性能。稍后再谈。

    如果您有以下代码:

    public void DoSomeWork(SqlCommand command)
    {
        SqlConnection conn = new SqlConnection(connString);
    
        conn.Open();
    
        command.Connection = conn;
    
        // Rest of the work here
     }
    

    编译器如何知道何时使用 conn 对象?或者,如果您传递了一个对其他方法的引用,而其他方法一直保持着它?

    显式调用 处置() 或使用 using 块清楚地表明你的意图,并迫使事情得到正确的清理。

    现在,回到表演上来。简单呼叫 处置() 在一个对象上不能保证任何性能的提高。这个 处置() 方法用于在处理完对象后“清理”资源。

    当使用未管理的资源时,性能会提高。如果托管对象没有正确地处置其未托管的资源,则会出现内存泄漏。丑陋的东西。

    留下打电话的决心 处置() 到编译器为止,这将消除这一级别的清晰度,并使调试由未管理的资源引起的内存泄漏变得更加困难。

        3
  •  3
  •   3Dave    15 年前

    您要求编译器对代码执行语义分析。在源代码的某个点之后没有显式引用某个内容,这并不意味着它没有被使用。如果我创建一个引用链,并将其传递给一个方法,该方法可能或可能不会将该引用存储在属性或其他持久性容器中,那么我真的应该期望编译器跟踪所有这些内容并找出我真正需要的内容吗? 意味 ?

    不稳定的实体也可能是一个问题。

    此外, using() {....} 更具可读性和直观性,在可维护性方面非常值得。

    作为工程师或程序员,我们努力做到 有效率的 但这与 懒惰的 .

        4
  •  3
  •   David Basarab    15 年前

    看看 MSDN Artilce for the C# Using Statement 这个 using 声明只是一个捷径,避免尝试,最后在分配的地方。调用Dispose不是像垃圾收集这样的低级功能。

    如你所见,使用被翻译成。

    {
      Font font1 = new Font("Arial", 10.0f);
      try
      {
        byte charset = font1.GdiCharSet;
      }
      finally
      {
        if (font1 != null)
          ((IDisposable)font1).Dispose();
      }
    }
    

    编译器如何知道将finally块放在哪里?它在垃圾收集时调用它吗?

    GarabageCollection不会在您离开方法后立即发生。读这个 article 对垃圾收集有更好的了解。只有在没有对对象的引用之后。资源的占用时间可能比需要的时间长得多。

    我一直在想,编译器不应该保护那些不清理资源的开发人员。仅仅因为语言是被管理的并不意味着它会保护自己。

        5
  •  2
  •   Stephen Cleary    15 年前

    C++支持这一点;它们称之为“引用类型的堆栈语义”。我支持将它添加到C,但它需要不同的语法(根据是否将局部变量传递给另一个方法来更改语义不是一个好主意)。

        6
  •  2
  •   JMarsch    15 年前

    我认为你正在考虑定稿。终结器使用C中的析构函数语法,垃圾收集器会自动调用它们。终结器只适合在清理非托管资源时使用。

    Dispose旨在允许对非托管资源进行早期清理(它也可用于清理托管资源)。

    检测实际上比看起来更难。如果你有这样的代码怎么办:

    
      var mydisposable = new...
      AMethod(mydisposable);
      // (not used again)
    
    

    Amethod中的一些代码可能保留了对MyDisposable的引用。

    • 可能它被分配给该方法内部的一个实例变量

    • 可能是我的一次性订阅了amethod中的一个事件(然后事件发布者保存了对mydisposable的引用)

    • 也许另一根线是由阿米霍德产生的。

    • 也许MyDisposable会被一个匿名方法或ammethod中的lamba表达式“封闭”。

    所有这些都使绝对确定您的对象不再使用变得困难,因此Dispose是为了让开发人员说“好的,我知道现在运行清理代码是安全的”);

    还要记住,Dispose不会释放对象——只有GC可以这样做。(GC确实具有理解我描述的所有场景的魔力,它知道何时清理对象,如果您确实需要在GC检测不到引用时运行代码,则可以使用终结器)。不过,请注意终结器——它们只用于类所拥有的非托管分配。

    您可以在此处阅读更多关于此内容的信息: http://msdn.microsoft.com/en-us/magazine/bb985010.aspx 这里: http://www.bluebytesoftware.com/blog/2005/04/08/DGUpdateDisposeFinalizationAndResourceManagement.aspx

    如果需要非托管句柄清理,请阅读有关SafeHandles的内容。

        7
  •  1
  •   nlawalker    15 年前

    这不是 编译程序 解释应用程序中的作用域,并执行类似于确定何时不再需要内存的操作。事实上,我很确定这是一个不可能解决的问题,因为编译器不可能知道你的程序在运行时会是什么样子,不管它有多聪明。

    这就是我们收集垃圾的原因。垃圾收集的问题在于它以不确定的间隔运行,通常如果对象实现IDisposable,原因是您希望能够立即处理它。像, 马上 立即。像数据库连接这样的构造不仅仅是一次性的,因为当它们被丢弃时,它们还有一些特殊的工作要做——这也是因为它们是稀缺的。

        8
  •  0
  •   Shimrod    15 年前

    对于G.C.来说,我似乎很难知道稍后在同一方法中将不再使用这个变量。显然,如果您离开该方法,并且不保留对变量的进一步引用,那么G.C.将处理它。但使用 using 在你的样本中,告诉总干事你是 当然 之后将不再使用此变量。

        9
  •  0
  •   Ash    15 年前

    这个 using statement 与性能无关(除非考虑将避免资源/内存泄漏作为性能)。

    它为您所做的一切就是确保当相关对象超出范围时,即使在using块内发生异常,也会对该对象调用IDisposable.Dispose方法。

    然后,dispose()方法负责释放对象使用的任何资源。这些通常是非托管资源,如文件、字体、图像等,但也可以是托管对象上的简单“清理”活动(但不是垃圾收集)。

    当然,如果Dispose()方法实现得不好,那么using语句就没有任何好处。

        10
  •  0
  •   Matt Mitchell    15 年前

    我认为OP是在说“当编译器可以很容易地神奇地解决问题时,为什么还要“使用”呢?”

    我想手术室是这么说的

    public void SomeMethod() 
    { 
        ... 
    
        var foo = new Foo();
    
        ... do stuff with Foo ... 
    
    
        // Foo isn't use after here (obviously). 
        ... 
    } 
    

    应等于

    public void SomeMethod() 
    { 
        ... 
    
        using (var foo = new Foo())
        {
        ... do stuff with Foo ... 
        }
    
        // Foo isn't use after here (obviously). 
        ... 
    } 
    

    因为foo不再使用了。

    当然,答案是编译器不能很容易地解决问题。垃圾收集(在.NET中神奇地称为“dispose()”)是一个非常复杂的字段。仅仅因为下面没有使用符号,这并不意味着变量没有被使用。

    举个例子:

    public void SomeMethod() 
    { 
        ... 
    
        var foo = new Foo();
        foo.DoStuffWith(someRandomObject);
        someOtherClass.Method(foo);
    
        // Foo isn't use after here (obviously).
        // Or is it?? 
        ... 
    } 
    

    在这个例子中,somerandomObject和someotherClass 可以 两者都引用了foo所指出的内容,所以如果我们调用foo.dispose(),就会破坏它们。你说你只是在想象一个简单的例子,但是唯一一个“简单的例子”就是你所提议的方法有效,你不从foo调用任何方法,也不将foo或它的任何成员传递给任何其他东西——实际上,当你根本不使用foo的时候,在这种情况下,你可能不需要声明它。即使在那时,你也不能确定某种反射或事件黑客并没有仅仅通过它的创建就获得对foo的引用,或者foo在其构造过程中没有连接到其他东西上。

        11
  •  0
  •   Travis    15 年前

    除了上面列出的细微原因之外,由于问题在所有情况下都无法可靠地解决,所以这些“简单”的情况是代码分析工具能够并且确实能够检测到的。让编译器确定性地做一些事情,让你的自动代码分析工具告诉你什么时候你在做一些愚蠢的事情,比如忘记调用Dispose。