Skip to content

Instantly share code, notes, and snippets.

@BruJu
Last active June 4, 2020 06:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save BruJu/351bbb74216cee6d34aae7938586dca7 to your computer and use it in GitHub Desktop.
Save BruJu/351bbb74216cee6d34aae7938586dca7 to your computer and use it in GitHub Desktop.
Rust Wasm Various Notes

Some related documentation

This is a WIP list of my notes on my research about "why my rust program is slow in wasm ?"

https://rustwasm.github.io/book/game-of-life/implementing.html#interfacing-rust-and-javascript

TODO :

Web Assembly

https://hacks.mozilla.org/2017/02/what-makes-webassembly-fast/

https://hacks.mozilla.org/2017/02/creating-and-working-with-webassembly-modules/

How Wasm works. Interesting but not that relevant

"It’s likely that anybody who’s developing a WebAssembly module to be used by web developers is going to create a wrapper around that module. That way, you as a consumer of the module don’t need to know about memory management."

https://medium.com/@OPTASY.com/webassembly-vs-javascript-is-wasm-faster-than-js-when-does-javascript-perform-better-db86d2ecf2cc

  • wasm file are smaller because it is not plain text : they are downloaded faster, decoded faster and executed faster
  • JS needs to parse, compile and optimize.
  • When JS is optimized, it is faster.

This is conter intuitive because it implies that we have to write in wasm the glue code, not the main parts of the code because the main parts will be JITed anyway.

Lost source

  • The WASM Machine is a stack based virtual machine.

