Skip to content

Improve hot reload performance - #28152

Merged
jonahwilliams merged 68 commits into
flutter:masterfrom
jonahwilliams:make_hot_reload_faster
Mar 15, 2019
Merged

jonahwilliams merged 68 commits into
flutter:masterfrom
jonahwilliams:make_hot_reload_faster

Conversation

@jonahwilliams

@jonahwilliams jonahwilliams commented Feb 19, 2019 •

Copy link
Copy Markdown
Contributor

Reduces devfs operations, diff of packages, and computation of dart dependencies. Instead of traversing packages, we also include information from pubspec to remove transitive dependencies, dev_dependencies, and any cached packages from changing.

We also log a warning when updating a package version or adding a new package, since in its current state this may cause a hot reload crash.

This moves the best case hot reload times for an emulator/simulator/desktop closer to 250 ms.

In cases where a large number of libraries are invalidated (50+), times are now bottle-necked by frontend server instead of filesystem operations.

Linux Emulator w/Change

Performing hot reload...                                                
Reloaded 0 of 575 libraries in 75ms.

Performing hot reload...                                                
Reloaded 0 of 575 libraries in 78ms.

Performing hot reload...                                                
Reloaded 0 of 575 libraries in 50ms.

Linux Emulator at HEAD

Performing hot reload...                                                
Reloaded 0 of 575 libraries in 412ms.

Performing hot reload...                                                
Reloaded 0 of 575 libraries in 413ms.

Performing hot reload...                                                
Reloaded 0 of 575 libraries in 395ms.

Windows Emulator with change

Performing hot reload...
Reloaded 0 of 575 libraries in 185ms.

Performing hot reload...
Reloaded 0 of 575 libraries in 185ms.

Performing hot reload...
Reloaded 0 of 575 libraries in 143ms.

Windows Emulator at HEAD

Performing hot reload...
Reloaded 0 of 575 libraries in 887ms.

Performing hot reload...
Reloaded 0 of 575 libraries in 797ms.

Performing hot reload...
Reloaded 0 of 575 libraries in 759ms.

Mac/Pixel 2 w/ change

Performing hot reload...                                                
Reloaded 0 of 577 libraries in 335ms.

Performing hot reload...                                                
Reloaded 0 of 577 libraries in 300ms.

Performing hot reload...                                                
Reloaded 0 of 577 libraries in 320ms.

Mac/Pixel 2 at HEAD

Performing hot reload...                                                
Reloaded 0 of 577 libraries in 869ms.

Performing hot reload...                                                
Reloaded 0 of 577 libraries in 857ms.

Performing hot reload...                                                
Reloaded 0 of 577 libraries in 898ms.

Improved trace for hot reload command:
i3s3fdxpl8q

We still have some additional overhead on windows from file stat commands: I think these could be removed as well.

capture

Trace on Windows

Below is a trace captured from a hot reload (the 6th reload) on an Android emulator running on Windows. I added Timeline events for _computeDartDependencies as well as the file stat operations in the devfs

capture_perf

And here is the top of the CPU profile:

capture3

The timeline events were added as follows:
flutter_tools/lib/src/run_hot.dart

index 116768a..6ba6b0b 100644
--- a/packages/flutter_tools/lib/src/run_hot.dart
+++ b/packages/flutter_tools/lib/src/run_hot.dart
@@ -3,6 +3,7 @@
 // found in the LICENSE file.

 import 'dart:async';
+import 'dart:developer';

 import 'package:json_rpc_2/error_code.dart' as rpc_error_code;
 import 'package:json_rpc_2/json_rpc_2.dart' as rpc;
@@ -99,6 +100,8 @@ class HotRunner extends ResidentRunner {
   }

   Future<bool> _refreshDartDependencies() async {
+    TimelineTask task =TimelineTask();
+    task.start('Compute dart dependencies');
     if (!hotRunnerConfig.computeDartDependencies) {
       // Disabled.
       return true;
@@ -138,6 +141,7 @@ class HotRunner extends ResidentRunner {
       );
       return false;
     }
+     task.finish();
     return true;
   }

