>웹 프론트엔드 >JS 튜토리얼 >asm.js 및 웹어셈블리-WEB 고성능 컴퓨팅

asm.js 및 웹어셈블리-WEB 고성능 컴퓨팅

php中世界最好的语言
php中世界最好的语言원래의
2017-11-18 14:46:042277검색

이전에 WebWorkers-WEB의 고성능 컴퓨팅에 대해 소개한 적이 있습니다. javascript에 대한 지식이 매우 흥미롭기 때문에 오늘은 asm.js와 webassemblies와 WEB 고성능 컴퓨팅의 관계에 대해 말씀드리겠습니다. 그 전에 고성능 문제를 해결해야 합니다. 계산 방법에는 두 가지가 있습니다. 하나는 WebWorker를 동시에 사용하는 것이고, 다른 하나는 저수준 정적 언어를 사용하는 것입니다.

2012년 Mozilla 엔지니어 Alon Zakai는 LLVM 컴파일러를 연구하던 중 갑자기 아이디어를 얻었습니다. C/C++를 Javascript로 컴파일하여 네이티브 코드의 속도를 달성할 수 있을까? 그래서 그는 C/C++ 코드를 Javascript의 하위 집합인 asm.js로 컴파일하는 데 사용되는 Emscripten 컴파일러를 개발했습니다. 성능은 네이티브 코드의 거의 50%입니다. 이 PPT를 보시면 됩니다.


나중에 Google은 브라우저에서 C/C++ 코드를 실행할 수 있는 기술인 Portable Native Client를 개발했습니다. 나중에는 모두가 자신의 일을 하는 것이 불가능하다고 느꼈던 것 같습니다. 실제로 Google, Microsoft, Mozilla, Apple 및 기타 주요 회사가 협력하여 웹용 범용 바이너리 및 텍스트 형식 프로젝트를 개발했는데, 이것이 바로 WebAssembly입니다. WebAssembly 또는 wasm은 웹 컴파일에 적합한 새로운 이식 가능하고 크기 및 로드 시간 효율적인 형식입니다.


WebAssembly는 현재 공개 표준으로 설계되고 있습니다. 모든 주요 브라우저의 대표자를 포함하는 W3C 커뮤니티 그룹입니다.


그래서 WebAssembly는 전망이 좋은 프로젝트여야 합니다. 현재 브라우저 지원을 살펴볼 수 있습니다:


asm.js 및 웹어셈블리-WEB 고성능 컴퓨팅


Install Emscripten


방문 https://kripken.github.io/emscripten-site/docs/getting_started/downloads .html


1. 플랫폼 버전


2.emsdk

# 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!

파일 생성 .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

Output

Hello, WebAssembly!

웹 페이지의 코드에서 다음 명령을 실행하면 hello.html과 hello.js라는 두 개의 파일이 생성됩니다. hello.js와 a.out.js의 내용은 완전히 동일합니다.

emcc hello.c -o hello.html<code>
 
➜  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입니다. 놀랄 일이 아닙니다. 물론 옵션을 통해 wasm을 생성할 수도 있으며, hello-wasm.html, hello-wasm.js, hello-wasm.wasm 3개의 파일이 생성됩니다.

emcc hello.c -s WASM=1 -o hello-wasm.html
asm.js 및 웹어셈블리-WEB 고성능 컴퓨팅그런 다음 브라우저가 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!는 모두 기본 함수에서 직접 입력하는 방식으로 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;
}

js 호출에 대한 add 메서드를 노출하는 방법에는 두 가지가 있습니다.

명령줄 매개변수를 통해 API 노출

emcc -s EXPORTED_FUNCTIONS="[&#39;_add&#39;]" add.c -o add.js

메소드 이름 add 앞에 _를 추가해야 합니다. 그런 다음 Node.js에서 다음과 같이 사용할 수 있습니다.

// file node-add.js
const add_module = require(&#39;./add.js&#39;);
console.log(add_module.ccall(&#39;add&#39;, &#39;number&#39;, [&#39;number&#39;, &#39;number&#39;], [2, 3]));

node node-add.js를 실행하면 5가 출력됩니다. 웹 페이지에서 사용해야 하는 경우

emcc -s EXPORTED_FUNCTIONS="[&#39;_add&#39;]" add.c -o add.html

를 실행합니다. 그런 다음 생성된 add.html에 다음 코드를 추가합니다.

<button onclick="nativeAdd()">click</button>
  <script type=&#39;text/javascript&#39;>
    function nativeAdd() {
      const result = Module.ccall(&#39;add&#39;, &#39;number&#39;, [&#39;number&#39;, &#39;number&#39;], [2, 3]);
      alert(result);
    }
  </script>

그런 다음 버튼을 클릭하면 실행 결과를 볼 수 있습니다.

Module.ccall은 C/C++ 코드 메서드를 직접 호출합니다. 더 일반적인 시나리오는 js에서 반복적으로 호출할 수 있는 함수를 얻는 것입니다. 이를 위해서는 Module.cwrap을 사용해야 합니다.

const cAdd = add_module.cwrap(&#39;add&#39;, &#39;number&#39;, [&#39;number&#39;, &#39;number&#39;]);
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=&#39;text/javascript&#39;>
    function nativeAdd() {
      const result = Module.ccall(&#39;add&#39;, &#39;number&#39;, [&#39;number&#39;, &#39;number&#39;], [2, 3]);
      alert(result);
    }
  </script>

그러나 버튼을 클릭하면 오류가 보고됩니다:

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)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=&#39;text/javascript&#39;>
    function nativeSum() {
      t1 = Date.now();
      const result = Module.ccall(&#39;sum&#39;, &#39;number&#39;, [&#39;number&#39;, &#39;number&#39;], [0, 100000000]);
      t2 = Date.now();
      console.log(`result: ${result}, cost time: ${t2 - t1}`);
    }
  </script>
  <script type=&#39;text/javascript&#39;>
    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

感觉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

居然没什么变化, 大失所望。号称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受到各大厂商一致推崇,作为一个新的标准,肯定前景会更好,期待会有更好的表现。

这就是asm.js & webassembly与web高性能计算的关系了,之后还有想法写一份结合Rust做WebAssembly的文章,有兴趣的朋友可以持续关注。


위 내용은 asm.js 및 웹어셈블리-WEB 고성능 컴퓨팅의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.