We've been struggling a bit with editor performance, so let's see what we can do to make it better.
First, the hex editor uses a huge table to present all data. Here's a sample row, with some spacing reformatted, and skipping event handlers:
<tr class="svelte-19yny8o"> <td class="offset">000160</td> <td class="hex"> <span>80</span> <span>e5</span> <span>c3</span> <span>6a</span> </td> <td class="hex"> <span>22</span> <span>93</span> <span>0c</span> <span>00</span> </td> <td class="hex"> <span>07</span> <span>c4</span> <span>26</span> <span>8c</span> </td> <td class="hex"> <span>be</span> <span>04</span> <span>00</span> <span>00</span> </td> <td class="ascii"> <span class="unprintable svelte-kmsjw3">.</span> <span class="unprintable svelte-kmsjw3">.</span> <span class="unprintable svelte-kmsjw3">.</span> j " <span class="unprintable svelte-kmsjw3">.</span> <span class="unprintable svelte-kmsjw3">.</span> <span class="unprintable svelte-kmsjw3">.</span> <span class="unprintable svelte-kmsjw3">.</span> <span class="unprintable svelte-kmsjw3">.</span> & <span class="unprintable svelte-kmsjw3">.</span> <span class="unprintable svelte-kmsjw3">.</span> <span class="unprintable svelte-kmsjw3">.</span> <span class="unprintable svelte-kmsjw3">.</span> <span class="unprintable svelte-kmsjw3">.</span> </td> </tr>
But it's really just one line of constant-width text.
It won't necessarily improve performance to simplify this, but it might, and it will also give us better control over layout.
src/AsciiSlice.svelte
As we tested in previous episode, about 1/3 of time was spent on the ASCII preview loop. We could simplify this, and remove any special treatment for unprintable characters - just replace them one by one by something that won't normally happen, like middle dot:
<script> export let data let ascii = "" for (let d of data) { if (d >= 32 && d <= 126) { ascii += String.fromCharCode(d) } else { ascii += "\xB7" } } </script> <span class="ascii">{ascii}</span>
This saves a lot of performance.
src/Slice.svelte
Next we could get rid of special hex group handling, and <table>
s, and just make CSS handle spacing:
<script> import { printf } from "fast-printf" import AsciiSlice from "./AsciiSlice.svelte" import { createEventDispatcher } from "svelte" let dispatch = createEventDispatcher() export let offset export let data </script> <div class="row"> <span class="offset">{printf("%06d", offset)}</span> <span class="hex"> {#each {length: 16} as _, i} <span on:mouseover={() => dispatch("changeoffset", offset+i)}> {data[i] !== undefined ? printf("%02x", data[i]) : " "} </span> {/each} </span> <AsciiSlice {data} /> </div> <style> .row:nth-child(even) { background-color: #555; } .offset { margin-right: 0.75em; } .hex span:nth-child(4n) { margin-right: 0.75em; } </style>
Changes so far reduce 256kB render from ~7.5s to ~5s.
Remove event handlers
That's still not amazing, so what's the next thing we can do? How about we get rid of event handlers for each byte?
<script> import { printf } from "fast-printf" import AsciiSlice from "./AsciiSlice.svelte" export let offset export let data </script> <div class="row"> <span class="offset">{printf("%06d", offset)}</span> <span class="hex"> {#each {length: 16} as _, i} <span data-offset={offset + i}> {data[i] !== undefined ? printf("%02x", data[i]) : " "} </span> {/each} </span> <AsciiSlice {data} /> </div> <style> .row:nth-child(even) { background-color: #555; } .offset { margin-right: 0.75em; } .hex span:nth-child(4n) { margin-right: 0.75em; } </style>
Well, that's fine, but we still want that event to be handled. No problem at all, let's just set data-offset
on each element and let the parent figure it out!
src/MainView.svelte
Usually event.target
is just the element that got event handler. But it doesn't have to be. event.target
could be a descendant which triggered the event.
This is great, as we can have a single handler on .main
that handles thousands of .hex span
.
As we could get an event even if we're actually mousing over something else (like ASCII preview, or offset, or empty space inside .main
), we need to check that we're over a relevant event with e.target.dataset.offset
check.
<script> import Slice from "./Slice.svelte" import { createEventDispatcher } from "svelte" export let data let dispatch = createEventDispatcher() let slices $: { slices = [] for (let i=0; i<data.length; i+=16) { slices.push({ offset: i, data: data.slice(i, i+16), }) } } function onmouseover(e) { if (!e.target.dataset.offset) { return } dispatch("changeoffset", e.target.dataset.offset) } </script> <div class="main" on:mouseover={onmouseover}> {#each slices as slice} <Slice {...slice} /> {/each} </div> <style> .main { flex: 1 1 auto; overflow-y: auto; width: 100%; } </style>
And that reduces 256kB load time further, from 5s to 4.5s. That's ~40% faster, but it's still far from what we want. If you're not happy about performance of your software, it's always a good idea to try some quick wins. Sometimes you win a lot, sometimes you win a little, but either way it didn't require too many changes.
Results
Here's the results:
In the next episode, we'll use try to push the performance a lot further.
As usual, all the code for the episode is here.
Top comments (0)