如何实现前端高性能计算? -欧洲杯足彩官网

0顶
0踩

如何实现前端高性能计算?

2017-11-22 11:09 by 副主编 jihong10102006 评论(0) 有20855人浏览
引用
来源:
作者:

最近做一个项目,里面涉及到在前端做大量计算,直接用js跑了一下,大概需要15s的时间, 也就是用户的浏览器会卡死15s,这个完全接受不了。

虽说有v8这样牛逼的引擎,但大家知道js并不适合做cpu密集型的计算,一是因为单线程,二是因为动态语言。我们就从这两个突破口入手,首先搞定“单线程”的限制,尝试用webworkers来加速计算。

前端高性能计算之一:webworkers

什么是webworkers

简单说,是一个html5的新api,web开发者可以通过此api在后台运行一个脚本而不阻塞ui,可以用来做需要大量计算的事情,充分利用cpu多核。

大家可以看看这篇文章介绍https://www.html5rocks.com/en/tutorials/workers/basics/, 或者。
引用
the web workers specification defines an api for spawning background scripts in your web application. web workers allow you to do things like fire up long-running scripts to handle computationally intensive tasks, but without blocking the ui or other scripts to handle user interactions.

可以打开自己体验一下webworkers的加速效果。

现在浏览器基本都了。

parallel.js

直接使用接口还是太繁琐,好在有人已经对此作了封装:。

注意可以通过node安装:
$ npm install paralleljs

不过这个是在node.js下用的,用的node的cluster模块。如果要在浏览器里使用的话, 需要直接应用js:


然后可以得到一个全局变量,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也不是万能的啊。。。

大家可以在测试, 提速大概在4倍左右,当然还是得看自己电脑cpu的核数。 另外我后来在同样的电脑上firefox55.0.3(64位)测试,上诉代码居然只要190ms!!!在safari9.1.1下也是190ms左右。。。

