Skip to content
This repository was archived by the owner on Aug 31, 2021. It is now read-only.

WIP: [[ Bugfix 21305 ]] Improve performance of stackfile saving on Windows #7319

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from

Conversation

runrevmark
Copy link
Contributor

@runrevmark runrevmark commented Apr 11, 2020

The patch adds a simple write buffer to the Win32 MCStdioFileHandle implementation. As serializing stacks usually involves a very large number of 1, 2 and 4 byte writes, the buffer vastly reduces the number of WriteFile Win32 API calls which are made.

In order to ensure there aren't any problems with saving stackfiles if the write buffer does not flush on close, a call to MCS_flush has been added to the stackfile serialization code after the last terminating byte is written. This ensures that the original stackfile will not be removed until it is certain that the new stackfile is fully written.

Note: The lack of a flush in the above case is actually a long standing 'hole' in stackfile saving. Whilst Windows did not have a engine-side write buffer, the CloseHandle API call could still fail, and the other platforms all used stdio FILE handles underneath which could also fail on fclose.

@runrevmark runrevmark self-assigned this Apr 11, 2020
@runrevmark runrevmark changed the title [[ Bugfix 21305 ]] Improve performance of saving stack on Windows WIP: [[ Bugfix 21305 ]] Improve performance of saving stack on Windows Apr 11, 2020
@livecodepanos livecodepanos added this to the 9.6.0-rc-1 milestone Apr 16, 2020
@runrevmark runrevmark force-pushed the bugfix-21305 branch 3 times, most recently from 8adbaad to c220219 Compare April 27, 2020 18:39
@runrevmark runrevmark removed the WIP label Apr 27, 2020
@runrevmark runrevmark changed the title WIP: [[ Bugfix 21305 ]] Improve performance of saving stack on Windows [[ Bugfix 21305 ]] Improve performance of saving stack on Windows Apr 27, 2020
@runrevmark
Copy link
Contributor Author

runrevmark commented Apr 27, 2020

Okay so I think this is ready for review - I'm still slightly on the fence about including both metrics caching and the write buffer addition in 9.6. This definitely and without a doubt completely eliminates the difference in stackfile save speed between macOS and Windows; I also have a feeling it might significantly mitigate the issues with 'active' virus-scan technology too (although despite a lot of digging around that subject I have not managed to determine what exactly triggers them - but large numbers of WriteFile API calls seem to me, at least, to be a good guess).

The metric caching is easy to assess correctness of, and even if there was a flaw it wouldn't be a disaster (the worse that would happen is that you might get the wrong UI font, or a DPI calc would be wrong).

The other part - the write buffer addition needs more consideration - as if that is not correct (well at least as correct as before - see below) then that will cause stackfile corruption.

The current MCSystemFileHandle API is flawed in that it does not have the ability to return errors for some operations - the most critical being Close

This flaw has been in there forever (even before the MCSystemFileHandle interface was added!) and on all platforms and is due to MCS_close not signalling an error state - which it could with write-buffered file implementations (which all platforms are - even if not explicitly in engine code). The mitigation for this is to ensure that the stream is explicitly MCS_flushd after the last byte is written. So from that point of view this is now 'better' than before from a correctness point of view - assuming the write buffering is correctly implemented, that is.

The other two methods which cannot return an error are GetFileSize and Tell. In this case their return values are synthesized using the current write offset to give the value they would return if any unwritten data had been written. I don't think this synthesis makes any difference in practice.

@runrevmark
Copy link
Contributor Author

runrevmark commented Apr 27, 2020

@mwieder It isn't wise to post comments on code in PRs which are marked as WIP as they can vanish from view (somewhat arbitrarily at times!). We use st-git (http://www.procode.org/stgit/) a lot internally to ensure well organised commits... Unfortunately, this means commit hashes are never stable back to the target/base branch and this causes issues for GitHub's tracking of source-level comments. Comments in the PR conversation for WIP things is fine though (as usual) :)

@mwieder
Copy link
Contributor

mwieder commented Apr 27, 2020

@runrevmark no worries - I missed the WIP label there.

@livecodeian
Copy link
Contributor

@runrevmark GetFileSize could return an incorrect value if the file was opened in update mode. Aside from that I don't see any other problems 😉

@montegoulding
Copy link
Member

@runrevmark I think you need a call to MCS_flush in MCFilesExecWriteToStream or we risk a breaking change in behaviour. Ideally this would be under user control at some point.

