Rustで書いたWebAssemblyに文字列を渡すときにUTF16, UTF8, StringBuilderのどれが速いか計測してみた（ついでにNative RustとWebAssemblyの速度差も調べた)

RustはUTF-8を内部で使っていて、JavaScriptはUTF-16なので変換が必要になります。さらにWebAssemblyでは可変長のデータのやり取りが厄介です。実際にデータをやり取りするコードを書いて計測し、どれがいいのか調べてみました。

環境 Windows 10 64bit Corei5-3550 3.3GHz メモリ4G
Chrome 67.0.3396.99 FireFox 61.01

#先に結論

StringBuilderは速く、UTF-16もまあまあ速いです。UTF-8はそれらと比べて一桁遅いです。この計測でネイティブとWebAssemblyの速度差に言及するのは難しいですが、このケースでは2倍ぐらいネイティブが速いかなという感じです。

#教訓

JavaScriptからWebAssemblyの関数を呼び出すコストは無視してよさそう。JavaScriptでGC対象のメモリを確保すると遅いのでWebAssembly側で確保したい。StringBuilderを使う方法は安全性が高く、パフォーマンスも良い。

#注意

　実行速度が安定しません。JavaScript側ではGCもあるし、WebAssemblyでメモリが足りなくなった場合、メモリ領域全体をより大きな領域に全部コピーして、アドレスは同じものを指しつつ空き領域も確保するという荒業を使うようなので、その時には遅くなります。さらにChromeの場合は、全体を何度か繰り返すと早くなるという謎の挙動が起きることもありました(なのでテストコードでは全体を10回繰り返しています)。

#UTF-8

