wasm+js實現文件獲取md5示例詳解

引言

在過去的幾年裡,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+wasm js+wasm分段
1 37477 ms 25638 ms 31680 ms 22898 ms
2 32926 ms 28088 ms 32516 ms 25168 ms
3 33413 ms 31412 ms 33424 ms 20547 ms
4 35054 ms 35821 ms 33906 ms 23130 ms
5 35986 ms 36895 ms 29014 ms 22011 ms
  • 1792M 測試5次分別用時:
序號 純js 純js分段 js+wasm js+wasm分段
1 16298 ms 19441 ms 27322 ms 19233 ms
2 11593 ms 29424 ms 28955 ms 18602 ms
3 24589 ms 28685 ms 28192 ms 18472 ms
4 24725 ms 29892 ms 28931 ms 18260 ms
5 24695 ms 31453 ms 36166 ms 19474 ms
  • 1536M 測試5次分別用時:
序號 純js 純js分段 js+wasm js+wasm分段
1 19856 ms 19591 ms 21259 ms 15920 ms
2 15119 ms 26283 ms 20821 ms 15634 ms
3 21387 ms 25861 ms 22473 ms 16893 ms
4 19550 ms 25797 ms 21793 ms 17239 ms
5 20363 ms 26402 ms 20782 ms 15786 ms
  • 1280M 測試5次分別用時:
序號 純js 純js分段 js+wasm js+wasm分段
1 6449 ms 12169 ms 22856 ms 16621 ms
2 14695 ms 17558 ms 19147 ms 18014 ms
3 17792 ms 20326 ms 17203 ms 14683 ms
4 18094 ms 16452 ms 18396 ms 14399 ms
5 15830 ms 19006 ms 19241 ms 14119 ms
  • 1024M 測試5次分別用時:
序號 純js 純js分段 js+wasm js+wasm分段
1 5003 ms 9441 ms 16233 ms 9252 ms
2 6240 ms 14917 ms 11145 ms 9316 ms
3 8563 ms 10849 ms 12653 ms 10963 ms
4 10261 ms 12155 ms 11607 ms 9108 ms
5 8775 ms 11138 ms 9869 ms 10451 ms
  • 900M 測試5次分別用時:
序號 純js 純js分段 js+wasm js+wasm分段
1 4632 ms 7721 ms 9590 ms 7887 ms
2 5858 ms 3312 ms 7161 ms 7963 ms
3 2859 ms 10808 ms 7646 ms 7973 ms
4 3531 ms 8614 ms 7904 ms 8197 ms
5 5744 ms 7612 ms 7131 ms 10714 ms
  • 800M 測試5次分別用時:
序號 純js 純js分段 js+wasm js+wasm分段
1 3329 ms 5884 ms 9318 ms 7270 ms
2 7222 ms 9917 ms 6897 ms 7096 ms
3 2602 ms 6066 ms 6295 ms 6908 ms
4 2757 ms 6662 ms 6551 ms 8164 ms
5 2509 ms 8730 ms 7126 ms 7039 ms
  • 600M 測試5次分別用時:
序號 純js 純js分段 js+wasm js+wasm分段
1 2721 ms 2824 ms 6557 ms 5019 ms
2 3241 ms 6867 ms 4943 ms 5026 ms
3 1803 ms 3012 ms 4902 ms 5052 ms
4 1930 ms 3010 ms 5007 ms 5022 ms
5 1807 ms 2885 ms 4881 ms 5238 ms
  • 400M 測試5次分別用時:
序號 純js js+wasm
1 6406 ms 3358 ms
2 6435 ms 3599 ms
3 6450 ms 3283 ms
4 6286 ms 3952 ms
5 6408 ms 3207 ms
  • 200M 測試5次分別用時:
序號 純js js+wasm
1 3497 ms 1705 ms
2 3412 ms 1643 ms
3 3263 ms 1825 ms
4 3284 ms 1710 ms
5 3376 ms 1768 ms
  • 100M 測試5次分別用時:
序號 純js js+wasm
1 1873 ms 923 ms
2 1776 ms 928 ms
3 1772 ms 913 ms
4 1682 ms 923 ms
5 1742 ms 898 ms
  • 50M 測試5次分別用時:
序號 純js js+wasm
1 1043 ms 516 ms
2 877 ms 479 ms
3 907 ms 504 ms
4 872 ms 459 ms
5 865 ms 495 ms
  • 20M 測試5次分別用時:
序號 純js js+wasm
1 487 ms 209 ms
2 387 ms 209 ms
3 410 ms 225 ms
4 512 ms 268 ms
5 399 ms 225 ms
  • 5M 測試10次分別用時:
序號 純js js+wasm
1 147 ms 92 ms
2 133 ms 90 ms
3 177 ms 94 ms
4 157 ms 42 ms
5 175 ms 84 ms
  • 1M 測試5次分別用時:
序號 純js js+wasm
1 71 ms 20 ms
2 66 ms 24 ms
3 45 ms 33 ms
4 80 ms 30 ms
5 97 ms 29 ms

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

  • 2048M 加載到52%時頁面崩潰
    • 采用Blob.slice方式分段計算
    • 每512M為一段,測試5次
序號 純js分段 js+wasm分段
1 51398 ms 17338 ms
2 41282 ms 16385 ms
3 42358 ms 16966 ms
4 43363 ms 15843 ms
5 40802 ms 16551 ms
  • 1792M 加載到59%時頁面崩潰
    • 采用Blob.slice方式分段計算
    • 每512M為一段,測試5次
