亚欧色一区w666天堂,色情一区二区三区免费看,少妇特黄A片一区二区三区,亚洲人成网站999久久久综合,国产av熟女一区二区三区

  • 發布文章
  • 消息中心
點贊
收藏
評論
分享
原創

前端如何實現數據零拷貝zero-copy?

2023-07-30 23:54:31
90
0

最近在做大數據表格的一些計算性能優化的工作,一、想采用wasm來提升計算性能,聽說js call wasm函數的代價很高,很難做函數級優化。二、采用web worker進行數據分片處理,需要將數據序列化copy到worker,在copy回來,開銷很大,難以接受,故此做了一番調研,分享一下如何在前端實現數據交換的零拷貝

 

一、數據分片

最簡單的方案,就是把這大量的數據進行分片異步處理,處理完一段數據之后,釋放主線程,讓ui渲染進程有機會能夠執行,在回頭繼續處理第二段數據,直到所有分片執行完成,更新最終UI側數據

二、將數據直接發送給web worker執行

// 以下為偽代碼,了解思想即可
/*
* main.js
*前端fetch到數據之后,直接發送給worker進行計算
*/
const bigData = [] // 500M的數據
const worker = new Worker("./worker.js");
worker.postMessage(bigData)

- 如果直接發送這份數據給到worker,postMessage會對這份數據做一次拷貝,那么瀏覽器就存儲了1G數據,
在拷貝的過程中還會對數據進行序列化,在worker中,還需要對數據反序列化,無疑增加了很多成本

/*
* worker.js
*/
self.onmessage = funcion({ data }){
    // calcData 進行復雜的計算邏輯
}
/*

*/

三、web worker優化方案

有沒有辦法,能夠把發送給worker的這份數據,不要拷貝,而是指針(地址)的方式,worker直接進行計算,然后返回給我們一個標記,我們直接還是調用之前的那份數據呢?是有辦法的
worker.postMessage(aMessage, transferList)

這個transferList是一個可選數組,用于傳遞所有權,什么意思呢,就是發送給worker之前,主線程是可以使用當前這份數據的,當一旦發送給worker之后,就失去了對這份數據的控制權,也就無法進行遍歷或者進行分片等操作了,只有等worker處理好之后,在將這份數據發送回來,如果worker異常或者沒有發送回來,那么這份數據就將會丟失掉。

好吧,那我就是想用,做好容災就行。還有一個局限性,我們傳遞的這份數據是要實現Transferable接口的,目前前端實現這個接口的數據結構有三個,ArrayBuffer,MessagePort,ImageBitmap,那我們就拿ArrayBuffer來舉例吧(只對它有所了解)

那看來,如果我們要使用ArrayBuffer,就要對數據進行約束了,因為二進制數組存放的都是二進制數據,并且支持的類型有以下幾種
這里插入圖片

main.js
const bigData = [] // 500M的數據
const worker = new Worker("./worker.js");
/*
把原始數據結構中我們所需要的數據提取出來
*/

worker.postMessage(bigData)

/*
* worker.js
*/
self.onmessage = function({ data }){
    const { buffer } = data;
    // 如果要操作這份數據,在創建一個view視圖,具體typedArray的操作api,可以查看MDN文檔
    // 創建視圖,簡單來說,就是去觀察二進制數組buffer中的數據,并不是實例化拷貝一份數據出來,buffer中的數據一個黑盒,不能直接進行操作,要先定義視圖并且賦予期數據類型,才可進行操作
    const view = new Int32Array(buffer)
    // 對view進行復雜操作,返回給主線程進行讀取
    self.postMessage(view,[view.buffer]);
}

四、共享內存sharedArrayBuffer

上面的方案,我們發現一個問題,就是數據控制權的問題,以及雖然沒有發生數據拷貝(相當于剪切),耗用的內存是不變的,更進一步來說,能不能直接把內存地址給到worker,woker直接去操作這份數據呢?

