- 作者:老汪软件技巧
- 发表时间:2024-09-01 17:03
- 浏览量:
回顾
开始新内容前让我们先回忆下之前的内容,之前我们构造了一棵约30万个节点的树,测出来结果 wasm比js慢约4倍,比较反直觉,所以我们拆分成:循环、对象深拷贝、向量切片、数学函数、向量的插入和删除,对这几部分分别测试,看看到底哪里慢了。
我们已经测试了循环、深拷贝、向量切片及原始指针、向量尾部操作、数学函数,对比结果是 wasm快。这篇内容我们测试下向量的头尾操作,和js对比看谁快。
构造向量的头尾操作
rust代码
#[wasm_bindgen]
#[derive(Clone)]
pub struct Item {
name: String,
children: Vec- ,
}
#[wasm_bindgen]
pub fn vecFun(len: usize) {
let mut nodeStack = vec![];
for _ in 0..len {
let tempNode = Item {
name: String::from(""),
children: vec![],
};
nodeStack.push(tempNode);
}
for _ in 0..len {
nodeStack.remove(0);
}
}
js代码
const vecFun = (len) => {
const nodeStack = [];
for (let i=0; i
const item = {
name: '',
children: [],
};
nodeStack.push(item);
}
for (let i=0; i
nodeStack.shift();
}
}
结论
将 len=7000 传入执行,执行结果
wasm-vec: 481.01708984375 ms
js-vec: 6.257080078125 ms
wasm这么慢,虽然push可能触发向量扩容及内存拷贝(参考向量大小与容量),但发现push很快,确定是向量头部删除很慢。
都是数组头部删除,那为什么js的就快很多,这里解开谜底:
rust的向量是一个动态数组,和大多数语言的数组概念类似,都是开辟一片连续内存,且元素类型都相同,这样做是为了高效访问,缺点是头部操作效率低,牵扯到后面每个元素的移动。
js的数组概念就不一样了,为了追求灵活方便,数组里可以放不同类型的数据,且要支持稀疏性和非整数键,有时类似一个哈希表、有时又是一个真正的数组(一片连续内存),即使底层是真正的数组,V8也对数组头部删除做了优化,只做删除标记延迟做元素移动。
对比来看js的数组和rust的向量不是一个东西,js对底层数据结构做了更多的封装及优化,追求的是灵活方便,但开发者很难参与到底层性能优化。
优化
既然数组这种数据结构不适合头部操作,分析下代码发现构造树的整个逻辑,所有向量操作也只发生在头尾,所以我们想到了双端队列 VecDeque,换成这个试试。
rust代码
#[wasm_bindgen]
pub fn vecDeqFun(len: usize) {
let mut nodeStack: VecDeque- = VecDeque::new();
for _ in 0..len {
let item = Item {
name: String::from(""),
children: vec![],
};
nodeStack.push_back(item);
}
for _ in 0..len {
nodeStack.pop_front();
}
}
优化后结论
再次将 len=7000 传入执行,执行结果
wasm-vec: 508.14501953125 ms
wasm-vec-deq: 0.658935546875 ms
js-vec: 7.974853515625 ms
可以看到使用了双端队列后变快了,wasm比js快约12倍。
问题找到了,那让我们重构下最开始反直觉的那段代码,看看效果如何。
欲知后事如何且听下回分解。