DEV Community

Tomasz Wegrzanowski
Tomasz Wegrzanowski

Posted on

Electron Adventures: Episode 65: Improving Performance

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>
    &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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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

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.

Discussion (0)