我最近在试验一个性能对比的情况,我希望把一个很大的json使用rust的hashmap存起来,然后js需要查询某一个键值对的话,就传入一个key,wasm的接口返回一个value
以下是我的部分代码
#[wasm_bindgen]
pub struct Param {
parmEnumList : HashMap<String,Vec<i32>>,
}
#[wasm_bindgen]
impl Param {
pub fn new(paramEnumList: JsValue) -> Param {
let parmEnumList = serde_wasm_bindgen::from_value(paramEnumList).unwrap();
Param {
parmEnumList
}
}
pub fn GetP(&self, code: JsValue) -> JsValue {
let paramCode : String = serde_wasm_bindgen::from_value(code).unwrap();
let val : &Vec<i32> = self.parmEnumList.get(¶mCode).unwrap();
let jsVal = serde_wasm_bindgen::to_value(&val).unwrap();
// console::log_1(jsVal);
jsVal
}
pub fn GetPTestPerf(&self) -> JsValue{
// let paramCode : String = serde_wasm_bindgen::from_value(code).unwrap();
for i in 0..1000000 {
self.parmEnumList.get("PL_1");
}
serde_wasm_bindgen::to_value(self.parmEnumList.get("PL_1").unwrap()).unwrap()
}
}
上面的代码中我做了一个小实验,我循环100w次,取"PL_1"的值
我在我的index.js 写了另一段代码,我要对比同样的数据,我使用js对象来存,然后直接用js的取json的值的方法,对同样key是"PL_1"进行取值,对比两段“逻辑”是一样的代码的(只是语言不一样)耗时
以下是index.js的代码
import {Param} from "sct_wasm";
let data = {
parmEnumList: {}
};
for (let i = 0; i < 100000; i++) {
let name = "PL_" + i;
data.parmEnumList[name] = [1,2,3]
}
window.ParamObj = Param.new(data.parmEnumList);
// wasm的map
console.time("试验wasm的getP")
ParamObj.GetPTestPerf("PL_1");
console.timeEnd("试验wasm的getP")
// js的map
console.time("试验js的getP")
for (let i = 0; i < 1000000; i++) {
data.parmEnumList["PL_1"]
}
console.timeEnd("试验js的getP")
结果让我大吃一惊, 试验wasm的getP: 28.261962890625ms 试验js的getP: 9.94091796875ms
wasm没有带给我想要的效率,js的执行时间少了2-3倍,这样让我原来想的方案很难做了,求大佬看看是不是rust的hashmap可以调整?求教这个现象背后的原因?
1
共 17 条评论, 1 页
评论区
写评论优势的话wasm的数据跑在一块连续的bytes上,没有gc开销,本身是二进制的,解析和jit开销要比js小。js里复杂耗cpu的算法用rust+wasm有优势,但是你用js原生的结构去和wasm比性能肯定很难比出个高低来。另外wasm本身是在快速发展的,1.0的规范也就去年刚出来。下面提到的wasm自身限制问题不少都有rfc在推进,编译器的优化也是一个渐进的过程。js的性能天花板基本到顶了,但wasm各方面都还有提升优化的空间。 对以下内容的回复:
既然如此,那么rust+wasm对比js的真正优势又是什么呢? 对以下内容的回复:
rust转wasm确实会出现一些性能损耗,有些是rust编译器的问题,有些是wasm的问题,随便举几个例子: 1.wasm的栈只支持i32,i64等几个基本类型,所以由rust代码转换成wasm的时候,会在堆的起始位置处预留一段空间模拟栈。复杂的数据结构需要放在这里,并将指针偏移存在wasm的栈上。导致全局变量的使用,效率低下。
rust转wasm时会增加多余的代码。比如最简单的a/b, rust生成时会增加除零和溢出检查,编译出来的代码非常长,而其实wasm指令本身已经考虑了这部分。
wasm本身是结构化的,复杂的跳转指令翻译的时候会非常低效;
simd等指令还不支持,一些编译器优化没法做;
内存拷贝等常用库函数只能在wasm里重新实现一遍,和优化过的native代码肯定有差距。所以现在也有rfc在准备增强这部分;
wasm的block之间不能传递变量,只能通过set local,get local传递,效率低。
wasm代码经jit转到native code本身也因jit优化程度影响性能。 等等还有很多。
对以下内容的回复:
是不是要加 build --release 这种
我用的是wasm-pack build命令,还有编译优化的选项吗?请指教 对以下内容的回复:
rust编译时打开优化选项了吗?
我就是在wasm里面调用了10w次呀,你没有认真看代码,index.js,不过目前我还是确信了,js的object确实比rust的hashmap转成wasm要优化的好 对以下内容的回复:
我觉得对比不科学, JS调用wasm本身就会有消耗。 应该在wasm内部调用10W次,然后对比下时间。我猜测和JS对象相差不大。
如果是JS没有内置的功能,如WAV编码为MP3 然后分别用JS和Rust的wasm实现 对比会更好。你现在比较的是V8优化后的map(仅支持字符串)和 Rust全功能的map做比较
BtreeMap我试过了,反而更慢 对以下内容的回复:
换成BtreeMap试试
只是说明js的object(作为最常用的,肯定也是V8优化的重点)的效率比转成wasm的rust的hashmap高,更复杂的数据结构还是要测下来才知道。
这个我了解了,我一直在思考,我是不是可以切换rust hashmap的hasher来提升它的性能,但是目前还没有找到相关的资料或者代码。另外就是按照上一个回复的讨论情况来看,会不会rust转成wasm也存在一些性能损耗 对以下内容的回复:
我刚刚看了一下这篇文章 https://zhuanlan.zhihu.com/p/26169639 , 你说的很有道理(笑哭),但是侧面也说明js的object的native code的效率比rust的hashmap高啊,我原先还打算把数据和算法全部放在rust里面来写,现在看看似乎还是各有优劣。 对以下内容的回复:
The default hashing algorithm is currently SipHash 1-3, though this is subject to change at any point in the future. While its performance is very competitive for medium sized keys, other hashing algorithms will outperform it for small keys such as integers as well as large keys such as long strings, though those algorithms will typically not protect against attacks such as HashDoS.
从标准库文档摘录的
js的hashmap又不是用js写的,而是用c/c++优化过的native code啊。编译成wasm后的肯定没法比。你应该用纯js实现rust的那套hashmap算法,再去比性能。
你没有仔细看代码,我没有传字符串的,仔细看看rust写的代码,我现在希望探讨一下js的map和rust的map差在什么地方,我希望了解一下js的map的设计原理 对以下内容的回复:
现代JS引擎的Hashmap也不慢啊,字符串在js和wasm之间传递降低了速度