Skip to content

ulid() slow vs monotonic #126

Open
Open

Description

@atlanteh

Does it make sense that ulid() is so slow relative to monothonic?
I ran this simple benchmark multiple times to test this library vs uuidv7, and it always returns around the same results:

import { ulid, monotonicFactory } from 'ulid';

function bench(name, fn, N = 5_000_000) {
  // warmup
  for (let i = 0; i < 200_000; i++) fn();
  const t0 = performance.now();
  for (let i = 0; i < N; i++) fn();
  const t1 = performance.now();
  console.log(`${name}: ${(N / ((t1 - t0) / 1000)).toFixed(1)} ids/sec`);
}

bench('uuidv7', uuidv7);
bench('ulid', ulid);
bench('ulid(monotonic)', monotonicFactory());

The results

uuidv7: 5486105.0 ids/sec
ulid: 31140.8 ids/sec
ulid(monotonic): 2054127.8 ids/sec

why is ulid() much slower than monotonic? and why is monotonic slower than uuidv7?

Activity

  1. cmcnicholas commented on Sep 12, 2025

    @cmcnicholas

    Does it make sense that ulid() is so slow relative to monothonic? I ran this simple benchmark multiple times to test this library vs uuidv7, and it always returns around the same results:

    import { ulid, monotonicFactory } from 'ulid';
    
    function bench(name, fn, N = 5_000_000) {
      // warmup
      for (let i = 0; i < 200_000; i++) fn();
      const t0 = performance.now();
      for (let i = 0; i < N; i++) fn();
      const t1 = performance.now();
      console.log(`${name}: ${(N / ((t1 - t0) / 1000)).toFixed(1)} ids/sec`);
    }
    
    bench('uuidv7', uuidv7);
    bench('ulid', ulid);
    bench('ulid(monotonic)', monotonicFactory());
    

    The results

    uuidv7: 5486105.0 ids/sec
    ulid: 31140.8 ids/sec
    ulid(monotonic): 2054127.8 ids/sec
    

    why is ulid() much slower than monotonic? and why is monotonic slower than uuidv7?

    Looks like some work is done on every call to ulid in detectPRNG() and seed creation (tiny but it is repeated).

    A way to improve that without needing the factory is if they allowed us to call detectPRNG() so we could store it once. e.g.

    import { ulid, detectPRNG } from 'ulid';
    
    const prng = detectPRNG();
    
    export function id() {
      return ulid(Date.now(), prng);
    }

    this avoids the repeated testing of detectPRNG.

    I think there might be room for improvement though, I can see allocating the Uint32Array each time a call is made is also pretty expensive: https://github.com/ulid/javascript/blob/master/source/ulid.ts#L51

    Suggestion would be they move the array initialisation to the outer part of the function and reuse it.

            const buffer = new Uint8Array(1);
            return () => {
                globalCrypto.getRandomValues(buffer);
                return buffer[0] / 0xff;
            };

    If you know your environment its running in you can just add this today e.g.

    import { ulid } from 'ulid';
    
    const buffer = new Uint8Array(1);
    function prng() {
      crypton.getRandomValues(buffer);
      return buffer[0] / 0xff;
    }
    
    export function id() {
      return ulid(undefined, prng);
    }

    Anyway, just some thoughts.

  2. cmcnicholas commented on Sep 13, 2025

    @cmcnicholas

    Does it make sense that ulid() is so slow relative to monothonic? I ran this simple benchmark multiple times to test this library vs uuidv7, and it always returns around the same results:

    import { ulid, monotonicFactory } from 'ulid';
    
    function bench(name, fn, N = 5_000_000) {
      // warmup
      for (let i = 0; i < 200_000; i++) fn();
      const t0 = performance.now();
      for (let i = 0; i < N; i++) fn();
      const t1 = performance.now();
      console.log(`${name}: ${(N / ((t1 - t0) / 1000)).toFixed(1)} ids/sec`);
    }
    
    bench('uuidv7', uuidv7);
    bench('ulid', ulid);
    bench('ulid(monotonic)', monotonicFactory());
    

    The results

    uuidv7: 5486105.0 ids/sec
    ulid: 31140.8 ids/sec
    ulid(monotonic): 2054127.8 ids/sec
    

    why is ulid() much slower than monotonic? and why is monotonic slower than uuidv7?

    just opened a PR after I went on a bit of a dive through the code #127 makes ulid work about 45% faster.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions