Array.includesとSet.hasで速度差がどれぐらい出るかを調べた。
良くあるObjectの添字を使ったテクニック等とも比較。
const { performance } = require('perf_hooks');
const test = (len=100) => {
const targetLen = Math.floor(len /10)
const shuffle = (a) => {
const c = [...a]
for (let i = c.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[c[i], c[j]] = [c[j], c[i]]
}
return c
}
const spec = [
["nodeのバージョン",process.versions.node],
["配列のサイズ" , len ],
["検索する項目数" , targetLen ]
]
//配列をlen個作成する [0,1,2,3,4,...,len-1]
const base = Array.from({length: len}, (v, k) => k)
//シャッフルする
const ary = shuffle(base)
//Set化する
const set =(() => {
const start = performance.now()
const set = new Set(ary)
const end = performance.now()
spec.push([ "Setの生成時間" , end - start])
return set
})()
//Mapの生成
const map = (() => {
const pre = base.map(b => [b,b])
const start = performance.now()
const map = new Map(pre)
const end = performance.now()
spec.push([ "Mapの生成時間" , end - start ])
return map
})()
//数字を添え字にしたオブジェクトを作る {"0":0,"1":1,...,[len-1]:len-1}
const obj = (() => {
const start = performance.now()
const obj = ary.reduce((pre,cur) => {
pre[`${cur}`] = cur
return pre
}, {})
const end = performance.now()
spec.push([ "Objectの生成時間" , end - start ])
return obj
})()
console.log(`
${spec.map(r => r.join("|")).join("\n")}
`)
//存在チェックの試験用の配列
const target = shuffle(base).slice(0,targetLen)
const result = [
["含むチェックの方式","処理時間時間"],
]
//indexOf を使う
{
const start = performance.now();
for(let t of target ){
if(ary.indexOf(t) > -1){
}
}
const end = performance.now();
result.push([ "Array.indexOf" , end - start])
}
//Array.inclueds のテスト
{
const start = performance.now();
for(let t of target ){
if(ary.includes(t)){
}
}
const end = performance.now();
result.push([ "Array.inclueds" , end - start])
}
//Set.has のテスト
{
const start = performance.now();
for(let t of target ){
if(set.has(t)){
}
}
const end = performance.now();
result.push([ "Set.has" , end - start])
}
//Map.has のテスト
{
const start = performance.now();
for(let t of target ){
if(map.has(t)){
}
}
const end = performance.now();
result.push([ "Map.has" , end - start])
}
//オブジェクトの key を使ったテクニック のテスト
{
const start = performance.now();
for(let t of target ){
if(obj[t]){
}
}
const end = performance.now();
result.push([ "obj[t]" , end - start])
}
// in でも試してみる
{
const start = performance.now();
for(let t of target ){
if( t in obj){
}
}
const end = performance.now();
result.push([ "t in obj" , end - start])
}
console.log(`
${result.map(r => r.join("|")).join("\n")}
`)
}
test(100000)
1回目
項目 | 値 |
---|---|
nodeのバージョン | 12.18.1 |
配列のサイズ | 100000 |
検索する項目数 | 10000 |
Setの生成時間 | 8.558184027671814 |
Mapの生成時間 | 13.922562003135681 |
Objectの生成時間 | 31.00487995147705 |
チェックの方式 | 処理時間時間 |
---|---|
Array.indexOf | 944.757071018219 |
Array.inclueds | 944.7714960575104 |
Set.has | 2.41020405292511 |
Map.has | 2.5640859603881836 |
obj[t] | 1.2104129791259766 |
t in obj | 0.7970190048217773 |
2回目
項目 | 値 |
---|---|
nodeのバージョン | 12.18.1 |
配列のサイズ | 100000 |
検索する項目数 | 10000 |
Setの生成時間 | 8.59699296951294 |
Mapの生成時間 | 11.606724977493286 |
Objectの生成時間 | 35.41410505771637 |
チェックの方式 | 処理時間時間 |
---|---|
Array.indexOf | 945.146420955658 |
Array.inclueds | 945.0419319868088 |
Set.has | 2.4140039682388306 |
Map.has | 2.5660550594329834 |
obj[t] | 1.2223680019378662 |
t in obj | 0.8045949935913086 |
3回目
項目 | 値 |
---|---|
nodeのバージョン | 12.18.1 |
配列のサイズ | 100000 |
検索する項目数 | 10000 |
Setの生成時間 | 8.761895060539246 |
Mapの生成時間 | 11.589484095573425 |
Objectの生成時間 | 30.759024024009705 |
チェックの方式 | 処理時間時間 |
---|---|
Array.indexOf | 943.3850150108337 |
Array.inclueds | 947.2091970443726 |
Set.has | 2.461833953857422 |
Map.has | 2.6097559928894043 |
obj[t] | 1.2358649969100952 |
t in obj | 0.8159409761428833 |
結論 Setを使う。
結果的だけみると in 演算子を使うパターンが一番早いが object に一旦変換するテクニックは
生成コストが高く常に使って効果を得られるようなテクニックではないので使わないほうが良い。
また、存在チェックしたい物が stringやnumber以外の場合は使えない。
Setは生成コストはそれほど高くなく、検索も早いので、状況によっては含むチェックのためだけに 配列から一旦Setを生成する価値はありそう。
Set.has と Map.hasはスピードにあまり差が無い。(MapがSetに依存してるのかな?
indexof と includes は速度に差は無く順当に遅い
あまり不思議な点はなかった。
ちなみに Map の key が オブジェクトでも良いって最近知った。