@montegoulding
Copy link
Member

@runrevmark are we worried about successfully creating a write buffer that can never be written when the file is only opened in read mode?

@runrevmark
Copy link
Contributor Author

@montegoulding
I take it the flush would be to ensure writes done by 'write to file' are immediate (so can be seen by other processes)? I guess this would be wise we do open files in SHARE mode on Win32 (even for write). However, it would be a change in behavior on other platforms then (which buffer by virtue of stdio). Perhaps we should just disable the write buffering for all but stackfile save streams for now.

@montegoulding
We only create a write buffer if Write is called and it is not a pipe... So I'm not sure how one could be created for a stream which is only read? Streams opened for read are only ever read from, and those opened for write are only ever written to - there is a guard in the write syntax methods to check whether the target file has been open for read... Is there an example you can see where this might happen?

@runrevmark
Copy link
Contributor Author

@livecodeian That is a very good point re update mode. Before changing the mechanism (I'll probably go for deferred error reporting) I'll double check whether GetFileSize is ever called when the stream is a stackfile write stream (as discussed above we probably want to limit the buffering to those types of streams for now).

@runrevmark
Copy link
Contributor Author

@livecodeian I think I've resolved the file size issue - it now computes the filesize as the max of (filesize + buffered-write-size) and (fileptr + buffered-write-size). I've also mitigated when write buffering will occur - restricting it to when stackfiles are being saved.

@montegoulding I've made it so that write buffering is only performed when the file handle is created for a stream which is used for writing a stackfile so that should resolve the issue with the change in behavior on windows for 'write to file' syntax this would have caused otherwise.

To resolve the latter I've added a new file open mode BufferedWrite which does the same thing as Write on all platforms apart from Windows; but on windows enables the write buffer in the created file handle. Hopefully I've added the relevant checks in all the right places! The BufferedWrite mode is only used when opening a stream which is to be used to write a stackfile.

if (this == IO_stdin)
return true; // Shouldn't it return false???

if (m_handle == NULL)
return false;

/* If this is not a pipe then we may have a write buffer. */
if (!m_is_pipe)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@runrevmark I think you need && m_should_buffer_writes here ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh! I can simplify that - I can set should_buffer_writes to false if the stream is a pipe. Thanks!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@montegoulding That should be addressed now - I've changed this if to m_should_buffer_writes, and only set the that to true if not a pipe in the constructor.

@livecodeian
Copy link
Contributor

livecodeian commented Apr 28, 2020

@runrevmark shouldn't that be max(actual_file_size, file_ptr + buffered_write_size) as the buffered_write_size won't affect the size unless it goes beyond the current EOF?

Alternatively you could just flush the buffer before getting the size

@runrevmark
Copy link
Contributor Author

@livecodeian Yes - you are correct - think I've amended that now :)

@runrevmark runrevmark added the WIP label Apr 28, 2020
@runrevmark
Copy link
Contributor Author

From a comment made by @montegoulding offline, I double checked the use of Tell() and Seek() when saving stackfiles. It turns out that MCS_tell() and MCS_writeat() are both used. Therefore Seek and Tell have to be able to work correctly with pending data to write. This means I will have to move operation to always flushing on Seek and Tell, but deferring any reported error until the next operation which can return one.

Marking this as WIP for now.

This patch adds a very simple write buffer to the MCStdioFileHandle
implementation on Win32. The write buffer is used on non-pipe handles
and is initialized on first use of a write. The buffer has a fixed
size of 64k, and a maximum single write size of 16k. Buffering writes
causes a significant improvement in performance of stack serialization
as that process results in a lot of 1, 2 and 4 byte individual writes.

Additionally, a call to `MCS_flush` has been added after the end byte
is written when serializing stackfiles. This ensures that if any buffered
write data fails to be written, an error is generated and the original
stackfile is not deleted. This specific change affects all platforms.
This patch adds a release note for bug 21305 where stackfile saving
on Windows was considerably slower than other platforms.
@runrevmark
Copy link
Contributor Author

I decided a modicum of caution was required here - so have split out the metrics caching into #7342. This needs a fair bit more work to be mergeable.

@runrevmark runrevmark changed the title [[ Bugfix 21305 ]] Improve performance of saving stack on Windows WIP: [[ Bugfix 21305 ]] Improve performance of stackfile saving on Windows Apr 30, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants