You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
JIT: consider optimizing constructor pattern for struct creation #9237
Currently if you newobj a struct via a constructor call the jit assumes the struct's old value may be read during the construction of the new value. So the pattern emitted is:
;; zero temp obj storage
;; call constructor on temp obj
;; copy temp obj to the destination
When the final destination is register promoted all this tends to get cleaned up, but when the final destination can't be register promoted the code the jit produces can be surprisingly large and slow.
In most cases the aliasing the jit is worried about does not happen, and the new value could be constructed directly into the destination. And also in most cases this construction will fully write the fields of the struct so the zero initialization is not necessary.
@AndyAyersMS
I think C# should allow to use C-style struct constructor to tell the compiler explicitly to initialize a struct just in place.
For example:
The issue I was seeing with object initialization is
vara=newA{field=20};
Doesn't need to fully initialize all the fields; so if it was a struct of 20 fields, can init 5 for example. However its probably faster to zero init all of them contiguously and then set the 5 rather than intermingling zeros and values for a patterned initialize.
Didn't mean to imply that the zero init is never necessary, just that frequently the subsequent code overwrites all the zeros and so (modulo some assumptions about GC safe points) those zero init stores are dead.
For zero init there is also the question of whether you can zero field by field or some kind of whole struct block init, and whether or not you should zero any padding voids.
Hard to say for sure when we might get to this. I am hoping maybe we can address some subset of cases relatively soon. The general solution likely requires first-class structs and new code to perform liveness on entire structs.
The challenge is that to ensure correct semantics we need to show that for struct-typed temp and dest
temp = 0
ctor(&temp, ...)
dest = temp
gives the same result as
ctor(&dest, ...)
and that crucially depends on what has happened to dest in the past, what is in the ... and what ctor does. The worry is that somehow the values in dest might be looked at when computing the values for temp, and so the optimized version, which effectively interleaves construction and the copy could change the values that end up in dest.
Because of the need to reason about past behavior it is not easy to fix this as the IL is imported. Instead the jit must produce the general pattern with the temp and then sometime later go back and clean it up if possible. However there might be some cases in the importer where we can say for sure that the old values can't be read and produce the optimized pattern directly.
#8891 (reverse copy prop of structs) has similar ambitions -- this issue is more about the timing and placement.
It would be preferable to catch (at least some cases of) this early and never introduce the temp, instead of introducing the temp and then optimizing away its use.
Among other things this pattern often appears in initializer type methods and so causes extremely bloated codegen in debug/minopts.
Activity
benaadams commentedon Nov 8, 2017
Language-wise; C# for example, forces complete initialization of all fields in the .ctor
I assume the object initialization pattern would be a wrinkle
And require zero init + assignment; rather than just assignment? (Though shouldn't block operating directly on destination)
ygc369 commentedon Nov 8, 2017
@AndyAyersMS
I think C# should allow to use C-style struct constructor to tell the compiler explicitly to initialize a struct just in place.
For example:
benaadams commentedon Nov 8, 2017
@ygc369 you'd need to file that in the https://github.com/dotnet/csharplang repository as its language syntax rather than runtime
For same effect can currently do
The issue I was seeing with object initialization is
Doesn't need to fully initialize all the fields; so if it was a struct of 20 fields, can init 5 for example. However its probably faster to zero init all of them contiguously and then set the 5 rather than intermingling zeros and values for a patterned initialize.
AndyAyersMS commentedon Nov 8, 2017
Didn't mean to imply that the zero init is never necessary, just that frequently the subsequent code overwrites all the zeros and so (modulo some assumptions about GC safe points) those zero init stores are dead.
For zero init there is also the question of whether you can zero field by field or some kind of whole struct block init, and whether or not you should zero any padding voids.
benaadams commentedon Nov 8, 2017
Was just highlighting maybe a useful corner case example.
I'd love to see this optimization as its a pretty common pattern when using a struct .ctor (with params)
ygc369 commentedon Nov 16, 2017
@AndyAyersMS
Very happy to see this optimization, structs should be initialized just in place, no copying needed.
When would it be done?
AndyAyersMS commentedon Nov 16, 2017
Hard to say for sure when we might get to this. I am hoping maybe we can address some subset of cases relatively soon. The general solution likely requires first-class structs and new code to perform liveness on entire structs.
The challenge is that to ensure correct semantics we need to show that for struct-typed
tempanddestgives the same result as
and that crucially depends on what has happened to
destin the past, what is in the...and whatctordoes. The worry is that somehow the values indestmight be looked at when computing the values fortemp, and so the optimized version, which effectively interleaves construction and the copy could change the values that end up indest.Because of the need to reason about past behavior it is not easy to fix this as the IL is imported. Instead the jit must produce the general pattern with the temp and then sometime later go back and clean it up if possible. However there might be some cases in the importer where we can say for sure that the old values can't be read and produce the optimized pattern directly.
AndyAyersMS commentedon May 7, 2018
#8891 (reverse copy prop of structs) has similar ambitions -- this issue is more about the timing and placement.
It would be preferable to catch (at least some cases of) this early and never introduce the temp, instead of introducing the temp and then optimizing away its use.
Among other things this pattern often appears in initializer type methods and so causes extremely bloated codegen in debug/minopts.