實現前端高效能運算分享
前端高效能運算之一:WebWorkers 什麼是WebWorkers
簡單說, WebWorkers 是一個HTML5的新API,web開發者可以透過此API在後台運行一個腳本而不阻塞UI,可以用來做需要大量運算的事情,充分利用CPU多核心。
現在瀏覽器基本上都 支援WebWorkers 了。
Parallel.js
直接使用 WebWorkers 介面還是太繁瑣,好在有人已經對此作了封裝: Parallel.js 。
注意 Parallel.js 可以透過node安裝:
$ npm install paralleljs
不過這個是在node.js下用的,用的node的cluster模組。如果要在瀏覽器裡使用的話, 需要直接套用js:
<script src="parallel.js"></script>
然後可以得到一個全域變量,Parallel。 Parallel提供了map和reduce兩個函數式程式設計的接口,可以非常方便的進行並發操作。
我們先來定義一下我們的問題,由於業務比較複雜,我這裡把問題簡化成求1-1,0000,0000的和,然後在依次減去1-1,0000,0000,答案顯而易見: 0! 這樣做是因為數字太大的話會有數據精確度的問題,兩種方法的結果會有一些差異,會讓人覺得並行的方法不可靠。此問題在我的mac pro chrome61下直接簡單地跑js運行的話大概是1.5s(我們實際業務問題需要15s,這裡為了避免用戶測試的時候把瀏覽器搞死,我們簡化了問題)。
const N = 100000000;// 总次数1亿 // 更新自2017-10-24 16:47:00 // 代码没有任何含义,纯粹是为了模拟一个耗时计算,直接用 // for (let i = start; i <= end; i += 1) total += i; // 有几个问题,一是代码太简单没有任何稍微复杂一点的操作,后面用C代码优化的时候会优化得很夸张,没法对比。 // 二是数据溢出问题, 我懒得处理这个问题,下面代码简单地先加起来,然后再减掉,答案显而易见为0,便于测试。 function sum(start, end) { let total = 0; for (let i = start; i <= end; i += 1) { if (i % 2 == 0 || i % 3 == 1) { total += i; } else if (i % 5 == 0 || i % 7 == 1) { total += i / 2; } } for (let i = start; i <= end; i += 1) { if (i % 2 == 0 || i % 3 == 1) { total -= i; } else if (i % 5 == 0 || i % 7 == 1) { total -= i / 2; } } return total; } function paraSum(N) { const N1 = N / 10;//我们分成10分,没分分别交给一个web worker,parallel.js会根据电脑的CPU核数建立适量的workers let p = new Parallel([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) .require(sum); return p.map(n => sum((n - 1) * 10000000 + 1, n * 10000000))// 在parallel.js里面没法直接应用外部变量N1 .reduce(data => { const acc = data[0]; const e = data[1]; return acc + e; }); } export { N, sum, paraSum }
程式碼比較簡單,我在這裡說幾個剛用的時候遇到的坑。
require所有需要的函數
例如在上訴程式碼中用到了sum,你需要提前require(sum),如果sum中由用到了另一個函數f ,你還需要require(f),同樣如果f中用到了g,則還需要require(g),直到你require了所有用到的定義的函數。 。 。 。
沒辦法require變數
我們上訴程式碼我本來定義了N1,但沒辦法用
ES6編譯成ES5之後的問題以及Chrome沒報錯
實際專案中一開始我們用到了ES6的特性:陣列解構。本來這是很簡單的特性,現在大部分瀏覽器都已經支援了,不過我當時配置的babel會編譯成ES5,所以會產生程式碼_slicedToArray,大家可以在線上Babel測試,然後Chrome下面始終不work,也沒有任何報錯訊息,查了很久,後來在Firefox下打開,有報錯訊息:
ReferenceError: _slicedToArray is not defined
看來Chrome也不是萬能的啊。 。 。
大家可以在 此Demo頁面 測試, 提速大概在4倍左右,當然還是得看自己電腦CPU的核數。 另外我後來在同樣的電腦上Firefox55.0.3(64位)測試,上訴代碼居然只要190ms! ! !在Safari9.1.1下也是190ms左右。 。 。
Refers
https://developer.mozilla.org/zh-CN/docs/Web/API/WebWorkersAPI/Usingwebworkers
https://www.html5rocks.com/en/tutorials/workers/basics/
https://parallel.js.org/
bash # Fetch the latest registry of available tools. ./emsdk update # Download and install the latest SDK tools. ./emsdk install latest # Make the "latest" SDK "active" for the current user. (writes ~/.emscripten file) ./emsdk activate latest # Activate PATH and other environment variables in the current terminal source ./emsdk_env.sh
3. 将下列添加到环境变量PATH中
~/emsdk-portable ~/emsdk-portable/clang/fastcomp/build_incoming_64/bin ~/emsdk-portable/emscripten/incoming
4. 其他
我在执行的时候碰到报错说LLVM版本不对,后来参考文档配置了LLVM_ROOT变量就好了,如果你没有遇到问题,可以忽略。
LLVM_ROOT = os.path.expanduser(os.getenv('LLVM', '/home/ubuntu/a-path/emscripten-fastcomp/build/bin'))
5. 验证是否安装好
执行emcc -v,如果安装好会出现如下信息:
emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 1.37.21 clang version 4.0.0 (https://github.com/kripken/emscripten-fastcomp-clang.git 974b55fd84ca447c4297fc3b00cefb6394571d18) (https://github.com/kripken/emscripten-fastcomp.git 9e4ee9a67c3b67239bd1438e31263e2e86653db5) (emscripten 1.37.21 : 1.37.21) Target: x86_64-apple-darwin15.5.0 Thread model: posix InstalledDir: /Users/magicly/emsdk-portable/clang/fastcomp/build_incoming_64/bin INFO:root:(Emscripten: Running sanity checks)
Hello, WebAssembly!
创建一个文件hello.c:
#include <stdio.h> int main() { printf("Hello, WebAssembly!\n"); return 0; }
编译C/C++代码:
emcc hello.c
上述命令会生成一个a.out.js文件,我们可以直接用Node.js执行:
node a.out.js
输出:
Hello, WebAssembly!
为了让代码运行在网页里面,执行下面命令会生成hello.html和hello.js两个文件,其中hello.js和a.out.js内容是完全一样的。
emcc hello.c -o hello.html
➜ webasm-study md5 a.out.js MD5 (a.out.js) = d7397f44f817526a4d0f94bc85e46429 ➜ webasm-study md5 hello.js MD5 (hello.js) = d7397f44f817526a4d0f94bc85e46429
然后在浏览器打开hello.html,可以看到页面:;;
前面生成的代码都是asm.js,毕竟Emscripten是人家作者Alon Zakai最早用来生成asm.js的,默认输出asm.js也就不足为奇了。当然,可以通过option生成wasm,会生成三个文件:hello-wasm.html, hello-wasm.js, hello-wasm.wasm。
emcc hello.c -s WASM=1 -o hello-wasm.html
然后浏览器打开hello-wasm.html,发现报错TypeError: Failed to fetch。原因是wasm文件是通过XHR异步加载的,用file:////访问会报错,所以我们需要启一个服务器。
npm install -g serve serve .
然后访问http://localhost:5000/hello-wasm.html,就可以看到正常结果了。
调用C/C++函数
前面的Hello, WebAssembly!都是main函数直接打出来的,而我们使用WebAssembly的目的是为了高性能计算,做法多半是用C/C++实现某个函数进行耗时的计算,然后编译成wasm,暴露给js去调用。
在文件add.c中写如下代码:
#include <stdio.h> int add(int a, int b) { return a + b; } int main() { printf("a + b: %d", add(1, 2)); return 0; }
有两种方法可以把add方法暴露出来给js调用。
通过命令行参数暴露API
emcc -s EXPORTED_FUNCTIONS="['_add']" add.c -o add.js
注意方法名add前必须加_。 然后我们可以在Node.js里面这样使用:
// file node-add.js const add_module = require('./add.js'); console.log(add_module.ccall('add', 'number', ['number', 'number'], [2, 3]));
执行node node-add.js会输出5。如果需要在web页面使用的话,执行:
emcc -s EXPORTED_FUNCTIONS="['_add']" add.c -o add.html
然后在生成的add.html中加入如下代码:
<button onclick="nativeAdd()">click</button> <script type='text/javascript'> function nativeAdd() { const result = Module.ccall('add', 'number', ['number', 'number'], [2, 3]); alert(result); } </script>
然后点击button,就可以看到执行结果了。
Module.ccall会直接调用C/C++代码的方法,更通用的场景是我们获取到一个包装过的函数,可以在js里面反复调用,这需要用Module.cwrap,具体细节可以参看 文档 。
const cAdd = add_module.cwrap('add', 'number', ['number', 'number']); console.log(cAdd(2, 3)); console.log(cAdd(2, 4));
定义函数的时候添加EMSCRIPTEN_KEEPALIVE
添加文件add2.c。
#include <stdio.h> #include <emscripten.h> int EMSCRIPTEN_KEEPALIVE add(int a, int b) { return a + b; } int main() { printf("a + b: %d", add(1, 2)); return 0; }
执行命令:
emcc add2.c -o add2.html
同样在add2.html中添加代码:
<button onclick="nativeAdd()">click</button> <script type='text/javascript'> function nativeAdd() { const result = Module.ccall('add', 'number', ['number', 'number'], [2, 3]); alert(result); } </script>
但是,当你点击button的时候,报错:
Assertion failed: the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)
可以通过在main()中添加emscripten_exit_with_live_runtime()解决:
#include <stdio.h> #include <emscripten.h> int EMSCRIPTEN_KEEPALIVE add(int a, int b) { return a + b; } int main() { printf("a + b: %d", add(1, 2)); emscripten_exit_with_live_runtime(); return 0; }
或者也可以直接在命令行中添加-s NO_EXIT_RUNTIME=1来解决,
emcc add2.c -o add2.js -s NO_EXIT_RUNTIME=1
不过会报一个警告:
exit(0) implicitly called by end of main(), but noExitRuntime, so not exiting the runtime (you can use emscripten_force_exit, if you want to force a true shutdown)
所以建议采用第一种方法。
上述生成的代码都是asm.js,只需要在编译参数中添加-s WASM=1中就可以生成wasm,然后使用方法都一样。
用asm.js和WebAssembly执行耗时计算
前面准备工作都做完了, 现在我们来试一下用C代码来优化前一篇中提过的问题。代码很简单:
// file sum.c #include <stdio.h> // #include <emscripten.h> long sum(long start, long end) { long total = 0; for (long i = start; i <= end; i += 3) { total += i; } for (long i = start; i <= end; i += 3) { total -= i; } return total; } int main() { printf("sum(0, 1000000000): %ld", sum(0, 1000000000)); // emscripten_exit_with_live_runtime(); return 0; }
注意用gcc编译的时候需要把跟emscriten相关的两行代码注释掉,否则编译不过。 我们先直接用gcc编译成native code看看代码运行多块呢?
➜ webasm-study gcc sum.c ➜ webasm-study time ./a.out sum(0, 1000000000): 0./a.out 5.70s user 0.02s system 99% cpu 5.746 total ➜ webasm-study gcc -O1 sum.c ➜ webasm-study time ./a.out sum(0, 1000000000): 0./a.out 0.00s user 0.00s system 64% cpu 0.003 total ➜ webasm-study gcc -O2 sum.c ➜ webasm-study time ./a.out sum(0, 1000000000): 0./a.out 0.00s user 0.00s system 64% cpu 0.003 total
可以看到有没有优化差别还是很大的,优化过的代码执行时间是3ms!。really?仔细想想,我for循环了10亿次啊,每次for执行大概是两次加法,两次赋值,一次比较,而我总共做了两次for循环,也就是说至少是100亿次操作,而我的mac pro是2.5 GHz Intel Core i7,所以1s应该也就执行25亿次CPU指令操作吧,怎么可能逆天到这种程度,肯定是哪里错了。想起之前看到的 一篇rust测试性能的文章 ,说rust直接在编译的时候算出了答案, 然后把结果直接写到了编译出来的代码里, 不知道gcc是不是也做了类似的事情。在知乎上 GCC中-O1 -O2 -O3 优化的原理是什么? 这篇文章里, 还真有loop-invariant code motion(LICM)针对for的优化,所以我把代码增加了一些if判断,希望能“糊弄”得了gcc的优化。
#include <stdio.h> // #include <emscripten.h> // long EMSCRIPTEN_KEEPALIVE sum(long start, long end) { long sum(long start, long end) { long total = 0; for (long i = start; i <= end; i += 1) { if (i % 2 == 0 || i % 3 == 1) { total += i; } else if (i % 5 == 0 || i % 7 == 1) { total += i / 2; } } for (long i = start; i <= end; i += 1) { if (i % 2 == 0 || i % 3 == 1) { total -= i; } else if (i % 5 == 0 || i % 7 == 1) { total -= i / 2; } } return total; } int main() { printf("sum(0, 1000000000): %ld", sum(0, 100000000)); // emscripten_exit_with_live_runtime(); return 0; }
执行结果大概要正常一些了。
➜ webasm-study gcc -O2 sum.c ➜ webasm-study time ./a.out sum(0, 1000000000): 0./a.out 0.32s user 0.00s system 99% cpu 0.324 total
ok,我们来编译成asm.js了。
#include <stdio.h> #include <emscripten.h> long EMSCRIPTEN_KEEPALIVE sum(long start, long end) { // long sum(long start, long end) { long total = 0; for (long i = start; i <= end; i += 1) { if (i % 2 == 0 || i % 3 == 1) { total += i; } else if (i % 5 == 0 || i % 7 == 1) { total += i / 2; } } for (long i = start; i <= end; i += 1) { if (i % 2 == 0 || i % 3 == 1) { total -= i; } else if (i % 5 == 0 || i % 7 == 1) { total -= i / 2; } } return total; } int main() { printf("sum(0, 1000000000): %ld", sum(0, 100000000)); emscripten_exit_with_live_runtime(); return 0; }
执行:
emcc sum.c -o sum.html
然后在sum.html中添加代码
<button onclick="nativeSum()">NativeSum</button> <button onclick="jsSumCalc()">JSSum</button> <script type='text/javascript'> function nativeSum() { t1 = Date.now(); const result = Module.ccall('sum', 'number', ['number', 'number'], [0, 100000000]); t2 = Date.now(); console.log(`result: ${result}, cost time: ${t2 - t1}`); } </script> <script type='text/javascript'> function jsSum(start, end) { let total = 0; for (let i = start; i <= end; i += 1) { if (i % 2 == 0 || i % 3 == 1) { total += i; } else if (i % 5 == 0 || i % 7 == 1) { total += i / 2; } } for (let i = start; i <= end; i += 1) { if (i % 2 == 0 || i % 3 == 1) { total -= i; } else if (i % 5 == 0 || i % 7 == 1) { total -= i / 2; } } return total; } function jsSumCalc() { const N = 100000000;// 总次数1亿 t1 = Date.now(); result = jsSum(0, N); t2 = Date.now(); console.log(`result: ${result}, cost time: ${t2 - t1}`); } </script>
另外,我们修改成编译成WebAssembly看看效果呢?
emcc sum.c -o sum.js -s WASM=1
Browser | webassembly | asm.js | js |
Chrome61 | 1300ms | 600ms | 3300ms |
Firefox55 | 600ms | 800ms | 700ms |
Safari9.1 | 不支持 | 2800ms | 因不支持ES6我懒得改写没测试 |
感觉Firefox有点不合理啊, 默认的JS太强了吧。然后觉得webassembly也没有特别强啊,突然发现emcc编译的时候没有指定优化选项-O2。再来一次:
emcc -O2 sum.c -o sum.js # for asm.js emcc -O2 sum.c -o sum.js -s WASM=1 # for webassembly
Browser | webassembly -O2 | asm.js -O2 | js |
Chrome61 | 1300ms | 600ms | 3300ms |
Firefox55 | 650ms | 630ms | 700ms |
居然没什么变化, 大失所望。号称asm.js可以达到native的50%速度么,这个倒是好像达到了。但是今年 Compiling for the Web with WebAssembly (Google I/O '17) 里说WebAssembly是1.2x slower than native code,感觉不对呢。 asm.js 还有一个好处是,它就是js,所以即使浏览器不支持,也能当成不同的js执行,只是没有加速效果。当然 WebAssembly 受到各大厂商一致推崇,作为一个新的标准,肯定前景会更好,期待会有更好的表现。
Refers
人工智能是最近两年绝对的热点,而这次人工智能的复兴,有一个很重要的原因就是计算能力的提升,主要依赖于GPU。去年Nvidia的股价飙升了几倍,市面上好点的GPU一般都买不到,因为全被做深度学习以及挖比特币的人买光了
以上内容就是实现前端高性能计算分享,希望能帮助到大家。
相关推荐:
以上是實現前端高效能運算分享的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

夸克網盤和百度網盤都是很便利的儲存工具,不少的用戶都在詢問這兩款軟體互通嗎?夸克網盤怎麼分享到百度網盤?下面就讓本站來為用戶們來仔細的介紹一下夸克網盤的文件怎麼保存到百度網盤方法吧。夸克網盤的文件怎麼保存到百度網盤方法1、想要知道怎麼把夸克網盤的文件轉到百度網盤,首先在夸克網盤上下載需要保存的文件,然後打開百度網盤客戶端後,選擇壓縮檔案要儲存的資料夾,雙擊開啟該資料夾。 2、開啟該資料夾後,點選視窗左上角區域的「上傳」。 3、在電腦中找到需要上傳的壓縮文件,點選選

1.首先我們進入到網易雲音樂中,然後在軟體首頁介面中,點選進入到歌曲的播放介面中。 2.然後在歌曲播放介面中,找到右上方的分享功能按鈕,如下圖紅框所示位置,點擊選擇分享的管道;在分享管道中,點擊底部的「分享至」選項,然後選擇第一個“微信朋友圈”,即可將內容分享至微信朋友圈。

近期,百度網盤安卓客戶端迎來了全新的8.0.0版本,這個版本不僅帶來了許多變化,還增添了許多實用功能。其中,最引人注目的便是資料夾共享功能的增強。現在,使用者可以輕鬆邀請好友加入,共同分享工作與生活中的重要文件,實現更便利的協作與分享。那麼究竟該如何分享給好友自己需要分享的文件呢,下文中本站小編就會為大家帶來詳細內容介紹,希望能幫助大家! 1)開啟百度雲APP,先點選在首頁選擇相關的資料夾,再點選介面右上角的【...】圖示;(如下圖)2)接著點選「共用成員」一欄中的【+ 】,最後在勾選所