refers

    前端高性能计算之二:asm.js & webassembly

    前面我们说了要解决高性能计算的两个方法,一个是并发用webworkers,另一个就是用更底层的静态语言。

    2012年,mozilla的工程师在研究编译器时突发奇想:能不能把c/c 编译成javascript,并且尽量达到native代码的速度呢?于是他开发了编译器,用于将c/c 代码编译成javascript的一个子集,性能差不多是原生代码的50%。大家可以看看这个ppt。

    之后google开发了[portable native client][pnaci],也是一种能让浏览器运行c/c 代码的技术。 后来估计大家都觉得各搞各的不行啊,居然google, microsoft, mozilla, apple等几家大公司一起合作开发了一个面向web的通用二进制和文本格式的项目,那就是,欧洲杯足彩官网上的介绍是:
    引用
    webassembly or wasm is a new portable, size- and load-time-efficient format suitable for compilation to the web.

    webassembly is currently being designed as an open standard by a w3c community group that includes representatives from all major browsers.

    所以,webassembly应该是一个前景很好的项目。我们可以看一下:

    安装emscripten

    访问https://kripken.github.io/emscripten-site/docs/getting_started/downloads.html

    1. 下载对应平台版本的sdk

    2. 通过emsdk获取最新版工具
     
     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 
    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 
    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,就可以看到执行结果了。

    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 
    #include 
    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的时候,报错:
    assertion failed: the runtime was exited (use no_exit_runtime to keep it alive after main() exits)

    可以通过在main()中添加emscripten_exit_with_live_runtime()解决:
    #include 
    #include 
    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 
    // #include 
    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直接在编译的时候算出了答案, 然后把结果直接写到了编译出来的代码里, 不知道gcc是不是也做了类似的事情。在知乎上这篇文章里, 还真有loop-invariant code motion(licm)针对for的优化,所以我把代码增加了一些if判断,希望能“糊弄”得了gcc的优化。
    #include 
    // #include 
    // 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 
    #include 
    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中添加代码
     
      
      
      

    另外,我们修改成编译成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%速度么,这个倒是好像达到了。但是今年里说webassembly是1.2x slower than native code,感觉不对呢。还有一个好处是,它就是js,所以即使浏览器不支持,也能当成不同的js执行,只是没有加速效果。当然受到各大厂商一致推崇,作为一个新的标准,肯定前景会更好,期待会有更好的表现。

    refers
      前端高性能计算之三:gpu加速计算

      人工智能是最近两年绝对的热点,而这次人工智能的复兴,有一个很重要的原因就是计算能力的提升,主要依赖于gpu。去年nvidia的股价飙升了几倍,市面上好点的gpu一般都买不到,因为全被做深度学习以及挖比特币的人买光了
      • 大小: 198.7 kb
      • 大小: 199.4 kb
      • 大小: 109.1 kb
      • 大小: 99.5 kb
      • 大小: 1.6 mb
      • 大小: 1.6 mb
      来自:
      0
      0
      评论 共 0 条 请登录后发表评论

      发表评论

      您还没有登录,请您登录后再发表评论

      相关推荐

      • 赛灵思宣布开始正式发放高性能计算行业首款针对intel前端总线(fsb)的fpga加速欧洲杯足彩官网的解决方案商业许可。基于高性能65nm virtex?-5 平台 fpga 和intel? quickassist技术,赛灵思公司的加速计算平台(accelerated computing...

      • 赛灵思宣布开始正式发放高性能计算行业首款针对intel前端总线(fsb)的fpga加速欧洲杯足彩官网的解决方案商业许可。基于高性能65nm virtex?-5 平台 fpga 和intel? quickassist技术,赛灵思公司的加速计算平台(accelerated computing...

      • 构建高性能web应用是现代软件开发中的重要挑战之一。本文将详细介绍一些关键的技术,帮助开发人员优化web应用的性能,提供良好的用户体验。

      • 前端面试题高性能高并发100

      • 本文将详细介绍前端性能优化的七大手段,包括减少请求数量、减小资源大小、优化网络连接、优化资源加载、减少重绘回流、使用性能更好的api和构建优化 减少请求数量 【合并】  如果不进行文件合并,有如下3个隐患...

      • 总阻塞时间:阻塞主线程并影响页面可用性的时间,比如异步任务过长导致阻塞主线程渲染(js线程和渲染线程冲突)渲染线程和js引擎是互斥的,所以对于一些计算密集型或高延迟的任务可能会阻塞页面的渲染。基于es6的静态...

      • 1.将样式表放在头部 首先说明一下,将样式表放在头部对于实际页面加载的时间并不能造成太大影响,但是这会减少页面首屏出现的时间,使页面内容... 参考:web前端性能优化——如何提高页面加载速度 - marcohan - 博客园

      • 前端页面性能优化指标

      • )宣布开始正式发放高性能计算行业首款针对intel前端总线(fsb)的fpga加速欧洲杯足彩官网的解决方案商业许可。基于高性能65nm virtex-5 平台fpga和intel quickassist技术,赛灵思公司的加速计算平台(accelerated computing ...

      • 前端性能监控专注于 web,小程序等大前端领域,主要关注用户页面性能(页面测速,接口测速,cdn 测速等)、质量(js 错误,ajax 错误等),并且通过联动腾讯云应用性能监控实现对前后端监控一体化的打通。...

      • 代码层面、webpack层面、web技术层面: vue 项目性能优化:

      • 因此前端复杂计算性能优化更多需要考虑计算过程中不能影响用户体验。对比目前业内的解法,worker和webassembly存在容器限制以及优化效果可能不明显。这里选用了一种时间切片的解法,整体思路和react fiber机制比较...

      • 我们不能避免,我们只能尽量减少 在做dom事件绑定的时候,尽量避免一个个的事件绑定,而是采用性能更高的事件委托来实现 事件委托(事件代理) 把事件绑定给外层容器,当里面的后代元素相关行为被触发,外层容器绑定...

      • 使用一般的tree组件渲染大量数据(如几千个树节点)的时候会非常卡顿,主要原因是页面中绘制的大量的dom,滚动或展开、收起不断造成页面重绘、回流,使得性能不佳。 解决思路: step1:将树形数据拍平成一般的...

      • 为了便于量化页面性能指标数据,量化页面性能优化措施达到的优化效果,以及向合作方提供准确可靠的性能数据报告,研发侧需要对现有性能数据采集的兼容性、功能性进行调研,便于后续制定数据采集方案。通用数据采集:...

      • 前端性能优化手段从以下几个方面入手: 加载优化,执行优化,渲染优化,脚本优化、代码优化 1、加载优化:减少http请求、缓存资源、压缩代码、无阻塞、首屏加载、按需加载、预加载、压缩图像、减少cookie、避免重定向...

      • 简介:随着前端的范畴逐渐扩大,深度逐渐下沉,富前端必然带来的一个问题就是性能。特别是在大型复杂项目中,重前端业务可能因为一个小小的数据依赖,导致整个页面卡顿甚至崩溃。本文基于quick bi(数据可视化分析...

      • 前端开发是创建web页面或app等前端界面呈现给用户的过程,通过html,css及javascript以及衍生出来的各种技术、框架、欧洲杯足彩官网的解决方案,来实现互联网产品的用户...移动互联网带来了大量高性能的移动终端设备以及快速的无线网

      • 在前端开发中,对于自己开发的app或者web page性能的好坏,一直是让前端开发很在意的话题。我们需要专业的网站测试工具,让我们知道自己的网页还有哪些需要更为优化的方面,我自己尝试了一款工具:lighthouse,感觉...

      • 很多外行人在考虑转行it时,都会出现...前端即网站前台部分,既包含了页面设计,又包含了页面实现,要是指运行在pc端,移动端等浏览器上展现给用户浏览的网页。 “前端”是网站与用户直接交互的部分,从字体到布局再

      global site tag (gtag.js) - google analytics
      网站地图