Skip to content
风起
风起

wasm VS js,谁快?(一)反直觉

起因

WebAssembly(缩写wasm)是一种低级的类汇编语言,能够高效地运行在浏览器中,可由C、C++和Rust等语言编译生成。

好吧,看样子wasm像是比js要快,是真的吗?那不妨让我们对比下,真实地感受下wasm的性能,这里用rust编译生成wasm。

结论

风起喜欢用简单易懂地方式说明白问题,直接贴答案

bash
total nodes:  299593
wasm-deq-tree: 347.54443359375 ms
js-tree: 7232.81201171875 ms

我们用相同的算法构造一棵相同大小的树,看谁生成的快,这里可以看到这棵树的节点总数将近30万个。wasm需要约300毫秒完成,js需要约7秒完成,对比下来wasm比js快约20倍。

对比结束,不想看过程的朋友可以散了,剩下的朋友就和我一起踏上这段性能探索之旅吧。

尝试,反直觉

我们构造的树结构如下

bash
{
	name: "node_0_0",
	children: [
		{
			name: "node_1_0",
			children: [...],
		},
		{
			name: "node_1_1",
			children: [...],
		},
		...
	],
}

构造树的代码

rust代码

bash
#[wasm_bindgen]
#[derive(Clone)]
pub struct Item {
    name: String,
    children: Vec<Item>,
}

#[wasm_bindgen]
pub fn treeFun(treeLevel: u32, nodeWidth: u32) -> Item {
    let tempNode = Item {
        name: String::from(""),
        children: vec![],
    };

    let mut root = tempNode.clone();
    root.name = String::from("node_0_0");

    if treeLevel < 2 {
        return root;
    }

    let mut nodeStack: Vec<&mut [Item]> = vec![];

    for m1 in 0..nodeWidth {
        let mut curItem = tempNode.clone();
        curItem.name = format!("node_{}_{}", 1, m1);
        root.children.push(curItem);
    }

    let mut vs: Vec<&mut [Item]> = root.children.chunks_mut(1).collect();
    for m2 in vs.iter_mut() {
        nodeStack.push(*m2);
    }

    for i in 2..treeLevel {
        let curTotal: u32 = nodeWidth.pow(i - 1);

        for j in 0..curTotal {
            let shiftItem = nodeStack.remove(0);

            for k1 in 0..nodeWidth {
                let mut curItem = tempNode.clone();
                curItem.name = format!("node_{}_{}", i, nodeWidth * j + k1);
                shiftItem[0].children.push(curItem);
            }

            let mut ss: Vec<&mut [Item]> = shiftItem[0].children.chunks_mut(1).collect();
            for _ in 0..nodeWidth {
                let curSs = ss.remove(0);
                nodeStack.push(curSs);
            }
        }
    }

    root
}

js代码

bash
const treeFun = (treeLevel, nodeWidth) => {
	const tempNode = {
    	name: '',
    	children: [],
	};
	
    const root = JSON.parse(JSON.stringify(tempNode));
    root.name = 'node_0_0';

    if (treeLevel < 2) {
        return root;
    }

    const nodeStack = [];

    for (var m1=0; m1<nodeWidth; m1++) {
        const curItem = JSON.parse(JSON.stringify(tempNode));
        curItem.name = `node_1_${m1}`;
        root.children.push(curItem);
    }

    for (var m2=0; m2<nodeWidth; m2++) {
        nodeStack.push(root.children[m2]);
    }

    for (var i=2; i<treeLevel; i++) {
        const curTotal = Math.pow(nodeWidth, (i-1));

        for (var j=0; j<curTotal; j++) {
            const shiftItem = nodeStack.shift();

            for (var k1=0; k1<nodeWidth; k1++) {
                const curItem = JSON.parse(JSON.stringify(tempNode));
                curItem.name = `node_${i}_${nodeWidth * j + k1}`;
                shiftItem.children.push(curItem);
            }

            for (var k2=0; k2<nodeWidth; k2++) {
                nodeStack.push(shiftItem.children[k2]);
            }
        }
    }

    return root;
}

我们尝试构造的树有7层深度,每个节点下有8个子节点,共299593个节点,执行结果如下

bash
total nodes:  299593
wasm-tree: 31134.56591796875 ms
js-tree: 7428.5888671875 ms

可以看到js执行速度明显快,奇怪了,反直觉,与预期不符。

分析rust代码,发现有这几部分:循环、对象深拷贝、向量切片、数学函数、向量的插入和删除。

我们对这几部分分别测试,看看到底哪里慢了。

欲知详情且听下回分解。