-
Notifications
You must be signed in to change notification settings - Fork 7.4k
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
Speed up upload by a factor of 17 #4787
Conversation
Uploads are very slow because of an unnecessary "client.connected()" check in _uploadReadByte(). Here is what happens: client.connected() is called for every byte read. WiFiClient::connected() calls recv(fd(), &dummy, 0, MSG_DONTWAIT); which takes a relatively long time, so the optimized path of returning a buffered byte via client.read() is effectively nullified. Removing the one line changed the upload speed for a 2 MB file (discarding the received data) from 22 KB/sec (before) to 367 KB/sec (after). The change is safe in the face of disconnects because client.read(), when it no longer has buffered data, calls (WiFiClient) fillBuffer(), which calls recv(), so the disconnection will be detected in due course.
I have done some basic testing on this and can confirm a huge improvement in upload speeds. |
Tested with Tasmota32 firmware upload by file function. Upload time reduced from 82 sec. to 33 sec. |
Commits history says that the assertion was added as a response on #2914 in June of 2019. |
Mabe an alternative would be to check every 16 byte or whatever would be
proper ?
|
I think the real concern here is that the socket could disappear, and then you will get a fatal error (maybe there is a simpler check for that). I'll try some tests to generate failures. The speedup is worth the occasional non-fatal error (which should just cause a resend). |
The "socket disappearance" case already works in my test setup. I will first describe the code path for that case, and then describe my test setup. When the socket disappears: Calls to _uploadReadByte() continue to succeed, returning correct data, while there is data in the WiFiClient rxbuffer. This is the fast path where client.read() checks a few memory variables and then return a buffered byte. When all of the buffered data has been returned, the next call to client.read invokes fillBuffer() via this line at the top of WiFiClient.cpp :: read():
fillBuffer() then returns 0 via this clause:
because, in r_available(), Since fillBuffer() returns 0, client.read() returns -1, causing an entry into _uploadReadByte()'s for(;;) loop. The I verified this analysis by adding a lot of Serial.println() calls to trace the code path. The only way I can see for this to hang per #2914 is if the timeout has been set to a very large value, so it appears to hang but the timeout has not been reached. There is a simple fix that involves moving the check for !client.connected inside the loop. With that check, a disconnect will abort the upload immediately, without waiting for a timeout. I will revise the PR accordingly. |
My test setup involves using Grbl_Esp32. It has a WebUI component. WebUI lets you upload files to an SD Card attached to the ESP32. To test the upload speed, I upload a large file to SD and time it with a stopwatch app. To test the raw upload speed, I modified the Grbl_ESP32 source code so that it discards the data instead of writing to the SD file. To test the client disconnect behavior, I close the browser window that is running WebUI while the upload is in progress, observing the disconnect via messages displayed on the ESP32 serial console. |
Done :) |
Uploads are very slow because of an unnecessary "client.connected()" check in _uploadReadByte().
Here is what happens:
client.connected() is called for every byte read. WiFiClient::connected() calls recv(fd(), &dummy, 0, MSG_DONTWAIT); which takes a relatively long time, so the optimized path of returning a buffered byte via client.read() is effectively nullified.
Removing the one line changed the upload speed for a 2 MB file (discarding the received data) from 22 KB/sec (before) to 367 KB/sec (after).
The change is safe in the face of disconnects because client.read(), when it no longer has buffered data, calls (WiFiClient) fillBuffer(), which calls recv(), so the disconnection will be detected in due course.