const worker = new Worker("./worker.js");
// 實例化共享內存
const buffer = new SharedArrayBuffer(arr.legnth * Int32Array.BYTES_PER_ELEMENT);
worker.postMessage({ buffer });
worker.onmessage = function () {
    const view = new Int32Array(buffer);
    console.log(buffer, view);
}

/*
注意:因為SharedArrayBuffer存在安全漏洞的原因,在2018年被叫停了,目前只能在chrome和Mozilla中開啟一定的安全策略下使用,可以先了解,未來可期
開啟SharedArrayBuffer的兩種方法
1.開啟 Cross-Origin-Opener-Policy: same-origin
2.在命令窗口,啟動chrome --enable-features=SharedArrayBuffer,僅用于調試
3.在//developer.chrome.com/origintrials/#/registration,注冊,返回一個臨時token,可用于調試
*/

這樣就已經實現了在worker直接分發數據零拷貝,每個不同weorker直接將接受到的數據,進行處理,修改buffer,處理完成之后,告訴主進程已完成done,主進程進行ui更新渲染即可,不過目前SharedArrayBuffe不能用于生產環境。

五、webassembly方案

整體實現思路:

1.調用webAssembly的方法,在webAssembly內存中分配空間,返回指針

2.JS端在webAssembly的memory內存中申請一段arrayBuffer,根據指針位置和數據量建立view視圖,并且把數據寫入arrayBuffer中

