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)