From 6daf343e8b1b99cb776e244f9f8e658e1887e633 Mon Sep 17 00:00:00 2001
From: René Kijewski <rene.kijewski@fu-berlin.de>
Date: Thu, 30 Mar 2023 06:28:26 +0200
Subject: Escape HTML faster
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Escaped HTML characters vary in length. So, in order to select the
correct replacement two variables need to be loaded: The pointer to the
new substring and its length. Because of this the generated code is less
dense than it could be.

With this PR instead of selecting the appropriate `&str`, an `&&str` is
selected. The former consumes two words while the latter consumes only
one. Intuitively one might assume that the double dereference makes the
code slower, but the optimized lookup seems to be so much faster, so
that the change is worth its weight.

Comparing the result of `cargo bench` (best out of three runs for both):

```text
Old:  [4.3592 µs 4.3675 µs 4.3764 µs]
New:  [3.8691 µs 3.8766 µs 3.8860 µs]
Diff: [-11.24 %  -11.24 %  -12.21 % ]
```
---
 askama_escape/src/lib.rs | 52 +++++++++++++++++++++---------------------------
 1 file changed, 23 insertions(+), 29 deletions(-)

(limited to 'askama_escape/src')

diff --git a/askama_escape/src/lib.rs b/askama_escape/src/lib.rs
index c388f2d..ef6c1f9 100644
--- a/askama_escape/src/lib.rs
+++ b/askama_escape/src/lib.rs
@@ -113,21 +113,18 @@ impl Escaper for Html {
     {
         let mut last = 0;
         for (index, byte) in string.bytes().enumerate() {
-            macro_rules! go {
-                ($expr:expr) => {{
-                    fmt.write_str(&string[last..index])?;
-                    fmt.write_str($expr)?;
-                    last = index + 1;
-                }};
-            }
-
-            match byte {
-                b'<' => go!("&lt;"),
-                b'>' => go!("&gt;"),
-                b'&' => go!("&amp;"),
-                b'"' => go!("&quot;"),
-                b'\'' => go!("&#x27;"),
-                _ => {}
+            let escaped = match byte {
+                b'<' => Some(&"&lt;"),
+                b'>' => Some(&"&gt;"),
+                b'&' => Some(&"&amp;"),
+                b'"' => Some(&"&quot;"),
+                b'\'' => Some(&"&#x27;"),
+                _ => None,
+            };
+            if let Some(escaped) = escaped {
+                fmt.write_str(&string[last..index])?;
+                fmt.write_str(escaped)?;
+                last = index + 1;
             }
         }
         fmt.write_str(&string[last..])
@@ -181,20 +178,17 @@ impl std::io::Write for JsonEscapeBuffer {
     fn write(&mut self, bytes: &[u8]) -> std::io::Result<usize> {
         let mut last = 0;
         for (index, byte) in bytes.iter().enumerate() {
-            macro_rules! go {
-                ($expr:expr) => {{
-                    self.0.extend(&bytes[last..index]);
-                    self.0.extend($expr);
-                    last = index + 1;
-                }};
-            }
-
-            match byte {
-                b'&' => go!(br#"\u0026"#),
-                b'\'' => go!(br#"\u0027"#),
-                b'<' => go!(br#"\u003c"#),
-                b'>' => go!(br#"\u003e"#),
-                _ => {}
+            let escaped = match byte {
+                b'&' => Some(br#"\u0026"#),
+                b'\'' => Some(br#"\u0027"#),
+                b'<' => Some(br#"\u003c"#),
+                b'>' => Some(br#"\u003e"#),
+                _ => None,
+            };
+            if let Some(escaped) = escaped {
+                self.0.extend(&bytes[last..index]);
+                self.0.extend(escaped);
+                last = index + 1;
             }
         }
         self.0.extend(&bytes[last..]);
-- 
cgit