首頁 > 軟體

wasm+js實現檔案獲取md5範例詳解

2022-08-09 18:01:01

引言

在過去的幾年裡,wasm的話題那真是從早上聊到晚上,可以說處於異常興奮的狀態,但是幾年過去了,它慢慢的被大多數人們忘記,原因比較簡單——落地難

今天就wasm能給js加多少分這個問題,做一個小型的討論,今天的專注點是,前端js獲取一個檔案的md5值,也就是上傳檔案時所需要的秒傳功能的核心

簡單來說,檔案上傳秒傳不僅僅是網路硬碟公司的專屬,平時我們上傳檔案給後端也是很常用的,前端通過對目標檔案md5計算後與後端進行對比,如果已經上傳過,則直接返回已有地址,這樣,大大節省了伺服器空間。基本思路如下:

  • 前端input type="file"獲取檔案
  • 通過md5工具庫進行計算,得到md5值
  • 請求介面,後端判斷此md5是否已經在資料庫裡
  • 如果在資料庫裡,則直接告訴前端,已存在(秒傳)

本文重點

今天的重點是如何快速獲取一個檔案的md5值,這裡就涉及到小檔案,大檔案的問題了。所以,我將以下面檔案體積為例來測試js與wasm對檔案md5計算的速度對比。

wasm我使用golang進行開發,因為golang打包成wasm會把執行時也加進去,所以,打包的結果2.2M,我們暫時忽略這個體積,因為如果能落地,那麼換成rust,換成c++都不是難事,如果不能落地,那麼,golang不行,c++也照樣不行。

準備工作

通過ffmeg 從一個2G+的檔案上擷取不同體積的檔案,用於測試。

ffmpeg -i /path/sourch.mp4  -fs 1M -c:v copy -c:a copy /path/1M.mp4
ffmpeg -i /path/sourch.mp4  -fs 5M -c:v copy -c:a copy /path/5M.mp4
ffmpeg -i /path/sourch.mp4  -fs 20M -c:v copy -c:a copy /path/20M.mp4
ffmpeg -i /path/sourch.mp4  -fs 50M -c:v copy -c:a copy /path/50M.mp4
ffmpeg -i /path/sourch.mp4  -fs 100M -c:v copy -c:a copy /path/100M.mp4
ffmpeg -i /path/sourch.mp4  -fs 200M -c:v copy -c:a copy /path/200M.mp4
ffmpeg -i /path/sourch.mp4  -fs 400M -c:v copy -c:a copy /path/400M.mp4
ffmpeg -i /path/sourch.mp4  -fs 600M -c:v copy -c:a copy /path/500M.mp4
ffmpeg -i /path/sourch.mp4  -fs 800M -c:v copy -c:a copy /path/800M.mp4
ffmpeg -i /path/sourch.mp4  -fs 900M -c:v copy -c:a copy /path/900M.mp4
ffmpeg -i /path/sourch.mp4  -fs 1024M -c:v copy -c:a copy /path/1024M.mp4
ffmpeg -i /path/sourch.mp4  -fs 1280M -c:v copy -c:a copy /path/1280M.mp4
ffmpeg -i /path/sourch.mp4  -fs 1536M -c:v copy -c:a copy /path/1536M.mp4
ffmpeg -i /path/sourch.mp4  -fs 1792M -c:v copy -c:a copy /path/1792M.mp4
ffmpeg -i /path/sourch.mp4  -fs 2048M -c:v copy -c:a copy /path/2048M.mp4

測試程式碼

純js測試程式碼

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>檔案md5</title>
  <script src="./SparkMD5.js"></script>
</head>
<body>
  <input id="file" type="file" />
  <script>
    document.querySelector('#file').addEventListener('change', e => {
      let startTime = Date.now()
      const file = e.target.files[0];
      const fileReader = new FileReader()
      console.log('size', file.size / 1024 / 1024 / 1024, "G")
      fileReader.onprogress = e => {
        console.log(`${Math.floor((e.loaded / e.total) * 100)}%`)
      }
      let usedTime = 0
      const md5 = new SparkMD5();
      fileReader.readAsBinaryString(file);
      fileReader.onload = e => {
        md5.appendBinary(e.target.result);
        const md5Str = md5.end()
        usedTime += Date.now() - startTime
        console.log('usedTime', usedTime, 'ms')
        console.log('md5', md5Str)
      }
    });
  </script>
</body>
</html>

wasm(go)原始碼

請參考:

github.com/butoften/wa…

js+wasm測試程式碼

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>檔案md5</title>
  <script src="./wasm_exec.js"></script>
</head>
<body>
  <script>
    function handleSayHello(message) {
      console.lof('str from go', message)
    }
    const go = new Go();
    WebAssembly.instantiateStreaming(fetch('wasm/md5.wasm'), go.importObject)
      .then(res => {
        go.run(res.instance);
      });
  </script>
  <input id="file" type="file" />
  <script>
    document.querySelector('#file').addEventListener('change', e => {
      let startTime = Date.now()
      const file = e.target.files[0];
      const fileReader = new FileReader()
      console.log('size', file.size / 1024 / 1024 / 1024, "G")
      fileReader.onprogress = e => {
        console.log(`${Math.floor((e.loaded / e.total) * 100)}%`)
      }
      let usedTime = 0
      fileReader.readAsArrayBuffer(file);
      fileReader.onload = e => {
        const bytes = new Uint8Array(e.target.result)
        wasmMd5Add(bytes)
        const md5Hash = wasmMd5End()
        usedTime += Date.now() - startTime
        console.log('usedTime', usedTime, 'ms')
        console.log('md5', md5Hash)
      }
    });
  </script>
</body>
</html>

測試條件

  • 從FileReader開始讀取算起到md5計算結束,因為現實中,我們需要做loading條動畫比例
  • mac 2.7 GHz 雙核Intel Core i5
  • mac 8 GB 1867 MHz DDR3

測試目標

chrome (版本:103.0.5060.114)

  • 2048M 測試5次分別用時:
  • 如果分段計算,每段使用512M
序號純js純js分段js+wasmjs+wasm分段
137477 ms25638 ms31680 ms22898 ms
232926 ms28088 ms32516 ms25168 ms
333413 ms31412 ms33424 ms20547 ms
435054 ms35821 ms33906 ms23130 ms
535986 ms36895 ms29014 ms22011 ms
  • 1792M 測試5次分別用時:
序號純js純js分段js+wasmjs+wasm分段
116298 ms19441 ms27322 ms19233 ms
211593 ms29424 ms28955 ms18602 ms
324589 ms28685 ms28192 ms18472 ms
424725 ms29892 ms28931 ms18260 ms
524695 ms31453 ms36166 ms19474 ms
  • 1536M 測試5次分別用時:
序號純js純js分段js+wasmjs+wasm分段
119856 ms19591 ms21259 ms15920 ms
215119 ms26283 ms20821 ms15634 ms
321387 ms25861 ms22473 ms16893 ms
419550 ms25797 ms21793 ms17239 ms
520363 ms26402 ms20782 ms15786 ms
  • 1280M 測試5次分別用時:
序號純js純js分段js+wasmjs+wasm分段
16449 ms12169 ms22856 ms16621 ms
214695 ms17558 ms19147 ms18014 ms
317792 ms20326 ms17203 ms14683 ms
418094 ms16452 ms18396 ms14399 ms
515830 ms19006 ms19241 ms14119 ms
  • 1024M 測試5次分別用時:
序號純js純js分段js+wasmjs+wasm分段
15003 ms9441 ms16233 ms9252 ms
26240 ms14917 ms11145 ms9316 ms
38563 ms10849 ms12653 ms10963 ms
410261 ms12155 ms11607 ms9108 ms
58775 ms11138 ms9869 ms10451 ms
  • 900M 測試5次分別用時:
