Electron Adventures: Episode 65: Improving Performance
Tags: javascript, svelte, electron
Published: 2021-09-28 12:35:10

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:

```html
<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>
    &amp;
    <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:

```html
<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:

```html
<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?

```html
<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.

```html
<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:

![Episode 65 Screenshot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/452mbftxfcppkvdg1e1o.png)

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](https://github.com/taw/electron-adventures/tree/master/episode-65-improving-performance).