-
Notifications
You must be signed in to change notification settings - Fork 4.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
HostWriter Performance Improvements #48774
Conversation
brianrob
commented
Feb 25, 2021
- Use CopyOnWrite MemoryMappedFiles to avoid copying the template host.
- Avoid a file open/read when attempting to remove a MachO signature when we know the input host is a PE file.
…'t attempt to remove the MachO signature for known PE files.
Tagging subscribers to this area: @vitek-karas, @agocke Issue Details
|
@vitek-karas, @agocke I've marked this as a draft PR so that I can get some additional coverage. At the same time, I'm interested in your thoughts. I'm looking to improve this since it shows up as an area of opportunity in the dev inner-loop performance effort, and the file copy has a relatively heavy impact on Windows when antivirus is involved. I still need to put together some comparison numbers to see if this has a material performance benefit. |
…le permission. This is OK, because the installed version of the apphost has -rwxr-xr-x permissions.
For additional test, I verified with the latest bits that the host, after transform is identical to one without my change on Windows and Linux. It appears that on OSX, we don't emit a host. Is this new? If so, we can probably trim out the MachO signature removal, unless this is just temporary. |
} | ||
finally | ||
{ | ||
if (memoryMappedViewAccessor != null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can either of these actually be null here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. As an example, if MemoryMappedFile.CreateFromFile
throws, then memoryMappedViewAccessor
would be null
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oop..
int pos = 0; | ||
int bufSize = 16384; //16K | ||
|
||
byte[] buf = new byte[bufSize]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is ArrayPool available?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, though I purposely did not use it here. This code is invoked from MSBuild once per-process, and the only existing use of ArrayPool
that I've been able to find is from RegularExpressions
, which allocations char[]
. My goal here is to avoid incurring the cost of initializing an ArrayPool<byte>
when we only need one byte[] in this code for the life of the process.
FYI @ericstj |
I believe this will fix #3832 |
RetryUtil.RetryOnWin32Error(UpdateResources); | ||
|
||
RetryUtil.RetryOnIOError(RemoveSignatureIfMachO); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Won't both of these reopen the file? Can they be rewritten to use the memory mapped file?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, both of these will re-open the file. UpdateResources
uses win32 APIs to enumerate and add the resources. There might be a way to do this against the existing open MemoryMappedFile
, but likely the cost of doing so is going to be higher than we want to pay, if it's possible. It would require that we re-write the functionality.
RemoveSignatureIfMachO
is potentially possible, but it became much harder to implement because the size of the file has to change. That said, I did change the code so that this doesn't get called unconditionally for PE files.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We may eventually want to rewrite UpdateResources so it can be supported xplat, but the work is not planned right now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a trick where you can keep an open handle read/write with sharing and it will prevent some other process from getting exclusive access. So even if you didn't re-implement these over the file mapping you might be able to keep a handle open to prevent interference. This works so long as the Windows APIs you're calling specify read/write sharing. If the Windows APIs themselves don't allow sharing your held handle prevents them from opening the file.
You can procmon the Windows API to see how it opens the file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did some experiments with this, but it doesn't look like this is possible. The file is loaded via LoadLibraryEx
, which does not appear to allow for sharing when an existing handle has write permissions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see, I guess when UpdateResource is rewritten that would fix this. Presumably some code can be copied from CSC since it knows how to do that.
For RemoveSignatureIfMachO
just make your new BinaryUtils.SaveFile
take a stream and open the stream above. Then change RemoveSignatureIfMachO
to take a stream and give it the written file stream (assuming reordering of these ops is safe). This is minimal change and saves one roll-of-the-dice around retries.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good call. At this point it is for sure safe because resources can only be copied into a PEFile, and the signature can only be removed from MachO binaries. So the operations cannot be run on the same binary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another thought on UpdateResource: right now this needs to be incremental on the binary output of CSC, which means the host needs to be rewritten whenever CSC recompiles. I wonder if instead you could use the input to CSC here (eg: -win32res
parameter). Potentially eliminating this task from most normal inner-loop scenarios.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's an interesting point. I have filed dotnet/sdk#16173 to track this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM aside from a few comments. Thanks for the work!
RetryUtil.RetryOnWin32Error(UpdateResources); | ||
|
||
RetryUtil.RetryOnIOError(RemoveSignatureIfMachO); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We may eventually want to rewrite UpdateResources so it can be supported xplat, but the work is not planned right now.
if (memoryMappedViewAccessor != null) | ||
{ | ||
memoryMappedViewAccessor.Dispose(); | ||
} | ||
if (memoryMappedFile != null) | ||
{ | ||
memoryMappedFile.Dispose(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (memoryMappedViewAccessor != null) | |
{ | |
memoryMappedViewAccessor.Dispose(); | |
} | |
if (memoryMappedFile != null) | |
{ | |
memoryMappedFile.Dispose(); | |
} | |
memoryMappedViewAccessor?.Dispose(); | |
memoryMappedFile?.Dispose(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
|
||
RetryUtil.RetryOnIOError(RemoveSignatureIfMachO); | ||
|
||
RetryUtil.RetryOnIOError(SetLastWriteTime); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this necessary if we're calling SaveFile
? It looks like the previous concern was that simple memory map modification didn't update the last write time, but I'm unclear if that's also true of SaveFile
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. It looks like the last write time is updated when we open and write the stream, so I've removed this call.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@brianrob Any comment on this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Never mind, the diff here just didn't reparent after your changes.
7077c09
to
825932c
Compare
I think I've hit all the feedback. @ericstj let me know if you have anything else based on the latest iteration. |
RetryUtil.RetryOnWin32Error(UpdateResources); | ||
|
||
RetryUtil.RetryOnIOError(RemoveSignatureIfMachO); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another thought on UpdateResource: right now this needs to be incremental on the binary output of CSC, which means the host needs to be rewritten whenever CSC recompiles. I wonder if instead you could use the input to CSC here (eg: -win32res
parameter). Potentially eliminating this task from most normal inner-loop scenarios.