序號純js純js分段js+wasmjs+wasm分段
14632 ms7721 ms9590 ms7887 ms
25858 ms3312 ms7161 ms7963 ms
32859 ms10808 ms7646 ms7973 ms
43531 ms8614 ms7904 ms8197 ms
55744 ms7612 ms7131 ms10714 ms
  • 800M 測試5次分別用時:
序號純js純js分段js+wasmjs+wasm分段
13329 ms5884 ms9318 ms7270 ms
27222 ms9917 ms6897 ms7096 ms
32602 ms6066 ms6295 ms6908 ms
42757 ms6662 ms6551 ms8164 ms
52509 ms8730 ms7126 ms7039 ms
  • 600M 測試5次分別用時:
序號純js純js分段js+wasmjs+wasm分段
12721 ms2824 ms6557 ms5019 ms
23241 ms6867 ms4943 ms5026 ms
31803 ms3012 ms4902 ms5052 ms
41930 ms3010 ms5007 ms5022 ms
51807 ms2885 ms4881 ms5238 ms
  • 400M 測試5次分別用時:
序號純jsjs+wasm
16406 ms3358 ms
26435 ms3599 ms
36450 ms3283 ms
46286 ms3952 ms
56408 ms3207 ms
  • 200M 測試5次分別用時:
序號純jsjs+wasm
13497 ms1705 ms
23412 ms1643 ms
33263 ms1825 ms
43284 ms1710 ms
53376 ms1768 ms
  • 100M 測試5次分別用時:
序號純jsjs+wasm
11873 ms923 ms
21776 ms928 ms
31772 ms913 ms
41682 ms923 ms
51742 ms898 ms
  • 50M 測試5次分別用時:
序號純jsjs+wasm
11043 ms516 ms
2877 ms479 ms
3907 ms504 ms
4872 ms459 ms
5865 ms495 ms
  • 20M 測試5次分別用時:
序號純jsjs+wasm
1487 ms209 ms
2387 ms209 ms
3410 ms225 ms
4512 ms268 ms
5399 ms225 ms
  • 5M 測試10次分別用時:
序號純jsjs+wasm
1147 ms92 ms
2133 ms90 ms
3177 ms94 ms
4157 ms42 ms
5175 ms84 ms
  • 1M 測試5次分別用時:
序號純jsjs+wasm
171 ms20 ms
266 ms24 ms
345 ms33 ms
480 ms30 ms
597 ms29 ms

firefox (版本號:103.0.1 (64 位))

  • 2048M 載入到52%時頁面崩潰
    • 採用Blob.slice方式分段計算
    • 每512M為一段,測試5次
序號純js分段js+wasm分段
151398 ms17338 ms
241282 ms16385 ms
342358 ms16966 ms
443363 ms15843 ms
540802 ms16551 ms
  • 1792M 載入到59%時頁面崩潰
    • 採用Blob.slice方式分段計算
    • 每512M為一段,測試5次
序號純js分段js+wasm分段
133690 ms13251 ms
237423 ms13636 ms
342903 ms13487 ms
432684 ms13662 ms
536691 ms14984 ms
  • 1536M 載入到69%時頁面崩潰
    • 採用Blob.slice方式分段計算
    • 每512M為一段,測試5次
序號純js分段js+wasm分段
128051 ms11425 ms
227822 ms11337 ms
328331 ms12508 ms
430089 ms11520 ms
532890 ms11507 ms
  • 1280M 載入到83%時頁面崩潰
    • 採用Blob.slice方式分段
    • 計算512M為一段
序號純js分段js+wasm分段
125680 ms9571 ms
223956 ms9549 ms
328829 ms10070 ms
423518 ms9449 ms
523200 ms9540 ms
  • 1024M 測試10次分別用時:
序號純jsjs+wasm
138277 ms7776 ms
240936 ms11254 ms
329861 ms7653 ms
425630 ms7517 ms
518934 ms11443 ms
624849 ms8039 ms
718214 ms7727 ms
818617 ms12987 ms
933281 ms7523 ms
1040757 ms8895 ms
  • 900M 測試10次分別用時:
