Cache the current working directory.

Recomputing this isn't terribly expensive, but it happens a lot so it's
nice to avoid.

Also avoid calling it in relative() when the path is already relative.

This brings current from about 0.070 iterations/us to about 0.097, and relative from about 0.080 to about 1.589.

R=rnystrom@google.com

Review URL: https://codereview.chromium.org//1473563003 .
diff --git a/benchmark/benchmark.dart b/benchmark/benchmark.dart
index 03871d0..8dc2690 100644
--- a/benchmark/benchmark.dart
+++ b/benchmark/benchmark.dart
@@ -47,6 +47,10 @@
     benchmark('toUri', context.toUri);
     benchmark('prettyUri', context.prettyUri);
   }
+
+  if (args.isEmpty || args.any((arg) => arg == 'current')) {
+    runBenchmark('current', (_) => path.current, [null]);
+  }
 }
 
 const COMMON_PATHS = const ['.', '..', 'out/ReleaseIA32/packages'];
diff --git a/lib/path.dart b/lib/path.dart
index af9efe5..93fe67e 100644
--- a/lib/path.dart
+++ b/lib/path.dart
@@ -79,17 +79,36 @@
 /// In the browser, this means the current URL, without the last file segment.
 String get current {
   var uri = Uri.base;
+
+  // Converting the base URI to a file path is pretty slow, and the base URI
+  // rarely changes in practice, so we cache the result here.
+  if (uri == _currentUriBase) return _current;
+  _currentUriBase = uri;
+
   if (Style.platform == Style.url) {
-    return uri.resolve('.').toString();
+    _current = uri.resolve('.').toString();
+    return _current;
   } else {
     var path = uri.toFilePath();
     // Remove trailing '/' or '\'.
-    int lastIndex = path.length - 1;
+    var lastIndex = path.length - 1;
     assert(path[lastIndex] == '/' || path[lastIndex] == '\\');
-    return path.substring(0, lastIndex);
+    _current = path.substring(0, lastIndex);
+    return _current;
   }
 }
 
+/// The last value returned by [Uri.base].
+///
+/// This is used to cache the current working directory.
+Uri _currentUriBase;
+
+/// The last known value of the current working directory.
+///
+/// This is cached because [current] is called frequently but rarely actually
+/// changes.
+String _current;
+
 /// Gets the path separator for the current platform. This is `\` on Windows
 /// and `/` on other platforms (including the browser).
 String get separator => context.separator;
diff --git a/lib/src/context.dart b/lib/src/context.dart
index 44bdde9..a05ffda 100644
--- a/lib/src/context.dart
+++ b/lib/src/context.dart
@@ -411,6 +411,9 @@
   /// "/", no path can be determined. In this case, a [PathException] will be
   /// thrown.
   String relative(String path, {String from}) {
+    // Avoid expensive computation if the path is already relative.
+    if (from == null && this.isRelative(path)) return path;
+
     // Avoid calling [current] since it is slow and calling join() when
     // [from] is absolute does nothing.
     if (from == null) {