3.調用webAssembly方法完成計算(將步驟一中申請的內存指針傳入,再返回計算完成的批量結果的指針位置和大小

4.JS端在webAssembly的memory arraybuffer上,按指針位置和數據量建立view,把數據讀出,進行UI渲染

以下是代碼的具體實現

#include <emscripten.h>
#include <iostream>
#include <algorithm>
#include <time.h>
using namespace std;
#ifdef __cplusplus
extern "C"
{ // C++ compiler 不會優化函數名,導出到js中,能夠按照原始名稱導出
#endif
   EMSCRIPTEN_KEEPALIVE int main()
   {
       return 100;
   }

   EMSCRIPTEN_KEEPALIVE void sortArr(int *ptr)
   {
       sort(ptr, ptr + 9);
   }
   EMSCRIPTEN_KEEPALIVE void randomArr(int *ptr)
   {
       srand((unsigned)time(NULL));
       for (int i = 0; i < 9; i++)
       {
           ptr[i] = rand();
       }
   }
   // 這里進行內存申請,其實調用的是wasm memory的線性內存申請
   EMSCRIPTEN_KEEPALIVE int *getArrPointer(int capacity)
   {
       int *prt = (int *)malloc(sizeof(int) * capacity);
       cout << "c++申請的內存地址為:" << prt << endl;
       return prt;
   }
#ifdef __cplusplus
}
#endif

使用emcc編譯工具進行編譯,得到一個js膠水文件,和一個.wasm文件

// 加載膠水文件,會得到一個Module對象,具體api細節,可網上查閱
const self = this;
Module.onRuntimeInitialized = function () { // 初始化之后
    const bigData = [];
    // 申請內存,返回指針
    const dataPointer = Module._getArrPointer(bigData.length);
    // 從memory中讀取數據塊,注意,這里一定要寫偏移量dataPointer
    const dataview = new Int32Array(Module['asm']['memory'].buffer, dataPointer, bigData.length)
    for (let i = 0; i < bigData.length; i++) {
        dataview[i] = bigData[i];
    }
    // c++業務邏輯處理
    Module._sortArr(dataPointer);
    // 處理完成新建一個view讀取,返回給UI進行渲染
}

六、其他調研以及一些想法

1.用sharedArrayBuffer初始化個wasm實例,用worker進行初始化多個wasm實例,共享一片內存

#include <stdlib.h>
#include <emscripten.h>

#ifdef __cplusplus
extern "C"
{ // So that the C++ compiler does not rename the functions below
#endif

    EMSCRIPTEN_KEEPALIVE int *getArrPointer(int capacity)
    {
        int *prt = (int *)malloc(sizeof(int) * capacity);
        prt[0] = 10;
        return prt;
    }

#ifdef __cplusplus
}
#endif

實例化wasm
const memory = 
new WebAssembly.Memory({
    initial: 80,
    maximum: 80,
    shared: true
});

// 編譯階段,采用emcc change1.cpp -O3 -o change1.wasm  --no-entry -Wl,--import-memory,
 * @param {*} e 
 * 1、加載wasm文件
 * 2、使用主進程傳入的memory對象,進行初始化wasm,調用c++函數,申請內存,返回一個指針
 * 3、
 */
self.onmessage = function ({ data }) {
    const { memory } = data;
    // 使用這個meory進行wasm的初始化,共享這片內存
    console.log("worker 執行了", memory)
    fetch("change1.wasm").then(response =>
        response.arrayBuffer()
    ).then(bytes => {
        return WebAssembly.instantiate(bytes, {
            env: {
                '__table_base': 0,
                'memory': memory,
                '__memory_base': 1024,
                'STACKTOP': 0,
                'STACK_MAX': memory.buffer.byteLength,
            }
        })
    }
    ).then(({ instance }) => {
        console.log("實例化后的wasm對象", instance);
        console.log("傳入對象的memory", memory)
        // const dataPointer = instance.exports.getArrPointer(10);
        // console.log("worker1中的申請的內存為:", dataPointer);
        // self.postMessage(dataPointer)
    });
}

上述方案其實比較完美,不過遇到了一個問題:

LinkError: WebAssembly.instantiate(): mismatch in shared state of memory, declared = 0, imported = 1

好像提示是使用了兩個不同內存實例,目前還比較困惑,沒找到合理的解釋,有進展后續更新~

以上是做的一些調研,下面總結一下

1、如果非必要的情況,不建議使用wasm這種解決方案,可以簡單用web worker實現,市面上很多都是一些簡單的理論,實踐起來坑比較多,成本也很高。

2、如果使用webassembly,那么使用方案五是目前我認為比較好的實踐方案,方案六可能是未來的最優解

0條評論
作者已關閉評論
張****偉
7文章數
0粉絲數
張****偉
7 文章 | 0 粉絲
原創

前端如何實現數據零拷貝zero-copy?

2023-07-30 23:54:31
90
0

最近在做大數據表格的一些計算性能優化的工作,一、想采用wasm來提升計算性能,聽說js call wasm函數的代價很高,很難做函數級優化。二、采用web worker進行數據分片處理,需要將數據序列化copy到worker,在copy回來,開銷很大,難以接受,故此做了一番調研,分享一下如何在前端實現數據交換的零拷貝

 

一、數據分片

最簡單的方案,就是把這大量的數據進行分片異步處理,處理完一段數據之后,釋放主線程,讓ui渲染進程有機會能夠執行,在回頭繼續處理第二段數據,直到所有分片執行完成,更新最終UI側數據

二、將數據直接發送給web worker執行

// 以下為偽代碼,了解思想即可
/*
* main.js
*前端fetch到數據之后,直接發送給worker進行計算
*/
const bigData = [] // 500M的數據
const worker = new Worker("./worker.js");
worker.postMessage(bigData)

- 如果直接發送這份數據給到worker,postMessage會對這份數據做一次拷貝,那么瀏覽器就存儲了1G數據,
在拷貝的過程中還會對數據進行序列化,在worker中,還需要對數據反序列化,無疑增加了很多成本

/*
* worker.js
*/
self.onmessage = funcion({ data }){
    // calcData 進行復雜的計算邏輯
}
/*

*/

三、web worker優化方案

有沒有辦法,能夠把發送給worker的這份數據,不要拷貝,而是指針(地址)的方式,worker直接進行計算,然后返回給我們一個標記,我們直接還是調用之前的那份數據呢?是有辦法的
worker.postMessage(aMessage, transferList)

這個transferList是一個可選數組,用于傳遞所有權,什么意思呢,就是發送給worker之前,主線程是可以使用當前這份數據的,當一旦發送給worker之后,就失去了對這份數據的控制權,也就無法進行遍歷或者進行分片等操作了,只有等worker處理好之后,在將這份數據發送回來,如果worker異常或者沒有發送回來,那么這份數據就將會丟失掉。

好吧,那我就是想用,做好容災就行。還有一個局限性,我們傳遞的這份數據是要實現Transferable接口的,目前前端實現這個接口的數據結構有三個,ArrayBuffer,MessagePort,ImageBitmap,那我們就拿ArrayBuffer來舉例吧(只對它有所了解)

那看來,如果我們要使用ArrayBuffer,就要對數據進行約束了,因為二進制數組存放的都是二進制數據,并且支持的類型有以下幾種
這里插入圖片

main.js
const bigData = [] // 500M的數據
const worker = new Worker("./worker.js");
/*
把原始數據結構中我們所需要的數據提取出來
*/

worker.postMessage(bigData)

/*
* worker.js
*/
self.onmessage = function({ data }){
    const { buffer } = data;
    // 如果要操作這份數據,在創建一個view視圖,具體typedArray的操作api,可以查看MDN文檔
    // 創建視圖,簡單來說,就是去觀察二進制數組buffer中的數據,并不是實例化拷貝一份數據出來,buffer中的數據一個黑盒,不能直接進行操作,要先定義視圖并且賦予期數據類型,才可進行操作
    const view = new Int32Array(buffer)
    // 對view進行復雜操作,返回給主線程進行讀取
    self.postMessage(view,[view.buffer]);
}

四、共享內存sharedArrayBuffer

上面的方案,我們發現一個問題,就是數據控制權的問題,以及雖然沒有發生數據拷貝(相當于剪切),耗用的內存是不變的,更進一步來說,能不能直接把內存地址給到worker,woker直接去操作這份數據呢?

const worker = new Worker("./worker.js");
// 實例化共享內存
const buffer = new SharedArrayBuffer(arr.legnth * Int32Array.BYTES_PER_ELEMENT);
worker.postMessage({ buffer });
worker.onmessage = function () {
    const view = new Int32Array(buffer);
    console.log(buffer, view);
}

/*
注意:因為SharedArrayBuffer存在安全漏洞的原因,在2018年被叫停了,目前只能在chrome和Mozilla中開啟一定的安全策略下使用,可以先了解,未來可期
開啟SharedArrayBuffer的兩種方法
1.開啟 Cross-Origin-Opener-Policy: same-origin
2.在命令窗口,啟動chrome --enable-features=SharedArrayBuffer,僅用于調試
3.在//developer.chrome.com/origintrials/#/registration,注冊,返回一個臨時token,可用于調試
*/

這樣就已經實現了在worker直接分發數據零拷貝,每個不同weorker直接將接受到的數據,進行處理,修改buffer,處理完成之后,告訴主進程已完成done,主進程進行ui更新渲染即可,不過目前SharedArrayBuffe不能用于生產環境。

五、webassembly方案

整體實現思路:

1.調用webAssembly的方法,在webAssembly內存中分配空間,返回指針

2.JS端在webAssembly的memory內存中申請一段arrayBuffer,根據指針位置和數據量建立view視圖,并且把數據寫入arrayBuffer中

3.調用webAssembly方法完成計算(將步驟一中申請的內存指針傳入,再返回計算完成的批量結果的指針位置和大小

4.JS端在webAssembly的memory arraybuffer上,按指針位置和數據量建立view,把數據讀出,進行UI渲染

以下是代碼的具體實現

#include <emscripten.h>
#include <iostream>
#include <algorithm>
#include <time.h>
using namespace std;
#ifdef __cplusplus
extern "C"
{ // C++ compiler 不會優化函數名,導出到js中,能夠按照原始名稱導出
#endif
   EMSCRIPTEN_KEEPALIVE int main()
   {
       return 100;
   }

   EMSCRIPTEN_KEEPALIVE void sortArr(int *ptr)
   {
       sort(ptr, ptr + 9);
   }
   EMSCRIPTEN_KEEPALIVE void randomArr(int *ptr)
   {
       srand((unsigned)time(NULL));
       for (int i = 0; i < 9; i++)
       {
           ptr[i] = rand();
       }
   }
   // 這里進行內存申請,其實調用的是wasm memory的線性內存申請
   EMSCRIPTEN_KEEPALIVE int *getArrPointer(int capacity)
   {
       int *prt = (int *)malloc(sizeof(int) * capacity);
       cout << "c++申請的內存地址為:" << prt << endl;
       return prt;
   }
#ifdef __cplusplus
}
#endif

使用emcc編譯工具進行編譯,得到一個js膠水文件,和一個.wasm文件

// 加載膠水文件,會得到一個Module對象,具體api細節,可網上查閱
const self = this;
Module.onRuntimeInitialized = function () { // 初始化之后
    const bigData = [];
    // 申請內存,返回指針
    const dataPointer = Module._getArrPointer(bigData.length);
    // 從memory中讀取數據塊,注意,這里一定要寫偏移量dataPointer
    const dataview = new Int32Array(Module['asm']['memory'].buffer, dataPointer, bigData.length)
    for (let i = 0; i < bigData.length; i++) {
        dataview[i] = bigData[i];
    }
    // c++業務邏輯處理
    Module._sortArr(dataPointer);
    // 處理完成新建一個view讀取,返回給UI進行渲染
}

六、其他調研以及一些想法

1.用sharedArrayBuffer初始化個wasm實例,用worker進行初始化多個wasm實例,共享一片內存

#include <stdlib.h>
#include <emscripten.h>

#ifdef __cplusplus
extern "C"
{ // So that the C++ compiler does not rename the functions below
#endif

    EMSCRIPTEN_KEEPALIVE int *getArrPointer(int capacity)
    {
        int *prt = (int *)malloc(sizeof(int) * capacity);
        prt[0] = 10;
        return prt;
    }

#ifdef __cplusplus
}
#endif

實例化wasm
const memory = 
new WebAssembly.Memory({
    initial: 80,
    maximum: 80,
    shared: true
});

// 編譯階段,采用emcc change1.cpp -O3 -o change1.wasm  --no-entry -Wl,--import-memory,
 * @param {*} e 
 * 1、加載wasm文件
 * 2、使用主進程傳入的memory對象,進行初始化wasm,調用c++函數,申請內存,返回一個指針
 * 3、
 */
self.onmessage = function ({ data }) {
    const { memory } = data;
    // 使用這個meory進行wasm的初始化,共享這片內存
    console.log("worker 執行了", memory)
    fetch("change1.wasm").then(response =>
        response.arrayBuffer()
    ).then(bytes => {
        return WebAssembly.instantiate(bytes, {
            env: {
                '__table_base': 0,
                'memory': memory,
                '__memory_base': 1024,
                'STACKTOP': 0,
                'STACK_MAX': memory.buffer.byteLength,
            }
        })
    }
    ).then(({ instance }) => {
        console.log("實例化后的wasm對象", instance);
        console.log("傳入對象的memory", memory)
        // const dataPointer = instance.exports.getArrPointer(10);
        // console.log("worker1中的申請的內存為:", dataPointer);
        // self.postMessage(dataPointer)
    });
}

上述方案其實比較完美,不過遇到了一個問題:

LinkError: WebAssembly.instantiate(): mismatch in shared state of memory, declared = 0, imported = 1

好像提示是使用了兩個不同內存實例,目前還比較困惑,沒找到合理的解釋,有進展后續更新~

以上是做的一些調研,下面總結一下

1、如果非必要的情況,不建議使用wasm這種解決方案,可以簡單用web worker實現,市面上很多都是一些簡單的理論,實踐起來坑比較多,成本也很高。

2、如果使用webassembly,那么使用方案五是目前我認為比較好的實踐方案,方案六可能是未來的最優解

文章來自個人專欄
文章 | 訂閱
0條評論
作者已關閉評論
作者已關閉評論
0
0