C# 优化和副作用

发布于 2024-10-12 20:07:51 字数 453 浏览 4 评论 0原文

C# 编译器或 JITter 完成的优化是否会产生明显的副作用?

我有一个例子。

var x = new Something();
A(x);
B(x);

当调用 A(x) 时,x 保证在 A 结束时保持活动状态 - 因为 B 使用相同的参数。但是,如果 B 定义为

public void B(Something x) { }

那么优化器可以消除 B(x),然后 GC.KeepAlive(x) 调用可能会是必要的。

这种优化真的可以通过JITter来完成吗?

除了堆栈跟踪更改之外,是否还有其他优化可能会产生明显的副作用?

Can optimizations done by the C# compiler or the JITter have visible side effects?

One example I've though off.

var x = new Something();
A(x);
B(x);

When calling A(x) x is guaranteed to be kept alive to the end of A - because B uses the same parameter. But if B is defined as

public void B(Something x) { }

Then the B(x) can be eliminated by the optimizer and then a GC.KeepAlive(x) call might be necessary instead.

Can this optimization actually be done by the JITter?

Are there other optimizations that might have visible side effects, except stack trace changes?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

依 靠 2024-10-19 20:07:51

如果您的函数 B 不使用参数 x,那么消除它并尽早收集 x 不会产生任何明显的副作用。

要成为“可见的副作用”,它们必须对程序可见,而不是对调试器或对象查看器等外部工具可见。

If your function B does not use the parameter x, then eliminating it and collecting x early does not have any visible side effects.

To be "visible side effects", they have to be visible to the program, not to an external tool like a debugger or object viewer.

岁月蹉跎了容颜 2024-10-19 20:07:51

当调用 A(x) 时,x 保证在 A 结束时保持活动状态 - 因为 B 使用相同的参数。

这种说法是错误的。假设方法 A 总是抛出异常。抖动可以知道永远不会到达B,因此可以立即释放x。假设方法 A 在最后一次引用 x 之后进入无条件无限循环;同样,抖动可以通过静态分析知道 x 永远不会再次被引用,并安排对其进行清理。我不知道抖动是否真正执行了这些优化;它们看起来很狡猾,但它们是合法的。


这种优化(即对未在任何地方使用的引用进行早期清理)实际上可以由 JITter 完成吗?

是的,在实践中,它已经完成了。这不是可观察到的副作用。

规范第 3.9 节证明了这一点,为了您的方便,我引用了该节:

如果该对象或其任何部分无法通过任何可能的继续执行(除了运行析构函数之外)来访问,则该对象被视为不再使用,并且有资格销毁。 C# 编译器和垃圾收集器可以选择分析代码以确定将来可能使用哪些对象引用。例如,如果作用域内的局部变量是对对象的唯一现有引用,但在从过程中的当前执行点开始的任何可能的继续执行中从未引用该局部变量,则垃圾收集器可能(但不需要)将该对象视为不再使用。


C# 编译器或 JITter 完成的优化会产生明显的副作用吗?

您的问题在规范的第 3.10 节中得到了解答,为了您的方便,我在此引用该部分:

继续执行 C# 程序
这样每个的副作用
执行线程保存在
关键执行点。

一面
效果定义为读或写
易失性字段的写入
非易失性变量,写入
外部资源,以及抛出
一个例外。

关键执行
这些点的顺序
必须保留的副作用是
对易失性字段、锁定语句的引用,
以及线程的创建和终止。

执行环境可以自由选择
更改 C# 的执行顺序
计划,但须符合以下条件
约束:

数据依赖是
保存在一个线程内
执行。也就是说,每个值
变量的计算就好像所有
线程中的语句被执行
按原始程序顺序。

初始化排序规则是
保存下来。


保留副作用的顺序
关于易失性读取和
写道。

此外,
执行环境不需要
评估表达式的一部分,如果它
可以推断出该表达式的
值未被使用并且不需要
产生副作用(包括
任何由调用方法或
访问易失性字段)。

什么时候
程序执行被中断
异步事件(例如
另一个线程抛出的异常),
不能保证
可观察到的副作用可见于
原始程序顺序。

倒数第二段我相信是您最关心的一段;也就是说,运行时允许执行哪些优化来影响可观察到的副作用?允许运行时执行不影响可观察到的副作用的任何优化。

请注意,特别是数据依赖性仅保留在执行线程内。从另一个执行线程观察时,保证保留数据依赖性。

