好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

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测试数据/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的资料请关注其它相关文章!

查看更多关于wasm+js实现文件获取md5示例详解的详细内容...

  阅读:41次