flutter_tools/lib/src/devfs.dart

index 95fc213..a78fbdd 100644
--- a/packages/flutter_tools/lib/src/devfs.dart
+++ b/packages/flutter_tools/lib/src/devfs.dart
@@ -5,6 +5,7 @@
 import 'dart:async';
 import 'package:json_rpc_2/json_rpc_2.dart' as rpc;
 import 'package:meta/meta.dart';
+import 'dart:developer';

 import 'asset.dart';
 import 'base/common.dart';
@@ -471,25 +472,31 @@ class DevFS {
     for (DevFSContent content in _entries.values) {
       content._exists = false;
     }
-
+    final TimelineTask task = TimelineTask();
     // Scan workspace, packages, and assets
     printTrace('DevFS: Starting sync from $rootDirectory');
     logger.printTrace('Scanning project files');
+    task.start('Scan Workspace');
     await _scanDirectory(rootDirectory,
                          recursive: true,
                          fileFilter: fileFilter);
+    task.finish();
+    task.start('Scan packages');
     if (fs.isFileSync(_packagesFilePath)) {
       printTrace('Scanning package files');
       await _scanPackages(fileFilter);
     }
+    task.finish();
+    task.start('Scan assets');
     if (bundle != null) {
       printTrace('Scanning asset files');
       bundle.entries.forEach((String archivePath, DevFSContent content) {
         _scanBundleEntry(archivePath, content);
       });
     }
-
     // Handle deletions.
+    task.finish();
+    task.start('diff Assets');
     printTrace('Scanning for deleted files');
     final String assetBuildDirPrefix = _asUriPath(getAssetBuildDirectory());
     final List<Uri> toRemove = <Uri>[];
@@ -551,6 +558,7 @@ class DevFS {
     // No need to send source files because all compilation is done on the
     // host and result of compilation is single kernel file.

Fixes #28150
Fixes #27446

@zoechi zoechi added tool Affects the "flutter" command-line tool. See also t: labels. t: hot reload Reloading code during "flutter run" labels Feb 20, 2019
@jonahwilliams jonahwilliams changed the title Remove compute dart dependencies Improve hot reload performance Feb 21, 2019
Comment thread packages/flutter_tools/lib/src/devfs.dart Outdated
Comment thread packages/flutter_tools/lib/src/globals.dart Outdated
@Hixie

Hixie commented Feb 21, 2019

Copy link
Copy Markdown
Contributor

cc @aam

@Hixie

Hixie commented Feb 21, 2019

Copy link
Copy Markdown
Contributor

cc @zanderso for review

stayResident: stayResident,
ipv6: ipv6);
ipv6: ipv6) {
watcher = DirectoryWatcher(projectRootPath ?? fs.currentDirectory.path);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I thought we talked about how this will regress dependency analysis: files outside of current directory won't be watched, unused files inside of current directory will be reported to the compiler.
The right solution is to have incremental compiler report files that comprise the application(similar to how list of files produced for .dep-file used for gradle), watch those.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

For most projects the current directory contains the only sources that will be changed. For instance, on a pub based workflow to update sources I need to kill the runner and run pub get too.

How much execution time are we willing to trade to make dependency tracking more accurate? The changes here represent ~0.5 seconds across all hot reloads on windows for example. Would requiring a hot restart to update other directories be a reasonable compromise?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Querying the compiler for the list of sources it used should be instantaneous - it already has this information because it just compiled all of them.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Understood. I think we both agree that the analyzer based approach is flawed - tomorrow lets discuss how we can proceed on incorporating the incremental compiler reports.

}
assert(reassembleViews.isNotEmpty);
printTrace('Evicting dirty assets');
await _evictDirtyAssets();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why is this getting deleted? Does flutter engine no longer need to be told to refresh assets when they are changed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