序號純jsjs+wasm
122752 ms8605 ms
216669 ms9313 ms
315716 ms6678 ms
416940 ms6521 ms
516732 ms9269 ms
615805 ms6582 ms
715718 ms6519 ms
815795 ms9377 ms
915641 ms6773 ms
1015622 ms7489 ms
  • 800M 測試10次分別用時:
序號純jsjs+wasm
115181 ms8333 ms
214031 ms5880 ms
314214 ms5987 ms
433812 ms5935 ms
514167 ms8666 ms
614666 ms8031 ms
728640 ms5991 ms
813992 ms5840 ms
913926 ms6032 ms
1014216 ms6637 ms
  • 600M 測試10次分別用時:
序號純jsjs+wasm
111418 ms4457 ms
211199 ms5370 ms
310717 ms4654 ms
410607 ms4436 ms
510611 ms4479 ms
610718 ms4368 ms
710560 ms5494 ms
811519 ms5044 ms
910802 ms4426 ms
1011779 ms4971 ms
  • 400M 測試10次分別用時:
序號純jsjs+wasm
18362 ms2981 ms
27516 ms2999 ms
37335 ms3030 ms
47357 ms3150 ms
57444 ms3001 ms
68456 ms3223 ms
77376 ms3120 ms
87313 ms3072 ms
97349 ms3240 ms
107447 ms3352 ms
  • 200M 測試10次分別用時:
序號純jsjs+wasm
14066 ms1525 ms
24440 ms1516 ms
34223 ms1510 ms
43916 ms1610 ms
53917 ms1509 ms
64028 ms1588 ms
73964 ms1514 ms
84037 ms1507 ms
93957 ms1506 ms
103987 ms1642 ms
  • 100M 測試10次分別用時:
序號純jsjs+wasm
12280 ms761 ms
22331 ms820 ms
32193 ms798 ms
42242 ms777 ms
52197 ms752 ms
62330 ms769 ms
72236 ms758 ms
82364 ms798 ms
92278 ms783 ms
102384 ms785 ms
  • 50M 測試10次分別用時:
序號純jsjs+wasm
11366 ms397 ms
21355 ms378 ms
31445 ms460 ms
41468 ms437 ms
51417 ms406 ms
61525 ms478 ms
71381 ms393 ms
81450 ms430 ms
91417 ms428 ms
101378 ms431 ms
  • 20M 測試10次分別用時:
序號純jsjs+wasm
1921 ms168 ms
2871 ms162 ms
3859 ms163 ms
4864 ms162 ms
51025 ms177 ms
6910 ms158 ms
7904 ms150 ms
8931 ms187 ms
91014 ms182 ms
10871 ms159 ms
  • 5M 測試10次分別用時:
序號純jsjs+wasm
1127 ms48 ms
2124 ms50 ms
3140 ms44 ms
4129 ms47 ms
5127 ms51 ms
6129 ms50 ms
7126 ms46 ms
8119 ms54 ms
9121 ms46 ms
10118 ms50 ms
  • 1M 測試10次分別用時:
序號純jsjs+wasm
146 ms18 ms
241 ms22 ms
343 ms13 ms
440 ms15 ms
544 ms11 ms
647 ms15 ms
742 ms11 ms
842 ms20 ms
945 ms13 ms
1044 ms16 ms

分段計算測試程式碼

純js

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>檔案md5</title>
  <script src="./SparkMD5.js"></script>