PHP與Vue:完美搭檔的前端開發利器在當今網路快速發展的時代,前端開發變得愈發重要。隨著使用者對網站和應用的體驗要求越來越高,前端開發人員需要使用更有效率和靈活的工具來創建響應式和互動式的介面。 PHP和Vue.js作為前端開發領域的兩個重要技術,搭配起來可以稱得上是完美的利器。本文將探討PHP和Vue的結合,以及詳細的程式碼範例,幫助讀者更好地理解和應用這兩

芒果TV擁有各種類型的電影、電視劇、綜藝等資源,用戶可以在其中自由的選擇觀看。芒果tv會員不僅能夠看到全部的VIP劇而且還能夠設定最高清的畫質,幫助用戶爽快看劇,下面小編就給大家帶來一些芒果tv免費的會員帳號供用戶們使用,趕緊來看一看吧。芒果tv最新會員帳號免費分享2023:注意:都是收集的最新會員帳號,可以直接登入使用,不要隨意的修改密碼。帳號:13842025699密碼:qds373帳號:15804882888密碼:evr6982帳號:13330925667密碼:jgqae帳號:1703

標題:解決Discuz微信分享無法顯示的問題,需要具體程式碼範例隨著行動網路的發展,微信成為了人們日常生活中不可或缺的一部分。在網站開發中,為了提升使用者體驗和擴大網站的曝光度,許多網站會整合微信分享功能,讓使用者能夠輕鬆分享網站的內容到朋友圈或微信群組。然而,有時在使用Discuz等開源論壇系統時,會遇到微信分享無法顯示的問題,這給使用者體驗帶來了一定的困

Django是一個由Python編寫的web應用框架,它強調快速開發和乾淨方法。儘管Django是web框架,但要回答Django是前端還是後端這個問題,需要深入理解前後端的概念。前端是指使用者直接和互動的介面,後端是指伺服器端的程序,他們透過HTTP協定進行資料的互動。在前端和後端分離的情況下,前後端程式可以獨立開發,分別實現業務邏輯和互動效果,資料的交

在前端開發面試中,常見問題涵蓋廣泛,包括HTML/CSS基礎、JavaScript基礎、框架和函式庫、專案經驗、演算法和資料結構、效能最佳化、跨域請求、前端工程化、設計模式以及新技術和趨勢。面試官的問題旨在評估候選人的技術技能、專案經驗以及對行業趨勢的理解。因此,應試者應充分準備這些方面,以展現自己的能力和專業知識。
