LoginSignup
0
0

JavaScriptのメソッドチェインが遅い理由

Last updated at Posted at 2024-06-29

RustとJavaScriptで似たような書き方のメソッドチェインを比較します。

Rust

fn main() {
    let v = vec![0, 1, 2];
    v.iter().map(|elem|{
        println!("map: {elem}");
        elem
    }).for_each(|elem| {
        println!("for_each: {elem}");
    })
}

出力はこんな感じになります。

map: 0
for_each: 0
map: 1
for_each: 1
map: 2
for_each: 2

JavaScript

const v = [0, 1, 2]
v.map((elem) => {
    console.log(`map: ${elem}`)
    return elem
}).forEach((elem) => {
    console.log(`for_each: ${elem}`)
})

出力はこんな感じになります。

map: 0
map: 1
map: 2
for_each: 0
for_each: 1
for_each: 2

違い

Rustは最初の要素に対してmap->for_eachが順番に処理されていきましたが、JavaScriptでは一旦mapが全要素処理してからforEachの処理が動いています。JavaScriptはmapの結果を一旦全部どこかに保存しないとfor_eachを動かせないということになります。対してRustは処理中は1つの要素の値だけしか覚えてなくていいので、途中経過の保存とか考える必要がありません。

つまりRustのメソッドチェインの方がはるかに効率良く処理できるということです。

まとめ

JavaScriptの標準メソッドチェインは(実装次第だけど)他の言語(のストリーム処理)と比べて遅くなりがち。

おまけ

JavaScriptもrx.jsを使うと同じロジックが使えます。

import { of, map } from 'rxjs'
of(1,2,3)
    .pipe(map((e) => {
        console.log(`map: ${e}`)
        return e
    })).subscribe((e) =>
        console.log(`subscribe: ${e}`)
    )
map: 1
subscribe: 1
map: 2
subscribe: 2
map: 3
subscribe: 3

rx.jsの測定(おまけのおまけ)

import {from, map} from 'rxjs'
(async()=>{
    const N = 5000000
    const array = [...Array(N)].map((_,i)=>i)
    const map_foreach = (v)=>{
        const r = new Array(v.length)
        v.map((e)=>e)
            .forEach((e, i)=>r[i]=e)
        return r
    }
    const foreach = (v)=>{
        const r = new Array(v.length)
        v.forEach((e, i)=>r[i]=e)
        return r
    }
    const forof = (v)=>{
        const r = new Array(v.length)
        for (const [e, i] of v.entries()) {r[i]=e}
        return r
    }
    const forloop = (v)=>{
        const r = new Array(v.length)
        for (let i = 0; i < N; ++i) {r[i]=v[i]}
        return r
    }
    const rxjs_map_foreach = async(v)=>{
        const r = new Array(v.length)
        let i = 0
        await from(v)
            .pipe(map((e) => e))
            .forEach((e) => r[i++]=e)
        return r
    }
    const measure = (f, ...args)=>{
        const s = performance.now()
        const r = f.func(...args)
        const e = performance.now()
        return [f.name, e - s, r]
    }
    const measureAsync = async(f, ...args) => {
        const s = performance.now()
        const r = await f.func(...args)
        const e = performance.now()
        return [f.name, e - s, r]
    }
    const print = (r)=>{
        console.log(`${r[0]}: ${r[1]}[ms]`)
    }
    const test = (r)=>{
        const ret = r[2]
        if (ret.length !== N) {
            r[1] = 0
        }
        for (const [i, e] of ret.entries()) {
            if (i != e) {
                console.log(i,e)
                r[1] = 0
                break
            }
        }
        return r
    }
    print(test(measure({name: 'foreach         ', func: foreach}, array)))
    print(test(measure({name: 'forof           ', func: forof}, array)))
    print(test(measure({name: 'forloop         ', func: forloop}, array)))
    print(test(measure({name: 'map_foreach     ', func: map_foreach}, array)))
    print(test(await measureAsync({name: 'rxjs_map_foreach', func: rxjs_map_foreach}, array)))
})()
foreach         : 93.28579699993134[ms]
forof           : 152.27213901281357[ms]
forloop         : 47.433206021785736[ms]
map_foreach     : 192.4973850250244[ms]
rxjs_map_foreach: 119.94715702533722[ms]
0
0
6

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0