Search code examples
node.jsrustwebassemblywasm-bindgenwasm-pack

My rust code compiled to wasm is slower than js, what did i do wrong?


This is the original function that i wanted to translate in rust to compile it in wasm in the idea that it will make it faster ( since it's a hot function in my server )

export const generateRandomGuid = function (): string {
  let guid: string = "0x";
  let guidString: string = uuidv4();
  const bytes = uuidParse(guidString);
  const arrayBytes = new Uint8Array(bytes);
  for (let index = 0; index < arrayBytes.length; index++) {
    if (guid.length === 18) break;
    const byte = arrayBytes[index].toString(16);
    if (arrayBytes[index].toString(16).length === 1) {
      guid += "0" + byte;
    } else {
      guid += byte;
    }
  }
  return guid;
};

I've translated it this way in rust:

use uuid::Uuid;
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn generate_random_guid() -> String {
    let my_uuid: Uuid = Uuid::new_v4();
    let array_bytes = my_uuid.as_bytes();
    let mut rand_id: String = String::new();
    rand_id.push_str("0x");
    for byte in array_bytes {
        let formatted_byte: String = format!("{:X}", byte);
        if formatted_byte.len() == 1 {
            let mut formatted_byte_with_additionnal_zero: String = "0".to_string();
            formatted_byte_with_additionnal_zero.push_str(&formatted_byte);
            rand_id.push_str(&formatted_byte_with_additionnal_zero);
        } else {
            rand_id.push_str(&formatted_byte);
        }
        if rand_id.len() == 18 {
            break;
        }
    }
    return rand_id;
}

Compiled in wasm using wasm-pack and with this configuration:

[package]
name = "h1emu-core"
version = "0.1.4"
edition = "2018"
[dependencies]
wasm-bindgen = "0.2.45"
uuid = {version = "0.8.2", features = ["v4","wasm-bindgen"], default-features = false }
getrandom = { version = "0.2.3", features = ["js"] }
[lib]
crate-type = ["cdylib"]
[profile.release]
lto = true
opt-level = 3

The result is not the one wanted, it appear the js version is twice faster than the wasm one. So i'm asking myself if it's just my rust code that is bad or my configuration or just that in my case wasm is not suitable.


Solution

  • While the allocations may account for the bulk of the runtime, all the formatting machinery surely doesn't help as well. This is all guaranteed to be ASCII, so we can operate on the raw numbers, and in the end convert to a String.

    I also got rid of the explicit UUID and instead use the rand crate directly, but feel free to do that as you please. Wasm should at least support getrandom.

    pub fn generate_random_guid() -> String {
        let random : [u8; 8] = rand::random();
    
        let mut str_bytes = vec![0u8; 16];
    
        const ASCII_ZERO: u8 = '0' as u8;
        const ASCII_NINE: u8 = '9' as u8;
        const ASCII_NUMBERS_LETTERS_OFFSET: u8 = 'A' as u8 - '9' as u8 - 1;
    
        for i in 0..8 {
            let mut leading = random[i] / 16 + ASCII_ZERO;
            let mut trailing = random[i] % 16 + ASCII_ZERO;
    
            leading += ((leading > ASCII_NINE) as u8) * ASCII_NUMBERS_LETTERS_OFFSET;
            trailing += ((trailing > ASCII_NINE) as u8) * ASCII_NUMBERS_LETTERS_OFFSET;
    
            str_bytes[2 * i] = leading;
            str_bytes[2 * i + 1] = trailing;
        }
    
        unsafe { String::from_utf8_unchecked(str_bytes) }
    }
    

    A quick look into godbolt reveals that this is compiled into near-optimal asm, I expect similar for wasm.