序號 純js分段 js+wasm分段
1 33690 ms 13251 ms
2 37423 ms 13636 ms
3 42903 ms 13487 ms
4 32684 ms 13662 ms
5 36691 ms 14984 ms
  • 1536M 加載到69%時頁面崩潰
    • 采用Blob.slice方式分段計算
    • 每512M為一段,測試5次
序號 純js分段 js+wasm分段
1 28051 ms 11425 ms
2 27822 ms 11337 ms
3 28331 ms 12508 ms
4 30089 ms 11520 ms
5 32890 ms 11507 ms
  • 1280M 加載到83%時頁面崩潰
    • 采用Blob.slice方式分段
    • 計算512M為一段
序號 純js分段 js+wasm分段
1 25680 ms 9571 ms
2 23956 ms 9549 ms
3 28829 ms 10070 ms
4 23518 ms 9449 ms
5 23200 ms 9540 ms
  • 1024M 測試10次分別用時:
序號 純js js+wasm
1 38277 ms 7776 ms
2 40936 ms 11254 ms
3 29861 ms 7653 ms
4 25630 ms 7517 ms
5 18934 ms 11443 ms
6 24849 ms 8039 ms
7 18214 ms 7727 ms
8 18617 ms 12987 ms
9 33281 ms 7523 ms
10 40757 ms 8895 ms
  • 900M 測試10次分別用時:
序號 純js js+wasm
1 22752 ms 8605 ms
2 16669 ms 9313 ms
3 15716 ms 6678 ms
4 16940 ms 6521 ms
5 16732 ms 9269 ms
6 15805 ms 6582 ms
7 15718 ms 6519 ms
8 15795 ms 9377 ms
9 15641 ms 6773 ms
10 15622 ms 7489 ms
  • 800M 測試10次分別用時:
序號 純js js+wasm
1 15181 ms 8333 ms
2 14031 ms 5880 ms
3 14214 ms 5987 ms
4 33812 ms 5935 ms
5 14167 ms 8666 ms
6 14666 ms 8031 ms
7 28640 ms 5991 ms
8 13992 ms 5840 ms
9 13926 ms 6032 ms
10 14216 ms 6637 ms
  • 600M 測試10次分別用時:
序號 純js js+wasm
1 11418 ms 4457 ms
2 11199 ms 5370 ms
3 10717 ms 4654 ms
4 10607 ms 4436 ms
5 10611 ms 4479 ms
6 10718 ms 4368 ms
7 10560 ms 5494 ms
8 11519 ms 5044 ms
9 10802 ms 4426 ms
10 11779 ms 4971 ms
  • 400M 測試10次分別用時:
序號 純js js+wasm
1 8362 ms 2981 ms
2 7516 ms 2999 ms
3 7335 ms 3030 ms
4 7357 ms 3150 ms
5 7444 ms 3001 ms
6 8456 ms 3223 ms
7 7376 ms 3120 ms
8 7313 ms 3072 ms
9 7349 ms 3240 ms
10 7447 ms 3352 ms
  • 200M 測試10次分別用時:
序號 純js js+wasm
1 4066 ms 1525 ms
2 4440 ms 1516 ms
3 4223 ms 1510 ms
4 3916 ms 1610 ms
5 3917 ms 1509 ms
6 4028 ms 1588 ms
7 3964 ms 1514 ms
8 4037 ms 1507 ms
9 3957 ms 1506 ms
10 3987 ms 1642 ms
  • 100M 測試10次分別用時:
序號 純js js+wasm
1 2280 ms 761 ms
2 2331 ms 820 ms
3 2193 ms 798 ms
4 2242 ms 777 ms
5 2197 ms 752 ms
6 2330 ms 769 ms
7 2236 ms 758 ms
8 2364 ms 798 ms
9 2278 ms 783 ms
10 2384 ms 785 ms
  • 50M 測試10次分別用時:
序號 純js js+wasm
1 1366 ms 397 ms
2 1355 ms 378 ms
3 1445 ms 460 ms
4 1468 ms 437 ms
5 1417 ms 406 ms
6 1525 ms 478 ms
7 1381 ms 393 ms
8 1450 ms 430 ms
9 1417 ms 428 ms
10 1378 ms 431 ms
  • 20M 測試10次分別用時:
序號 純js js+wasm
1 921 ms 168 ms
2 871 ms 162 ms
3 859 ms 163 ms
4 864 ms 162 ms
5 1025 ms 177 ms
6 910 ms 158 ms
7 904 ms 150 ms
8 931 ms 187 ms
9 1014 ms 182 ms
10 871 ms 159 ms
  • 5M 測試10次分別用時:
序號 純js js+wasm
1 127 ms 48 ms
2 124 ms 50 ms
3 140 ms 44 ms
4 129 ms 47 ms
5 127 ms 51 ms
6 129 ms 50 ms
7 126 ms 46 ms
8 119 ms 54 ms
9 121 ms 46 ms
10 118 ms 50 ms
  • 1M 測試10次分別用時:
序號 純js js+wasm
1 46 ms 18 ms
2 41 ms 22 ms
3 43 ms 13 ms
4 40 ms 15 ms
5 44 ms 11 ms
6 47 ms 15 ms
7 42 ms 11 ms
8 42 ms 20 ms
9 45 ms 13 ms
10 44 ms 16 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的資料請關註WalkonNet其它相關文章!

推薦閱讀: