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 necessar开发者_运维百科y instead.
Can this optimization actually be done by the JITter?
Are there other optimizations that might have visible side effects, except stack trace changes?
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.
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".
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?"
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.
精彩评论