Skip to content
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

Improve performance of adding and removing files #1949

Merged
merged 27 commits into from
Jan 13, 2020
Merged

Conversation

goto-bus-stop
Copy link
Contributor

@goto-bus-stop goto-bus-stop commented Nov 20, 2019

This PR contains a bunch of performance improvements. I just
applied them and tested the happy path, so we need to check if
everything still works 🙈

If we want to get this in earlier we could land the addFile()
optimizations first, and the virtualization stuff later with more
testing.

Overview:

core.addFiles()

Previously, adding 1300 files took around 260ms, and looked like
this in Firefox's performance tab:

Flamegraph

All of the downward peaks are setState() calls and GC. The first
commit adds an addFiles() method that takes an array of files, and
does setup work for all of them before doing a single setState()
call.

Now it takes around 60ms, and looks like this:

Flamegraph

Here, most of the time is spent generating file IDs and guessing file
types. Those would be areas to look at next.

hideAllPanels()

After the last commit, addFiles() still spends a lot of time in
emit() and hideAllPanels(). The reason is that addFiles() still
emits all the hundreds of file-added events, and the Dashboard responds
to each with hideAllPanels(), which does a state update. But this all
happens synchronously, and the state almost certainly did not change
since the last file-added event that fired a millisecond ago.

This adds a check to avoid the state update if it is not necessary.

Flamegraph

Adding 1300 files takes about 40ms now.

With this change, the addFiles() call is no longer the slowest
part—now preact rendering all the items is!

generateFileID and getFileNameAndExtension

Replaces some clever things with more mundane and faster things!

Now, generateFileID is a bunch of string concatenations.
Now, getFileNameAndExtension uses lastIndexOf() instead of a regex.

Flamegraph

Adding 1300 files takes about 25ms.

Virtualizing <FileList />

This will be done separately.

This is using an adaptation of preact-virtual-list to render only the
file items that are visible or close enough to be visible.

I didn't properly measure the performance impact of this, so that's
unclear.

Lazy thumbnail generation

This will be done separately.

This patch adds a lazy: true option to the ThumbnailGenerator plugin,
which will cause it to wait for thumbnail:request events before
generating a thumbnail for a file. When a <FileItem /> is mounted, it
emits one of these events.

If the waitForThumbnailsBeforeUpload option is set, lazy generation is
disabled.

Previously, adding 1300-ish files took around 260ms, and looked like
this in Firefox's performance tab:

![Flamegraph](https://i.imgur.com/08veuoU.png)

All of the downward peaks are `setState()` calls and GC.

Now it takes around 60ms, and looks like this:

![Flamegraph](https://i.imgur.com/xFdwVBV.png)

Here, most of the time is spent generating file IDs and guessing file
types. Those would be areas to look at next.
After the last commit, `addFiles()` still spends a lot of time in
`emit()` and `hideAllPanels()`. The reason is that `addFiles()` still
emits all the hundreds of file-added events, and the Dashboard responds
to each with `hideAllPanels()`, which does a state update. But this all
happens synchronously, and the state almost certainly did not change
since the last `file-added` event that fired a millisecond ago.

This adds a check to avoid the state update if it is not necessary.

![Flamegraph](https://i.imgur.com/KhuD035.png)

Adding 1300 files takes about 40ms now.

With this change, the `addFiles()` call is no longer the slowest
part—now preact rendering all the items is!
Replaces some clever things with more mundane and faster things!

Now, generateFileID is a bunch of string concatenations.
Now, getFileNameAndExtension uses `lastIndexOf()` instead of a regex.

![Flamegraph](https://i.imgur.com/ZQ1IhxI.png)

Adding 1300 files takes about 25ms.
module.exports = (props) => {
const noFiles = props.totalFileCount === 0
const dashboardFilesClass = classNames(
'uppy-Dashboard-files',
{ 'uppy-Dashboard-files--noFiles': noFiles }
)

// 190px height + 2 * 5px margin
const rowHeight = 200
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we could add a comment in CSS that the height is tied to this property in JS as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It actually also needs different heights for different uppy-size--xyz classes too, will also add that. 190 + 2*5px is only for uppy-size--lg

@arturi
Copy link
Contributor

arturi commented Nov 26, 2019

This is AMAZING. Really fast with 10,000 files on Firefox and Chrome, Mac OS.

  1. Cancelling 10,000 files takes quite a while and hangs a browser a little. We could go back to setting state to files: {} instead of calling foreach file -> removeFile(file), but removeFile also tracks upload state, and people rely on file-removed events (which we could just emit in a loop).

  2. Testing on IE11, adding 10,000 files via browse results in this:

    Screen Shot 2019-11-26 at 19 48 01

    Could be IE11’s own limitations.

@goto-bus-stop
Copy link
Contributor Author

removeFiles() now does a single state update. Went from ~2 seconds for 1300 files on my Firefox to < 30ms, lol

@goto-bus-stop goto-bus-stop marked this pull request as ready for review November 27, 2019 15:03
@arturi
Copy link
Contributor

arturi commented Nov 27, 2019

AMAZING again, “cancel all” is instantaneous now.

We should also probably change uppy.addFile() to uppy.addFiles() in DragDrop and FileInput, right?

@arturi arturi requested a review from lakesare November 28, 2019 15:57
@lakesare
Copy link
Contributor

Great job!

Virtualizing

Virtualizing doesn't work very well for me, Chrome MacOS + 3055 files + width 531px results in files getting invisible for me:

image

They become visible again once I scroll past some point, and then they disappear again.


if (errors.length > 0) {
const err = new Error('Multiple errors occured while adding files')
err.errors = errors
Copy link
Contributor

Choose a reason for hiding this comment

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

Hm, if one error occurs after we try to add one file, - we will get a generic Error: Multiple errors occured while adding files, with no indication of what happened.
That could work if we were catching and logging error.errors everywhere after we use .addFiles, but we don't yet do that.

Should we log each error ourselves in addFiles() instead? That would also make:

    try {
      this.uppy.addFiles(descriptors)
    } catch (err) {
      this.uppy.log(err)
    }

tradition unnecessary?

PS. Looks like it's occurrRred (https://en.wiktionary.org/wiki/occured).

@goto-bus-stop
Copy link
Contributor Author

I think resolving the potential accessibility issues with VirtualList is gonna take some time so I'll prob pull it out of this PR into a different one, so we can get the add/removeFiles improvements in soon

@lakesare
Copy link
Contributor

lakesare commented Dec 2, 2019

@goto-bus-stop, have you tried something like https://github.com/bvaughn/react-window? Looks like it works well with tabs.

@goto-bus-stop goto-bus-stop changed the title Gotta go fast Improve performance of adding and removing files Dec 4, 2019
@goto-bus-stop goto-bus-stop merged commit 1463ee7 into master Jan 13, 2020
@goto-bus-stop goto-bus-stop deleted the gotta-go-fast branch January 13, 2020 11:29
@arturi
Copy link
Contributor

arturi commented Jan 14, 2020

💪 🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants