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

是否存在只读的运行时开销?

  •  18
  • JulianR  · 技术社区  · 15 年前

    出于某种原因,我一直认为 readonly 字段具有相关联的开销,我认为这是CLR跟踪 只读 字段是否已初始化。这里的开销是一些额外的内存使用,用于跟踪状态和分配值时的检查。

    也许是因为我不知道 只读 字段只能在构造函数内部或字段声明本身内初始化,如果不进行运行时检查,您将无法保证它不会在各种方法中被多次分配。但是现在我知道了,C编译器可以很容易地静态检查它,对吗?是这样吗?

    另一个原因是我读到了 只读 有一个“轻微”的性能影响,但他们从来没有进入这个声明,我找不到关于这个主题的信息,因此我的问题。除了运行时检查,我不知道还有什么其他性能影响。

    第三个原因是我看到了 只读 在编译的IL中保存为 initonly ,那么这个信息出现在IL中的原因是什么? 只读 仅仅是C编译器的一个保证,即字段从未被分配到构造函数或声明之外吗?

    另一方面,我发现你可以设置 readonly int 通过反射而不是clr抛出异常,如果 只读 是一个运行时检查。

    所以我的猜测是:“readonlyness”只是一个编译时特性,有人能确认/拒绝吗?如果是的话,这个信息被包括在IL中的原因是什么?

    4 回复  |  直到 12 年前
        1
  •  16
  •   Szymon Rozga    15 年前

    您必须从与访问修饰符相同的角度来查看它。访问修饰符存在于IL中,但它们真的是运行时检查吗?(1)我不能在编译时直接分配私有字段;(2)我可以使用反射来分配它们。到目前为止,它似乎没有运行时检查,比如 只读 .

    但让我们检查访问修饰符。执行以下操作:

    1. 使用公共类C创建程序集A.dll
    2. 创建引用a.dll的程序集b.exe。B.exe使用C类。
    3. 构建两个程序集。运行b.exe工作正常。
    4. 重新生成.dll,但将C类设置为内部。替换b.exe目录中的a.dll。

    现在,运行b.exe会引发运行时异常。

    在IL中也存在访问修饰符,对吗?他们的目的是什么?其目的是引用.NET程序集的其他程序集需要知道允许它们访问什么以及不允许它们访问什么,包括编译时和运行时。

    readonly在IL中似乎有类似的用途 . 它告诉其他程序集是否可以写入特定类型的字段。然而, 只读 似乎不 要进行访问修饰符在上面的示例中显示的相同运行时检查。似乎readonly是编译时检查,不会在运行时发生。请看一下这里的性能示例: Read-only performance vs const .

    同样,这并不意味着IL是无用的。IL首先确保发生编译时错误。记住,当您构建时,您不是根据代码构建的,而是根据程序集。

        2
  •  6
  •   Reed Copsey    15 年前

    如果您使用的是标准实例变量,readonly将执行与正常变量几乎相同的操作。添加的IL将成为编译时检查,但在运行时几乎被忽略。

    如果您使用的是静态只读成员,则情况会有所不同…

    由于静态只读成员是在静态构造函数期间设置的,因此JIT“知道”某个值存在。没有额外的内存-readonly只是阻止其他方法设置这个,但这是一个编译时检查。

    因为JIT知道这个成员永远不会改变,所以它在运行时会得到“硬编码”,所以最终的效果就像有一个常量值一样。区别在于,在JIT时间内,它将花费更长的时间,因为JIT编译器需要做额外的工作来硬连接readonly的值。(不过,这会非常快。)

    Expert C++/CLI 马库斯·黑格尔对此有一个相当好的解释。

        3
  •  4
  •   supercat    12 年前

    其他答案中还没有提到的一个重要点是,当访问只读字段或访问任何属性时,使用数据副本满足请求。如果所讨论的数据是一个包含超过4-8字节数据的值类型,那么这种额外复制的成本有时会很高。请注意,当结构从16字节增长到17字节时,成本会大幅增加,但结构可能会比许多应用程序中的类大很多,而且仍然比类快。 如果不经常复制 . 例如,如果假定一个类型代表三维空间中三角形的顶点。一个简单的实现是一个包含三个 float 对于每个点,可能总共36个字节。如果点和每个点内的坐标是可变的公共字段,则可以访问 someTriangle.P1.X 快速简便,无需复制顶点1的Y坐标以外的任何数据。另一方面,如果 P1 是财产还是 readonly 字段,编译器必须复制 P1 到临时结构,然后读取 X 从那。

        4
  •  3
  •   Barry Kelly    15 年前

    即使readonly只在编译时有效,仍然需要将数据存储在程序集中(即IL)。CLR是一个 共同的 语言 运行时-用一种语言编写的类可以被其他语言使用和扩展。

    因为每个用于clr的编译器都不知道如何读取和编译其他语言,以便保留 readonly 字段,该数据需要存储在程序集中,以便其他语言的编译器尊重它。

    当然,字段被标记的事实 只读 意味着JIT可以做其他事情,例如优化(例如值的内联使用)等,而不管您使用反射来更改字段的值,创建修改 initonly 对应构造函数之外的字段(实例或静态,取决于字段类型)将导致无法验证的程序集。