　コードと結果は後でのせますが、かいつまむとこんな感じです。まずRust側でmallocとfreeに相当するものを作り、JavaScript側にエクスポートします。(http://mmi.hatenablog.com/entry/2017/08/06/230823 を参考にしました)

```Rust
#[no_mangle]
pub extern "C" fn alloc(len : usize) -> *mut u8{
    let mut vec = Vec::<u8>::with_capacity(len);
    unsafe { vec.set_len(len); }
    Box::into_raw(vec.into_boxed_slice()) as *mut u8
}

#[no_mangle]
pub extern "C" fn free(raw: *mut u8, len : usize) {
    unsafe {
        let s = ::std::slice::from_raw_parts_mut(raw, len);
        Box::from_raw(s);
    }
}
```

　JavaScriptではメモリを確保し、UTF-8にエンコードした文字列をゴリゴリ書き込んでいきます。(letとvarが無作為に並んでいますが気にしないでください)

```JavaScript
let utf8 = new TextEncoder().encode(s);
let len = utf8.byteLength;
var allocated = ex.alloc(len);
var mem = ex.memory.buffer; //メモリバッファはalloc時に無効化されてしまう可能性があるので、alloc後取得
var array = new Uint8Array(mem, allocated, len);

for (var i = 0; i < len; ++i) {
    array[i] = utf8[i];
}
let r = ex.test_with_utf8_unchecked(allocated, len);
```

　for文でデータを書き込んでいますが、memcpyに相当するUint8Array.setメソッドもあります。使ってみましたがほぼ速度差はありませんでした。Rust側でStringを作るときに、有効なUTF8であるかチェックするものとチェックしないものどちらも使ってみましたが、ほぼ速度差はありませんでした。

　文字列を渡して全ての文字の文字コードを足し合わせる処理を10000回やりましたが、ABCDEのような簡単な文字列で30ms、長い文字列で60msほどかかりました。TextEncoder.encodeでArrayBufferが毎回作られるので、GCのために遅くなっているのだと思います。ArrayBufferを新規につくらずにUTF8にエンコードする方法はみつかりませんでした。

#UTF16

```Rust
let len = s.length;
var allocated = ex.alloc(len * 2);
var mem = ex.memory.buffer;
let array = new Uint16Array(mem, allocated, len);

for (var i = 0; i < len; i++) {
    array[i] = s.charCodeAt(i);
}
let r = ex.test_with_utf16_lossy(allocated, len)
```

 普通にallocでメモリを確保して、for文でコピーしてWebAssemblyに渡しているだけです。Rust側でUTF16からUTF8に変換します。これは速いです。余計なメモリ確保がないですし、2バイトずつコピーできます。短い文字列で5ms、長い文字列で30msといった感じです。

#StringBuilder

　StringBuilderの場合は、「JavaScriptからmalloc相当の関数を呼び出す」といったことはせず、Rust側に用意された文字列作成関数を呼び出して文字列を作ります。

```Rust
pub struct StringBuilder{
    s : String
}

#[no_mangle]
pub extern "C" fn create_string_builder() -> *mut StringBuilder{
    let b = Box::new(StringBuilder{ s : "".to_string() });
    Box::into_raw(b)
}

#[no_mangle]
pub extern "C" fn destroy_string_builder(b : *mut StringBuilder){
    unsafe{ Box::from_raw(b); }
}
```

```JavaScript
//JavaScript側
var b = ex.create_string_builder();
for (let c of s) {
    ex.string_builder_append_unchecked(b, c.codePointAt(0));
}
```

　UnicodeのCodePointを関数に与えていくことで、Rust側に文字列を作ります。

　正当なunicodeかチェックする関数を使うバージョンと使わないバージョンを作ってみましたが速度はほぼ変わりませんでした。JavaScriptでStringのCodePointを列挙するにはArray.Fromを使う方法もあります。

```JavaScript
let array = Array.from(s);
for (let c of array) {
    ex.string_builder_append_unchecked(b, c);
}
```

　これはすごく遅くなりました。Arrayのメモリをいちいち割り当ててるので遅くなるのだと思います。

　String構築時にwith_capacityを使うことでpush時の文字列のサイズ拡張を避けることが出来ます。これは少しだけ速くなることが確認できます。

```Rust
#[no_mangle]
pub extern "C" fn create_string_builder_with_capacity(size : i32) -> *mut StringBuilder{
    let b = Box::new(StringBuilder{ s : String::with_capacity(size as usize) });
    Box::into_raw(b)
}
```

　計測結果としては、StringBuilderの方がUTF16より総じて速く、特に短い文字列で顕著に速いといった感じです。

#ネイティブ

　Rustの標準の範囲だとJavaScriptでの処理とほぼ同じものがネイティブでかけるのはUTF16版なので、UTF16版のJavaScript部分をRustで書き直し、ネイティブ実行したものと、WebAssemblyで実行したものを作って比較しました。短い文字列は1.5倍、長い文字列は6倍といった感じの速度差でした。JavaScript版とWebAssembly版の速度差は、長い文字列ではあまりなく、短い文字列では2倍ほど差がついていました。

```Rust

#[no_mangle]
pub extern "C" fn alloc(len : usize) -> *mut u8{
    let mut vec = Vec::<u8>::with_capacity(len);
    unsafe { vec.set_len(len); }
    Box::into_raw(vec.into_boxed_slice()) as *mut u8
}

#[no_mangle]
pub extern "C" fn free(raw: *mut u8, len : usize) {
    unsafe {
        let s = ::std::slice::from_raw_parts_mut(raw, len);
        Box::from_raw(s);
    }
}

pub struct StringBuilder{
    s : String
}

#[no_mangle]
pub extern "C" fn create_string_builder() -> *mut StringBuilder{
    let b = Box::new(StringBuilder{ s : "".to_string() });
    Box::into_raw(b)
}

#[no_mangle]
pub extern "C" fn create_string_builder_with_capacity(size : i32) -> *mut StringBuilder{
    let b = Box::new(StringBuilder{ s : String::with_capacity(size as usize) });
    Box::into_raw(b)
}

#[no_mangle]
pub extern "C" fn destroy_string_builder(b : *mut StringBuilder){
    unsafe{ Box::from_raw(b); }
}

#[no_mangle]
pub extern "C" fn string_builder_append(sb : *mut StringBuilder, code_point : i32) -> i32{
    match ::std::char::from_u32(code_point as u32) {
        Some(c) => {
            unsafe { (*sb).s.push(c) };
            return 1;
        }
        _ => return 0,
    }
}

#[no_mangle]
pub extern "C" fn string_builder_append_unchecked(sb : *mut StringBuilder, code_point : i32){
    unsafe { (*sb).s.push(::std::char::from_u32_unchecked(code_point as u32)); }
}

#[no_mangle]
pub extern "C" fn test_with_string_builder(sb : *mut StringBuilder) -> i32 {
    unsafe{ test_method(&(*sb).s) }
}

#[no_mangle]
pub extern "C" fn test_with_utf16(utf16 : *mut u16, len : i32) -> i32 {
    unsafe {
        let slice =  ::std::slice::from_raw_parts(utf16, len as usize);
        match String::from_utf16(slice) {
            Ok(s) => test_method(&s),
            _ => -1,
        }
    }
}

#[no_mangle]
pub extern "C" fn test_with_utf16_lossy(utf16 : *const u16, len : i32) -> i32 {
    unsafe{
        let slice = ::std::slice::from_raw_parts(utf16, len as usize);
        test_method(&String::from_utf16_lossy(slice))
    }
}

#[no_mangle]
pub extern "C" fn test_with_utf8_unchecked(utf8 : *const u8, len : i32) -> i32 {
    unsafe{
        let slice = ::std::slice::from_raw_parts(utf8, len as usize);
        test_method(&String::from_utf8_unchecked(slice.to_vec()))
    }
}

#[no_mangle]
pub extern "C" fn test_with_utf8(utf8 : *const u8, len : i32) -> i32 {
    unsafe{
        let slice = ::std::slice::from_raw_parts(utf8, len as usize);
        match String::from_utf8(slice.to_vec()){
            Ok(s) => test_method(&s),
            _ => -1
        }
    }
}

fn test_method(s : &str) -> i32{
    let mut result = 0;
    for c in s.chars(){
        result += c as i32;
    }
    return result;
}

pub struct FullAsmInput{
    array : Vec<Vec<u16>>
}

#[no_mangle]
pub extern "C" fn create_full_asm_input() -> *const FullAsmInput {
    let ss = ["ABCDE", "あああああ",
        "ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ",
        "あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをん",
        "😃😁😂",
        "😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂"];

    let vec : Vec<Vec<u16>> = ss.iter().map(|s| s.encode_utf16().collect()).collect();
    let b = Box::new(FullAsmInput{ array : vec });
    return Box::into_raw(b);
}

#[no_mangle]
pub extern "C" fn full_webasm(inp : *const FullAsmInput, index : i32, loop_count : i32) -> i32 {
    unsafe{
        let mut r = 0;
        for _ in 0..loop_count {
            let utf16 : &Vec<u16> = &(*inp).array[index as usize];
            let s = String::from_utf16_lossy(utf16);
            r += test_method(&s);
        }
        return r;
    }
}

extern "C" {
    fn timer_start();
    fn timer_stop();
}

#[no_mangle]
pub extern "C" fn test_full_asm_utf16() -> i32{
    let top_loop = 3;
    let super_top_loop = 10;
    let inner_loop = 10000;

    let mut result_holder = 0;
    let inp = create_full_asm_input();
    for _ in 0..super_top_loop{
        unsafe {
            for index in 0..(*inp).array.len() {
                for _ in 0..top_loop {
                    timer_start();
                    let r = full_webasm(inp, index as i32, inner_loop);
                    timer_stop();
                    result_holder += r;
                }
            }
        }
    }
    return result_holder;
}


pub fn test_native_utf16(){
    let top_loop = 3;
    let super_top_loop = 3;
    let inner_loop = 10000;

    let mut result_holder = 0;
    let inp = create_full_asm_input();
    for _ in 0..super_top_loop{
        unsafe {
            for index in 0..(*inp).array.len() {
                for _ in 0..top_loop {
                    let start = ::std::time::Instant::now();
                    let r = full_webasm(inp, index as i32, inner_loop);
                    let end = start.elapsed();
                    let s = String::from_utf16_lossy(&(*inp).array[index]);
                    println!("{}ms {} {}", end.subsec_millis(), s, r);
                    result_holder += r;
                }
            }
        }
    }
    println!("Done {}", result_holder);
}

pub fn test_native_sb(){
    let top_loop = 3;
    let super_top_loop = 3;
    let inner_loop = 10000;

    let mut result_holder = 0;
    let inp = create_full_asm_input();

    for _ in 0..super_top_loop{
        unsafe {
            for index in 0..(*inp).array.len() {
                for _ in 0..top_loop {
                    let start = ::std::time::Instant::now();
                    for _ in 0..inner_loop{
                        let s = String::from_utf16_lossy(&(*inp).array[index]);
                        let sb = create_string_builder_with_capacity((s.len() * 3) as i32);
                        for c in s.chars() {
                            string_builder_append_unchecked(sb, c as i32);
                        }
                        let r = test_method(&(*sb).s);
                        result_holder += r;
                        destroy_string_builder(sb);
                    }
                    let end = start.elapsed();
                    let s = String::from_utf16_lossy(&(*inp).array[index]);
                    println!("{}ms {} {}", end.subsec_millis(), s, result_holder);
                }
            }
        }
    }
    println!("Done {}", result_holder);
}
```

```JavaScript
function main() {
    fetch('compact64.wasm').then(response => response.arrayBuffer())
        .then(bytes => WebAssembly.instantiate(bytes, {}))
        .then(results => {
            var ss = ["ABCDE", "あああああ",
                "ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ",
                "あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをん",
                "😃😁😂",
                "😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂"];
            var loop = 10000;
            var top_loop = 3;
            var super_top_loop = 10;
            const ex = results.instance.exports;
            var result_holder = 0;

            for (var super_top_counter = 0; super_top_counter < super_top_loop; super_top_counter++) {
                for (let s of ss) {
                    for (var top_counter = 0; top_counter < top_loop; top_counter++) {
                        console.time("sb " + s);
                        for (var i = 0; i < loop; i++) {
                            var b = ex.create_string_builder();
                            for (let c of s) {
                                ex.string_builder_append_unchecked(b, c.codePointAt(0));
                            }
                            let r = ex.test_with_string_builder(b);
                            result_holder += r;
                            ex.destroy_string_builder(b);
                        }
                        console.timeEnd("sb " + s);
                    }
                    for (var top_counter = 0; top_counter < top_loop; top_counter++) {
                        console.time("sb wc" + s);
                        for (var i = 0; i < loop; i++) {
                            var b = ex.create_string_builder_with_capacity(s.length * 3);
                            for (let c of s) {
                                ex.string_builder_append_unchecked(b, c.codePointAt(0));
                            }
                            let r = ex.test_with_string_builder(b);
                            result_holder += r;
                            ex.destroy_string_builder(b);
                        }
                        console.timeEnd("sb wc" + s);
                    }
                    for (var top_counter = 0; top_counter < top_loop; top_counter++) {
                        console.time("sb nc" + s);
                        for (var i = 0; i < loop; i++) {
                            var b = ex.create_string_builder_with_capacity(s.length * 3);
                            for (let c of s) {
                                ex.string_builder_append(b, c.codePointAt(0));
                            }
                            let r = ex.test_with_string_builder(b);
                            result_holder += r;
                            ex.destroy_string_builder(b);
                        }
                        console.timeEnd("sb nc" + s);
                    }
                }
            }

            console.log("done " + result_holder);
        });
}
```

```JavaScript
function main() {
    fetch('compact64.wasm').then(response => response.arrayBuffer())
        .then(bytes => WebAssembly.instantiate(bytes, {}))
        .then(results => {
            var ss = ["ABCDE", "あああああ",
                "ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ",
                "あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをん",
                "😃😁😂",
                "😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂"];
            var loop = 10000;
            var top_loop = 3;
            var super_top_loop = 3;
            const ex = results.instance.exports;

            var result_holder = 0;
            for (var super_top_counter = 0; super_top_counter < super_top_loop; super_top_counter++) {

                for (let s of ss) {
                    for (var top_counter = 0; top_counter < top_loop; top_counter++) {
                        console.time("utf8 uc" + s);

                        for (var loop_counter = 0; loop_counter < loop; loop_counter++) {

                            let utf8 = new TextEncoder().encode(s);
                            let len = utf8.byteLength;
                            var allocated = ex.alloc(len);
                            var mem = ex.memory.buffer;
                            var array = new Uint8Array(mem, allocated, len);

                            for (var i = 0; i < len; ++i) {
                                array[i] = utf8[i];
                            }
                            let r = ex.test_with_utf8_unchecked(allocated, len);
                            //console.log("result " + r);

                            result_holder += r;
                            ex.free(allocated);
                        }
                        console.timeEnd("utf8 uc" + s);
                    }
                    for (var top_counter = 0; top_counter < top_loop; top_counter++) {
                        console.time("utf8 " + s);

                        for (var loop_counter = 0; loop_counter < loop; loop_counter++) {
                            let utf8 = new TextEncoder().encode(s);
                            let len = utf8.byteLength;
                            var allocated = ex.alloc(len);
                            var mem = ex.memory.buffer;
                            var array = new Uint8Array(mem, allocated, len);
                            array.set(utf8, 0);

                            let r = ex.test_with_utf8(allocated, len)
                            //console.log("result " + r);

                            result_holder += r;
                            ex.free(allocated);
                        }
                        console.timeEnd("utf8 " + s);
                    }
                }

            }


            console.log("done " + result_holder);
        });
}
```

```JavaScript
function main() {
    fetch('compact64.wasm').then(response => response.arrayBuffer())
        .then(bytes => WebAssembly.instantiate(bytes, {}))
        .then(results => {
            var ss = ["ABCDE", "あああああ",
                "ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ",
                "あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをん",
                "😃😁😂",
                "😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂"];
            var loop = 10000;
            var top_loop = 3;
            var super_top_loop = 10;
            const ex = results.instance.exports;

            var result_holder = 0;
            for (var super_top_counter = 0; super_top_counter < super_top_loop; super_top_counter++) {
                for (let s of ss) {
                    for (var top_counter = 0; top_counter < top_loop; top_counter++) {
                        console.time("utf16 " + s);

                        for (var loop_counter = 0; loop_counter < loop; loop_counter++) {
                            let len = s.length;
                            var allocated = ex.alloc(len * 2);
                            var mem = ex.memory.buffer;
                            let array = new Uint16Array(mem, allocated, len);

                            for (var i = 0; i < len; i++) {
                                array[i] = s.charCodeAt(i);
                            }
                            let r = ex.test_with_utf16_lossy(allocated, len)

                            result_holder += r;
                            ex.free(allocated);
                        }
                        console.timeEnd("utf16 " + s);
                    }
                }

            }


            console.log("done " + result_holder);
        });
}
```

```
UTF8 Chrome
utf8 ABCDE: 35.390869140625ms
utf8 ABCDE: 35.55224609375ms
utf8 ABCDE: 47.984130859375ms
utf8 setABCDE: 35.149169921875ms
utf8 setABCDE: 54.915771484375ms
utf8 setABCDE: 36.01708984375ms
utf8 あああああ: 38.47998046875ms
utf8 あああああ: 36.250732421875ms
utf8 あああああ: 53.450927734375ms
utf8 setあああああ: 37.826904296875ms
utf8 setあああああ: 56.7099609375ms
utf8 setあああああ: 39.06787109375ms
utf8 ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ: 45.118896484375ms
utf8 ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ: 45.10888671875ms
utf8 ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ: 102.430908203125ms
utf8 setABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ: 44.109130859375ms
utf8 setABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ: 64.126953125ms
utf8 setABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ: 44.39306640625ms
utf8 あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをん: 58.490966796875ms
utf8 あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをん: 57.77099609375ms
utf8 あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをん: 76.09423828125ms
utf8 setあいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをん: 192.132080078125ms
utf8 setあいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをん: 56.69775390625ms
utf8 setあいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをん: 56.27783203125ms
utf8 😃😁😂: 37.4521484375ms
utf8 😃😁😂: 36.100830078125ms
utf8 😃😁😂: 51.11865234375ms
utf8 set😃😁😂: 36.751953125ms
utf8 set😃😁😂: 57.02490234375ms
utf8 set😃😁😂: 55.968017578125ms
utf8 😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂: 70.14892578125ms
utf8 😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂: 65.68798828125ms
utf8 😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂: 83.737060546875ms
utf8 set😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂: 66.589111328125ms
utf8 set😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂: 85.83203125ms
utf8 set😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂: 68.0380859375ms
```

```
StringBuilder Chrome
sb ABCDE: 2.738037109375ms
sb ABCDE: 3.153076171875ms
sb ABCDE: 2.84814453125ms
sb wcABCDE: 2.470947265625ms
sb wcABCDE: 2.3798828125ms
sb wcABCDE: 2.394775390625ms
sb あああああ: 4.3671875ms
sb あああああ: 4.308837890625ms
sb あああああ: 4.3046875ms
sb wcあああああ: 3.43798828125ms
sb wcあああああ: 3.531005859375ms
sb wcあああああ: 3.44287109375ms
sb ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ: 15.16796875ms
sb ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ: 15.104248046875ms
sb ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ: 15.220947265625ms
sb wcABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ: 13.92822265625ms
sb wcABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ: 13.8203125ms
sb wcABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ: 13.89111328125ms
sb あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをん: 23ms
sb あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをん: 22.77490234375ms
sb あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをん: 22.839111328125ms
sb wcあいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをん: 20.926025390625ms
sb wcあいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをん: 20.75ms
sb wcあいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをん: 20.583984375ms
sb 😃😁😂: 3.388916015625ms
sb 😃😁😂: 3.39111328125ms
sb 😃😁😂: 3.458984375ms
sb wc😃😁😂: 2.758056640625ms
sb wc😃😁😂: 2.79296875ms
sb wc😃😁😂: 2.719970703125ms
sb 😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂: 28.6201171875ms
sb 😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂: 28.443115234375ms
sb 😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂: 28.491943359375ms
sb wc😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂: 25.0009765625ms
sb wc😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂: 25.05615234375ms
sb wc😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂: 24.971923828125ms
```

```
UTF16 Chrome
utf16 ABCDE: 6.06298828125ms
utf16 ABCDE: 5.43408203125ms
utf16 ABCDE: 6.02197265625ms
utf16 あああああ: 6.592041015625ms
utf16 あああああ: 7.18505859375ms
utf16 あああああ: 6.51416015625ms
utf16 ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ: 19.6708984375ms
utf16 ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ: 18.281005859375ms
utf16 ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ: 18.60986328125ms
utf16 あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをん: 24.781982421875ms
utf16 あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをん: 25.1171875ms
utf16 あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをん: 24.196044921875ms
utf16 😃😁😂: 6.579833984375ms
utf16 😃😁😂: 6.188232421875ms
utf16 😃😁😂: 6.69091796875ms
utf16 😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂: 38.985107421875ms
utf16 😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂: 37.990966796875ms
utf16 😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂: 37.183837890625ms
```

```
StringBuilder FF
sb ABCDE: 3ms test_js_string_sb.js:29:25
sb ABCDE: 2ms test_js_string_sb.js:29:25
sb ABCDE: 4ms test_js_string_sb.js:29:25
sb wcABCDE: 2ms test_js_string_sb.js:42:25
sb あああああ: 5ms test_js_string_sb.js:29:25
sb あああああ: 3ms test_js_string_sb.js:29:25
sb wcあああああ: 4ms test_js_string_sb.js:42:25
sb wcあああああ: 3ms test_js_string_sb.js:42:25
sb wcあああああ: 5ms test_js_string_sb.js:42:25
sb ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ: 18ms test_js_string_sb.js:29:25
sb ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ: 17ms test_js_string_sb.js:29:25
sb ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ: 16ms test_js_string_sb.js:29:25
sb wcABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ: 16ms test_js_string_sb.js:42:25
sb あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをん: 23ms test_js_string_sb.js:29:25
sb あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをん: 22ms test_js_string_sb.js:29:25
sb wcあいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをん: 22ms test_js_string_sb.js:42:25
sb wcあいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをん: 23ms test_js_string_sb.js:42:25
sb 😃😁😂: 4ms test_js_string_sb.js:29:25
sb 😃😁😂: 3ms test_js_string_sb.js:29:25
sb wc😃😁😂: 3ms test_js_string_sb.js:42:25
sb wc😃😁😂: 2ms test_js_string_sb.js:42:25
sb 😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂: 30ms test_js_string_sb.js:29:25
sb 😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂: 36ms test_js_string_sb.js:29:25
sb 😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂: 32ms test_js_string_sb.js:29:25
sb wc😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂: 27ms test_js_string_sb.js:42:25
sb wc😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂: 28ms test_js_string_sb.js:42:25
sb wc😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂: 27ms test_js_string_sb.js:42:25
```

```
UTF16 FF
utf16 ABCDE: 8ms test_js_string_utf16.js:35:25
utf16 ABCDE: 9ms test_js_string_utf16.js:35:25
utf16 ABCDE: 8ms test_js_string_utf16.js:35:25
utf16 あああああ: 11ms test_js_string_utf16.js:35:25
utf16 あああああ: 10ms test_js_string_utf16.js:35:25
utf16 ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ: 15ms test_js_string_utf16.js:35:25
utf16 ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ: 14ms test_js_string_utf16.js:35:25
utf16 あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをん: 21ms test_js_string_utf16.js:35:25
utf16 あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをん: 20ms test_js_string_utf16.js:35:25
utf16 あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをん: 22ms test_js_string_utf16.js:35:25
utf16 😃😁😂: 8ms test_js_string_utf16.js:35:25
utf16 😃😁😂: 10ms test_js_string_utf16.js:35:25
utf16 😃😁😂: 8ms test_js_string_utf16.js:35:25
utf16 😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂: 26ms test_js_string_utf16.js:35:25
utf16 😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂: 24ms test_js_string_utf16.js:35:25
utf16 😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂: 26ms test_js_string_utf16.js:35:25
```



```
UTF16 Native
2ms ABCDE 3350000
2ms ABCDE 3350000
2ms ABCDE 3350000
3ms あああああ 617700000
3ms あああああ 617700000
3ms あああああ 617700000
3ms ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ 40300000
3ms ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ 40300000
3ms ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ 40300000
10ms あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをん 1407372704
9ms あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをん 1407372704
9ms あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをん 1407372704
2ms 😃😁😂 -439547296
2ms 😃😁😂 -439547296
2ms 😃😁😂 -439547296
10ms 😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂 1557177856
10ms 😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂 1557177856
10ms 😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂😃😁😂 1557177856
```

```
UTF16 WebAsm
t: 2.655029296875ms
t: 2.674072265625ms
t: 2.7431640625ms
t: 4.09716796875ms
t: 4.06005859375ms
t: 4.05517578125ms
t: 17.89013671875ms
t: 17.746826171875ms
t: 17.2470703125ms
t: 26.5791015625ms
t: 26.385986328125ms
t: 26.4130859375ms
t: 2.96728515625ms
t: 3.013916015625ms
t: 3.029052734375ms
t: 30.0400390625ms
t: 30.01416015625ms
t: 30.182861328125ms
```