</head>
<body>
  <input id="file" type="file" />
  <script>
  document.querySelector('#file').addEventListener('change', e => {
      let startTime = Date.now()
      const file = e.target.files[0];
      const fileReader = new FileReader()
      console.log('size', file.size / 1024 / 1024 / 1024, "G")
      fileReader.onprogress = e => {
        console.log(`${Math.floor((e.loaded / e.total) * 100)}%`)
      }
      let usedTime = 0
      const md5 = new SparkMD5();
      let index = 0
      const chunkSize = 512 * 1024 * 1024;//file.size / count
      let count = Math.ceil(file.size / chunkSize)
      console.log('分幾份', count)
      loadSliceFile();
      function loadSliceFile() {
        const sliceFile = file.slice(index * chunkSize, index * chunkSize + chunkSize)
        fileReader.readAsBinaryString(sliceFile);
      }
      fileReader.onload = e => {
        index += 1;
        md5.appendBinary(e.target.result);
        if (index < count) {
          loadSliceFile()
        }
        else {
          const md5Str = md5.end()
          usedTime += Date.now() - startTime
          console.log('usedTime', usedTime, 'ms')
          console.log('md5', md5Str)
        }
      }
    });
  </script>
</body>
</html>

js+wasm

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>檔案md5</title>
  <script src="./wasm_exec.js"></script>
  <!-- <script src="./wasm_exec_tiny.js"></script> -->
</head>
<body>
  <script>
    function handleSayHello(message) {
      console.lof('str from go', message)
    }
    const go = new Go();
    WebAssembly.instantiateStreaming(fetch('wasm/md5.wasm'), go.importObject)
      .then(res => {
        go.run(res.instance); // 執行 golang裡 main 方法
      });
  </script>
  <input id="file" type="file" />
  <script>
    document.querySelector('#file').addEventListener('change', e => {
      let startTime = Date.now()
      const file = e.target.files[0];
      const fileReader = new FileReader()
      console.log('size', file.size / 1024 / 1024 / 1024, "G")
      fileReader.onprogress = e => {
        console.log(`${Math.floor((e.loaded / e.total) * 100)}%`)
      }
      let usedTime = 0
      let index = 0
      const sliceSize = 512
      const chunkSize = sliceSize * 1024 * 1024;//file.size / count
      let count = Math.ceil(file.size / chunkSize)
      console.log('分幾份', count)
      loadSliceFile();
      function loadSliceFile() {
        const sliceFile = file.slice(index * chunkSize, index * chunkSize + chunkSize)
        fileReader.readAsArrayBuffer(sliceFile);
      }
      fileReader.onload = e => {
        index += 1;
        const bytes = new Uint8Array(e.target.result)
        wasmMd5Add(bytes)
        if (index < count) {
          loadSliceFile()
        }
        else {
          const md5Hash = wasmMd5End()
          usedTime += Date.now() - startTime
          console.log('usedTime', usedTime, 'ms')
          console.log('md5', md5Hash)
        }
      }
    });
  </script>
</body>
</html>

測試結論

firefox

  • 超過1G的檔案,直接崩潰,只能通過分段計算最終合併計算
  • 從1M到2G,wasm的速度是純js計算的2-3倍
  • 20M,wasm是純js的 6倍

chrome

  • 0-400M時,wasm是純js的2-3倍
  • 600M-1024M時,純js不分段比wasm要快
    • 分段js比不分段wasm快一點點
    • 分段js比分段wasm慢一點點
  • 1280M,差不太多
  • 大於1280M,js比wasm分段慢
  • 對於js,分段要慢一些
  • 對於wasm,分段要快一些

最終結論

  • chrome對js的優化,使得在600M-1024M期間的大檔案純js計算md5速度要快於wasm,其他範圍還是wasm效能好一些
  • 由於firefox超過1G就崩潰了,所以我們平時寫程式碼時,還是要做分段載入的。
  • 業務中,還是可以使用wasm來提升效能的
  • 可以針對 chrome與其他瀏覽器來製作不同的方案
  • 其實golang 計算md5基本上是js的7-9倍,但js給wasm複製資料的時間佔用了太多,導致wasm被降低了速度,檔案越大,複製時間越長,越慢

wasm 還是可以使用的,眾觀全域性,速度提升2-3倍。chrome可以針對性處理

以上就是wasm+js實現檔案獲取md5範例詳解的詳細內容,更多關於wasm js獲取md5的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com