http://link.crwd.fr/45ZL#https://blog.acolyer.org/2017/09/18/bringing-the-web-up-to-speed-with-webassembly/?utm_campaign=crowdfire&utm_content=crowdfire&utm_medium=social&utm_source=twitter

  • WebAssembly has been designed to solve the problem asm.js addresses.
  • Easy validation of bytecode (Wasm : 1 page, Java : 150 pages)
  • (Maybe from another source) : Bytecode can be loaded and used while it is downloaded
  • Each module is executed in its own linear memory (an untrusted module can't do anything bad)
  • "WebAssembly is on average 33.7% faster than asm.js, with validation taking only 3% of the time it does for asm.j.s"

https://pspdfkit.com/blog/2017/webassembly-a-new-hope/

  • asm.js is basically Javascript wrote in a way that the interpreter can optimize aggressively while still respecting the standard
  • This article also presents the key concept of wasm
  • Benchmarks on part with asm.js (up to x2 compared to native code) but article written in 2017

Wasm is faster

https://medium.com/samsung-internet-dev/performance-testing-web-assembly-vs-javascript-e07506fd5875

Matrices multiplication with blocking

  • WebAssembly is faster except on small matrices
  • C is faster on the non blocked implementation (better optimization than the blocks)
  • JS is faster with blocking

JIT might not be that powerful

Web Assembly is slow (most of the time performances equals to JS)

https://blog.soshace.com/introduction-to-webassembly-the-magic-of-native-code-in-web-apps/

  • "WebAssembly also offers predictable performance — the ability to reduce the difference in browser performance (in some cases from 400% to less than 50%)."

  • It’s tempting to compare these technologies only by performance numbers, so here’s another curious finding: JavaScript and WebAssembly have equal peak performance. Still, preferring one technology over the other simply because of performance gains (if any) is short-sighted — there are a plethora of other factors that come into play.

But :

  • We should also note that the performance equality of JavaScript and WebAssembly will probably disappear in the future: WebAssembly will soon receive support for threads and simd (single instruction, multiple data which will allow it to outperform JavaScript.

https://developers.google.com/web/updates/2019/02/hotpath-with-wasm

Benchmark the rotation of an image in wasm C, wasm Rust, wasm TypeScript and vanilla Javascript.

Time performances are roughly the same

https://www.usenix.org/conference/atc19/presentation/jangda

https://www.usenix.org/system/files/atc19-jangda.pdf

Mimics a C / C++ kernel in Wasm.

  • 0.8 to 3.5 slowdown (avg ~1.5) comparing to native performances

https://stackoverflow.com/questions/48173979/why-is-webassembly-function-almost-300-time-slower-than-same-js-function

  • The 300x speedup sometimes announced is wrong because "micro benchmarks" are not representative
  • WebAssembly VM are new are just MVP (Minimal Viable Product), JS is 20 years old

Wasm Bindgen is slow

https://engineering.widen.com/blog/A-Tale-of-Performance---Javascript,-Rust,-and-WebAssembly/

Implements a date compare function in wasm. Slower because text decoding is slow.

Just In Time Compilation : wasm is not slow, Javascript is just too fast

modern JavaScript engines like SpiderMonkey or Rhino follow the same approach, but what makes V8 stand out is that it does not produce any intermediate code. Someone, someday

With JavaScript the performance generally increases with each iteration as it is further optimised. It can also decrease due to se-optimisation.

Related issues in the repo of wasm_bingen / wasmpack

~~ rustwasm/wasm-pack#558 ~~

rustwasm/book#154

What should be tried

Benchmarks

https://youtu.be/uMuYaES4W3o - Speed, Speed, Speed: JavaScript vs C++ vs WebAssembly (Franziska Hinkelmann at CovalenceConf 2019)

  • NodeJs has an option to disable optimizations (--no optimization)

https://youtu.be/uMuYaES4W3o?t=1220

https://stackoverflow.com/questions/46331830/why-is-my-webassembly-function-slower-than-the-javascript-equivalent

  • Heavy maths : expect 10% speedup
  • UInt8Buffer copy may be slow, especially on Chrome / Node
  • Use benchmark.js

rustwasm/wasm-bindgen#1119

  • "FWIW the profilers showed that very little time was spent in wasm itself, so at least that part is fast here!"

  • How to profile JS / JS Glue Code / Wasm ?

Wasmbindgen traps

  • Objects that are exported have a free function that should be manually called to let the glue code know that this object will not be used anymore

  • We don't actually own JsValue. JsValue is worst than &JsValue because JsValue's lifetime is more than this function call. In other words, an imported function that receives a JsValue doesn't really own the JsValue in the sense that if it loses the object, the object isn't destroyed.

  • Every quad we create and exports must be explicitely freed which is not rdf.js.org friendly. This is reflected in the functiosn that returns strings that wasmbindgen generate in which wasm.__wbindgen_free(r0, r1); is always called at the end

Options

[profile.release]
codegen-units = 1
lto = true
opt-level = 'z'
panic = 'abort'

https://doc.rust-lang.org/rustc/codegen-options/index.html

List of compile option of rustc. lto = fat may be interesting (but not that much because of thin lto.

Wasm-bindgen examples

Actual projects may help to find ideas about why our code is slow

ZinGo

API Conformance

  • I think I didn't try this and that's stupid

Useless links in our context

Further works

Web Assembly Interface types

wasm_bindgen reads the environement variable WASM_INTERFACE_TYPES (set to 1) to generates Wasm Interfaces Types. ( rustwasm/wasm-bindgen#1725 )

But Interfaces Types are not yet merged into the Web Assembly standard



Things that didn't work

Chainable wasm function

Documentation

Found snippet :

    pub fn cells(&self) -> *const Cell {
        self.cells.as_ptr()
    }

I needed to try

  pub fn get_self(&self) -> *const Self {
      self.as_ptr()
    }

rustwasm/wasm-bindgen#1581

What I tried

        #[wasm_bindgen(js_class=$js_name)]
        impl $rust_export_name {
            /// Deletes the passed quad from this dataset
            #[wasm_bindgen(js_name="delete")]
            pub fn delete(&mut self, quad: JsImportQuad) -> Self {
                let sophia_quad = SophiaExportDataFactory::from_quad(quad);
                self.dataset.remove(
                    &sophia_quad._subject,
                    &sophia_quad._predicate,
                    &sophia_quad._object,
                    match &sophia_quad._graph {
                        None => None,
                        Some(x) => Some(x)
                    }
                ).unwrap();

                self
            }
        }
error: cannot return a borrowed ref with #[wasm_bindgen]

error: aborting due to previous error

error: could not compile `sophia-wasm`.

Former TODO that will not be executed because they are not relevant anymore :

  • Explore the string problems

    • Are UTF8 and UTF16 that different ?
    • Can we disable text encoder (probably not)
    • Can we build a Term type that relies on JsStrings ? (and are they efficient in the wasm bindgen context ?)
  • The macro is horrible, we could build :

    • A base struct WasmWrapper that uses a SophiaDataset and builds the RDF JS functions
    • A trait WasmWrapperRewritten that uses a base WasmWrapper and recalls its function
    • We could implement WasmWrapperRewritten to reimplement some of the methods
    • A macro that either takes a WasmWrapper or a WasmWrapperRewritten and binds it with wasm bindgen

The purpose is to make it easier to rewrite some functions that our dataset have a better method to implement than the naive approches currently in the huge macro

Why strings are an issue when exporint Sophia DS

from my whiteboard

Sophia Dataset's API heavily rely on strings (Terms are expected to be hashable and to be dereferencable into strings)

Javascript uses UTF16, Rust uses UTF8

V8 text encoder / decoder are slow according to the different articles I found

TO bypass this, we could :

  • Require &JsString as parameter instead of strings and either do the conversion ourself or never do it. The problem is we are still expected to be able to hash and deref into Rust strings
  • Return JsString which force the user to free the return strings or wrap every function that returns a JsString to convert it into a String.
  • Returning UInt8Array (Box<[u8]>) can be an alternativ to let wasm bindgen generate (some of) the appropriate glue code

All these process involve a lot of copy (which is expected) for strings Js already owned by default

We could try to pass utf 16 strings and hope they are encoded with the same bytes in utf 8 (but i don't know yet if it is feasible, it should not as the literals should be able to contain every possible character but I have to check the specification)

  • An option exists in wasm bindgen to "intern" the strings. I have not yet explored this possibility

  • Rust str has a "encode_utf16" function that could be used instead of JS text decoder

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