Skip to content

Benchmarks

Noah Baldwin edited this page Apr 29, 2025 · 9 revisions

SVG Optimisation

When running SVGOs defaults against OXVG's defaults -- which are almost identical -- OXVG runs considerably faster.

Just running it once yields the following comparison, bringing a 10x improvement overall and 3.5x improvement for the actual optimization. This shows that the main bottleneck of svgo is the cold start of node/bun, reading files, and writing to stdout. If your use case is focused on optimizing many/large files then oxvg is for you! If you're more focused on running optimisation on in-memory svgs, then you'll also see positive improvements in performance if you're willing to consider the tradeoffs.

provider total time optimisation time
oxvg 16.44ms 12.08ms
svgo (node) 383.36ms 89ms
svgo (bun) 222.16ms 69ms

These results are based on running the programs on blobs.svg.

How it's run
./target/release/oxvg optimise blobs.svg
node bin/svgo.js blobs.svg -o -
bun bin/svgo.js blobs.svg -o -

Path parsing & Optimisation

When parsing a detailed world map consider the following comparisons in some off-the-bat benchmarks. OXVG was benchmarked using divan while SVGO was benchmarked using console.time.

Remember to always take benchmarks with a grain of salt. Performance may vary depending on hardware and platform. That being said, as of 12th October 2024, not much effort has been made into optimising OXVG runtime performance, yet we see an average 3.2x improvement.

OXVG

test fastest slowest median mean samples iters
parse 30 µs 96 µs 30 µs 32 µs 100 100
optimise 136 µs 221 µs 136 µs 141 µs 100 100
whole svg 146 ms 153 ms 147 ms 147 ms 100 100

SVGO

test fastest slowest median mean samples iters
parse 68 µs 892 µs 71 µs 81 µs 100 100
optimise 456 µs 3ms 799 µs 492 µs 537 µs 100 100
whole svg 440 ms 605 ms 470 ms 476 ms 100 100

Source code

OXVG benchmark source
/// Create a file at `crates/oxvg_optimiser/benches/path.rs`
/// Run the command `cargo bench -q -p oxvg_optimiser --profile bench`
use divan::Bencher;

fn main() {
    divan::main();
}

#[divan::bench]
fn parse(bencher: Bencher) {
    bencher.bench(|| oxvg_path::Path::parse(PATH));
}

#[divan::bench]
fn optimise(bencher: Bencher) {
    let path = oxvg_path::Path::parse(PATH).unwrap();
    bencher.bench(|| -> oxvg_path::Path {
        oxvg_path::convert::run(
            &path,
            &oxvg_path::convert::Options::default(),
            &oxvg_path::convert::StyleInfo::empty(),
        )
    });
}

#[divan::bench]
fn svg(bencher: Bencher) {
    use xml5ever::{
        driver::{parse_document, XmlParseOpts},
        tendril::TendrilSink,
    };

    bencher
        .with_inputs(|| {
            let job: oxvg_optimiser::Jobs =
                serde_json::from_str(r#"{ "convertPathData": {} }"#).unwrap();
            let dom: rcdom::RcDom =
                parse_document(rcdom::RcDom::default(), XmlParseOpts::default()).one(LARGE_SVG);
            [(job, dom)]
        })
        .bench_values(|[(job, dom)]| job.run(&dom));
}

/// Using the United Arab Emirates path from https://www.amcharts.com/lib/3/maps/svg/worldHigh.svg
const PATH = &str = "";

/// Using the world map svg from https://www.amcharts.com/lib/3/maps/svg/worldHigh.svg
const LARGE_SVG = &str = "";
SVGO benchmark source
/**
 * create a file at /benchmark.js
 * run `node ./benchmark.js`
 * In my case I'm using nushell, an easy way to process the results is as follows
 * ```nu
 * let result = (node ./benchmark.js | lines | parse "{key}: {value}" | update value {|| $in | into duration })
 * # sort results to get fastest/slowest
 * $result | where key == "parse" | sort-by value
 * # get statistical info
 * $result | where key == "parse" | math median
 * $result | where key == "parse" | math avg
 * ```
 *
 * bunjs also produces results around 2x faster than Node
 * ```nu
 * let result = (bun ./benchmark.js err>| lines | parse "[{value}] {key}" | update value {|| $in | into duration })
 * ```
 */
import { optimize } from './lib/svgo-node.js';
import { parsePathData } from './lib/path.js';
import { fn } from './plugins/convertPathData.js';

function main() {
  for (let i = 0; i < 100; i += 1) {
    parse();
    optimise();
    svg();
  }
}

function parse() {
  console.time('parse');
  parsePathData(PATH);
  console.timeEnd('parse');
}

function optimise() {
  const {
    element: { enter },
  } = fn(
    { type: 'root', children: [] },
    { applyTransforms: false, applyTransformsStroked: false },
  );
  const node = {
    type: 'element',
    name: 'path',
    attributes: { d: PATH },
    children: [],
  };
  console.time('optimise');
  enter(node);
  console.timeEnd('optimise');
}

function svg() {
  const config = {
    plugins: [
      {
        name: 'convertPathData',
        params: {
          applyTransforms: false,
          applyTransformsStroked: false,
        },
      },
    ],
  };
  console.time('svg');
  optimize(LARGE_SVG, config);
  console.timeEnd('svg');
}

/// Using the United Arab Emirates path from https://www.amcharts.com/lib/3/maps/svg/worldHigh.svg
const PATH = "";

/// Using the world map svg from https://www.amcharts.com/lib/3/maps/svg/worldHigh.svg
const LARGE_SVG = ``;
Hardware
                  -`                     noah@archlinux
                 .o+`                    --------------
                `ooo/                    OS: Arch Linux x86_64
               `+oooo:                   Host: G5 KC
              `+oooooo:                  Kernel: Linux 6.10.10-arch1-1
              -+oooooo+:                 Uptime: 46 seconds
            `/:-:++oooo+:                Packages: 1219 (pacman)
           `/++++/+++++++:               Shell: nushell 0.98.0
          `/++++++++++++++:              Display (BOE08B3): 1920x1080 @ 144 Hz in 16″
         `/+++ooooooooooooo/`            DE: GNOME 47.0
        ./ooosssso++osssssso+`           WM: Mutter (X11)
       .oossssso-````/ossssss+`          WM Theme: Adwaita
      -osssssso.      :ssssssso.         Theme: Adwaita [GTK2/3/4]
     :osssssss/        osssso+++.        Icons: Adwaita [GTK2/3/4]
    /ossssssss/        +ssssooo/-        Font: Atkinson Hyperlegible (13pt) [GTK2/3/4]
  `/ossssso+/:-        -:/+osssso+-      Cursor: Adwaita (24px)
 `+sso+:-`                 `.-/+oso:     Terminal: zellij 0.40.1
`++:.                           `-/+/    CPU: Intel(R) Core(TM) i5-10500H (12) @ 4.50 GHz
.`                                 `/    GPU 1: NVIDIA GeForce RTX 3060 Mobile / Max-Q
                                         GPU 2: Intel UHD Graphics @ 1.05 GHz [Integrated]
                                         Memory: 4.34 GiB / 15.32 GiB (28%)
                                         Swap: 0 B / 4.00 GiB (0%)
                                         Disk (/): 328.10 GiB / 475.94 GiB (69%) - btrfs
                                         Local IP (wlan0): 192.168.1.62/24
                                         Battery (BAT): 95% [Discharging]
                                         Locale: en_US.UTF-8

> node --version
v20.15.0

> cargo --version
cargo 1.79.0 (ffa9cf99a 2024-06-03)
Clone this wiki locally