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]