如果这不能回答您的问题,请提出更具体的问题。特别是,如果您不认为上面给出的定义与您对“可观察到的副作用”的定义不匹配,则需要对“可观察到的副作用”进行仔细和精确的定义,以便更详细地回答您的问题。

When calling A(x) x is guaranteed to be kept alive to the end of A - because B uses the same parameter.

This statement is false. Suppose method A always throws an exception. The jitter could know that B will never be reached, and therefore x can be released immediately. Suppose method A goes into an unconditional infinite loop after its last reference to x; again, the jitter could know that via static analysis, determine that x will never be referenced again, and schedule it to be cleaned up. I do not know if the jitter actually performs these optimization; they seem dodgy, but they are legal.


Can this optimization (namely, doing early cleanup of a reference that is not used anywhere) actually be done by the JITter?

Yes, and in practice, it is done. That is not an observable side effect.

This is justified by section 3.9 of the specification, which I quote for your convenience:

If the object, or any part of it, cannot be accessed by any possible continuation of execution, other than the running of destructors, the object is considered no longer in use, and it becomes eligible for destruction. The C# compiler and the garbage collector may choose to analyze code to determine which references to an object may be used in the future. For instance, if a local variable that is in scope is the only existing reference to an object, but that local variable is never referred to in any possible continuation of execution from the current execution point in the procedure, the garbage collector may (but is not required to) treat the object as no longer in use.


Can optimizations done by the C# compiler or the JITter have visible side effects?

Your question is answered in section 3.10 of the specification, which I quote here for your convenience:

Execution of a C# program proceeds
such that the side effects of each
executing thread are preserved at
critical execution points.

A side
effect is defined as a read or write
of a volatile field, a write to a
non-volatile variable, a write to an
external resource, and the throwing of
an exception.

The critical execution
points at which the order of these
side effects must be preserved are
references to volatile fields, lock statements,
and thread creation and termination.

The execution environment is free to
change the order of execution of a C#
program, subject to the following
constraints:

Data dependence is
preserved within a thread of
execution. That is, the value of each
variable is computed as if all
statements in the thread were executed
in original program order.

Initialization ordering rules are
preserved.

The
ordering of side effects is preserved
with respect to volatile reads and
writes.

Additionally, the
execution environment need not
evaluate part of an expression if it
can deduce that that expression’s
value is not used and that no needed
side effects are produced (including
any caused by calling a method or
accessing a volatile field).

When
program execution is interrupted by an
asynchronous event (such as an
exception thrown by another thread),
it is not guaranteed that the
observable side effects are visible in
the original program order.

The second-to-last paragraph is I believe the one you are most concerned about; that is, what optimizations is the runtime allowed to perform with respect to affecting observable side effects? The runtime is permitted to perform any optimization which does not affect an observable side effect.

Note that in particular data dependence is only preserved within a thread of execution. Data dependence is not guaranteed to be preserved when observed from another thread of execution.

If that doesn't answer your question, ask a more specific question. In particular, a careful and precise definition of "observable side effect" will be necessary to answer your question in more detail, if you do not consider the definition given above to match your definition of "observable side effect".

梦幻的心爱 2024-10-19 20:07:51

在您的问题中包含 B 只会让事情变得混乱。给定以下代码:

var x = new Something();
A(x);

假设 A(x) 是托管代码,则调用 A(x) 维护对 x 的引用,因此垃圾在 A 返回之前,收集器无法收集 x。或者至少直到 A 不再需要它。 JITer 完成的优化(不存在错误)不会过早收集 x

您应该定义“可见副作用”的含义。人们希望 JITer 优化至少能产生使代码更小或更快的副作用。这些是“可见的”吗?或者你的意思是“不受欢迎的”?

Including B in your question just confuses the matter. Given this code:

var x = new Something();
A(x);

Assuming that A(x) is managed code, then calling A(x) maintains a reference to x, so the garbage collector can't collect x until after A returns. Or at least until A no longer needs it. The optimizations done by the JITer (absent bugs) will not prematurely collect x.

You should define what you mean by "visible side effects." One would hope that JITer optimizations at least have the side effect of making your code smaller or faster. Are those "visible?" Or do you mean "undesireable?"

凉月流沐 2024-10-19 20:07:51

Eric Lippert 开始了一个关于重构的精彩系列,这让我相信 C# 编译器和 JITter 确保不会引入副作用。 第 1 部分< /a> 和 第 2 部分 目前已上线。

Eric Lippert has started a great series about refactoring which leads me to believe that the C# Compiler and JITter makes sure not to introduce side effects. Part 1 and Part 2 are currently online.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文