As far as I can tell we only support updating assets on hot restart. These calls are fired on every hot reload regardless of the changed files and seem to serve no purpose

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I know that you can use hot reload when you (just) update assets. Well, at least it used to work in the past fine.
It's good idea to skip sending this request if no assets were changed though.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Its possible we regressed or removed this feature in the past, or I might be missing something. I will do some archaeology tomorrow to confirm

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I looked into this some more, we support reloading assets but only in the case where the content of the asset itself has changed - we don't support adding/removing/renaming them.

I've added back the evict call, but we can still skip scanning the bundle since additional/removed assets wont show up until a restart anyway

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Supposedly we need to scan assets to figure out which ones were changed, which ones need to be transferred to the device.

Comment thread packages/flutter_tools/lib/src/run_hot.dart
@@ -0,0 +1,105 @@
// Copyright 2019 The Chromium Authors. All rights reserved.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks!

Comment thread packages/flutter_tools/test/project_file_invalidator_test.dart Outdated
Comment thread packages/flutter_tools/lib/src/run_hot.dart

try {
// Will return immediately if pubspec.yaml is up-to-date.
await pubGet(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What will happen now if user changes pubspec.yaml in application they hot reload?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We currently run a pub get when starting via flutter run or attach. It's not possible to actually update dependencies via pub and hot reload, because this changes .packages.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If changing .packages doesn't work that I would imagine is also a bug. Once that is fixed(not sure where the culprit is), without this pub get step, changes to pubspec.yaml have no chance of being picked up though.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Filled #29186

await _scanPackages(fileFilter);
}
if (bundle != null) {
if (bundle != null && fullRestart) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What is the reason for doing this for fullRestart only? Don't we need to scan assets for changes when doing hot reload as well? I think you mentioned there might be a limitation currently that we don't pick up newly added assets on hot reload, but isn't that just a bug? All pieces seems to be in place to have that functionality available.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Filled #29184

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We need to update both the assets and the asset manifest. Right now we have the capability to update the assets but the manifest doesn't update correctly. Its possible this is an oversight or a bug.

Currently the asset _entries remains populated, so hot reload of assets still works.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'm going to add this back for now, because I've found its possible to hot reload assets in the hello_world example. Its not possible in flutter_gallery where I originally tried, and the differences there are number of assets and the usage of package assets.

@jonahwilliams

jonahwilliams commented Mar 12, 2019 •

Copy link
Copy Markdown
Contributor Author

We should still be able to update existing packages though, will update the PR.

EDIT: same issue as adding a new package

@jonahwilliams
jonahwilliams requested review from aam and zanderso March 13, 2019 21:19
return false;
} else {
throw 'Could not parse additional details for dependency $name; line was: "$line"';
// We're an empty target.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

is there a way to confirm that the line describes empty target and keep else { throw for futher unexpected cases?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed this code, instead I should use the regular yaml parser - all I need are the the list of dependencies.

if (content is DevFSFileContent) {
filesUris.add(uri);
invalidatedFiles.add(content.file.uri.toString());
syncedBytes -= content.size;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

How syncedBytes is going to be updated with this code gone?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I added syncedBytes which I accidentally removed from the dill sync. All other entires are assets and are handled above

? fs.path.relative(pathToReload, from: projectRootPath)
: pathToReload,
);
if (!dirtyEntries.containsKey(entryUri)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why was this if removed, I'm curious?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'm not sure how this could ever be false - this entryUri is for the dill file and at this point we have only added assets to the dirtyEntries.

Comment thread packages/flutter_tools/lib/src/resident_runner.dart Outdated
Comment thread packages/flutter_tools/lib/src/run_hot.dart Outdated
@jonahwilliams
jonahwilliams merged commit cd803ac into flutter:master Mar 15, 2019
@github-actions github-actions Bot locked as resolved and limited conversation to collaborators Aug 7, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

t: hot reload Reloading code during "flutter run" tool Affects the "flutter" command-line tool. See also t: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Remove computeDartDependencies from flutter tool Update DevFS to use a Watcher

7 participants