wasm实例开发004-矩阵乘法

本文我们将介绍如何通过wasm来实现矩阵乘法,在开始之前我们先了解一下矩阵乘法的运算法则。

一、矩阵乘法的运算法则

①、首先我们定义两个矩阵m1m2,要将这2个矩阵做乘法运算,则需要满足m1的列数必须等于m2的行数,如果不满足则不能运算。

②、矩阵乘法运算是用m1的每一行分别与m2的每一列对应位置元素相乘并累加作为结果,如图所示:

使用m1的第一行乘以m2的第一列并累加即1*1+2*3得到结果7,使用m1的第一行乘以m2的第二列并累加即1*2+2*4得到结果10,使用m1的第二行乘以m2的第一列并累加即3*1+4*3得到结果15,依次类推得到最终结果。

二、C代码编写

对于js来说,矩阵的表现形式就是一个二维数组,对于C语言来说虽然我们也可以使用二维数组来描述矩阵,但是考虑后期使用的便捷性,我们定义一个结构体用以描述矩阵:

typedef struct {
    int row;
    int col;
    int **data;
} Matrix;

我们没办法通过js直接将一个结构体传递给C程序,但我们可以通过一维数组加上矩阵的行和列来将初始一个矩阵的必要信息传递给C程序,当C程序接收到这些信息后生成结构体矩阵即可,初始代码如下:

Matrix initJsMatrix(uint8_t *arr, int row, int col) {
    Matrix matrix;
    matrix.row = row;
    matrix.col = col;
    matrix.data = (int **)malloc(sizeof(int *) * row);
    for (int i=0; i<row; i++) {
        matrix.data[i] = (int *)malloc(sizeof(int) * col);
        for (int j=0; j<col; j++) {
            matrix.data[i][j] = arr[i * col + j];
        }
    }
    return matrix;
}

我们最终要实现的是矩阵乘法,所以C程序会接受两个矩阵的信息,我们在C程序中定义一个入口方法用于给js调用:

Matrix globalJsRet;

int **jsMulMatrix(uint8_t *arr1, int row1, int col1, uint8_t *arr2, int row2, int col2) {
    Matrix m1 = initJsMatrix(arr1, row1, col1);
    Matrix m2 = initJsMatrix(arr2, row2, col2);
    Matrix ret = mulMatrix(m1, m2);
    globalJsRet = ret;
    return ret.data;
}

int *jsGetLineAddress(int i) {
    return globalJsRet.data[i];
}

jsMulMatrix是暴露给C程序用于做矩阵乘法运算的入口函数,最终的计算结果通过二维数组返回给js,而二维数组的每一行的数据在内存中的存储不一定是连续的,后期我们在js中获取结果的时候需要知道二维数组每一行数据的起始指针,所以我们定义了一个全局变量globalJsRet用于记录我们的计算结果,方便后期js调用jsGetLineAddress函数获取每一行的起始指针。

注意这里接收数组指针的类型一定要用uint8_t,不然会导致接收到的数据错误,uint8_t定义为头文件<stdint.h>中。

矩阵乘法运算的函数定义如下:

Matrix mulMatrix(Matrix m1, Matrix m2) {
    Matrix ret;
    ret.row = m1.row;
    ret.col = m2.col;
    ret.data = (int **)malloc(sizeof(int *) * m1.row);
    for (int i=0; i<m1.row; i++) {
        ret.data[i] = (int *)malloc(sizeof(int) * m2.col);
        for (int j=0; j<m2.col; j++) {
            for (int k=0; k<m1.col; k++) {
                ret.data[i][j] += m1.data[i][k] * m2.data[k][j];
            }
        }
    }
    return ret;
}

三、将C程序编译成wasm应用

我们将C代码保存为matrix.c,注意头文件的引用,然后在.c文件相同目录下新建build.sh文件,输入以下内容并保存:

#! /usr/bin/env bash

emcc -o matrix.js matrix.c \
    -Oz \
    -s EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]' \
    -s EXPORTED_FUNCTIONS='["_jsMulMatrix", "_jsGetLineAddress"]' -s LINKABLE=1

执行source 你的emsdk目录/emsdk_env.sh命令链接emcc命令,并在当前目录通过chmod +x ./build.sh命令赋予打包脚本执行权限,最后通过./build.sh命令完成C程序到wasm应用的编译。

四、使用js调用wasm应用

要使用js调用C暴露的函数,我们可以使用ccall方法轻松完成数组向C程序的传递,然后使用C导出的jsGetLineAddress方法获取结果二维数组每一行的起始指针,最终完成结果二维数组的获取,示例代码如下:

Module.ccall(
    'jsMulMatrix',
    'array', 
    ['array', 'number', 'number', 'array', 'number', 'number'],
    [arr1, row1, col1, arr2, row2, col2]
);

const row = row1;
const col = col2;
const ret = [];
for (let i=0; i<row; i++) {
    const pointer = _jsGetLineAddress(i);
    ret.push(Array.from(new Uint32Array(Module.asm.memory.buffer, pointer, col)));
}
console.log(ret);

如果对js调用wasm各种数据类型的交换还不是太清楚的朋友可以去看一下之前的文章《彻底弄懂Js和Wasm间各种数据类型的传递》,这里我们实现了一个简单的demo(链接放在文末),运行效果如下:

五、结语

相较于本系列前3篇文章,本文实现了一个相对复杂一点的应用,主要涉及了一维数组向C函数的传递,我们可以通过ccallcwrap轻松完成,以及js获取结果二维数组的方法,我们通过C程序定义的辅助函数jsGetLineAddress来获取每一行数据的起始指针,最终拼接出整个结果二维数组。

在线demo:https://demo.deanhan.cn/matrix/

如需下载本文涉及的源码及素材包,请在关注本站公众号后发送:matrix

系列文章导航:

第1节: Wasm实例开发001-斐波那契数列

第2节: Wasm实例开发002-最长回文字符串

第3节: wasm实例开发003-简单数据的加密解密

第5节: wasm实例开发005-图像高斯模糊

第6节: wasm实例开发006-证件照换底色

第7节: wasm实例开发007-图片菱形渐变

  • 支付宝二维码 支付宝
  • 微信二维码 微信

本文地址: /js-wasm-mul-matrix-with-c.html

版权声明: 本文为原创文章,版权归 逐梦个人博客 所有,欢迎分享本文,转载请保留出处!

相关文章