diff --git a/src/xsprintf/PhutilQueryString.php b/src/xsprintf/PhutilQueryString.php
--- a/src/xsprintf/PhutilQueryString.php
+++ b/src/xsprintf/PhutilQueryString.php
@@ -4,14 +4,15 @@
 
   private $escaper;
   private $argv;
+  private $unmaskedString;
 
   public function __construct(PhutilQsprintfInterface $escaper, array $argv) {
     $this->escaper = $escaper;
     $this->argv = $argv;
 
     // This makes sure we throw immediately if there are errors in the
-    // parameters.
-    $this->getMaskedString();
+    // parameters. We build the unmasked string to populate the cache.
+    $this->getUnmaskedString();
   }
 
   public function __toString() {
@@ -19,7 +20,10 @@
   }
 
   public function getUnmaskedString() {
-    return $this->renderString(true);
+    if ($this->unmaskedString === null) {
+      $this->unmaskedString = $this->renderString(true);
+    }
+    return $this->unmaskedString;
   }
 
   public function getMaskedString() {
@@ -27,6 +31,14 @@
   }
 
   private function renderString($unmasked) {
+    // On a normal page, about 25% of the query fragments we render are just
+    // empty strings representing clauses we aren't using. If the pattern has
+    // no string length and there are no other arguments, just return the
+    // empty string as an optimization.
+    if (!isset($this->argv[0][0]) && !isset($this->argv[1])) {
+      return '';
+    }
+
     return xsprintf(
       'xsprintf_query',
       array(