Heim >Web-Frontend >js-Tutorial >asm.js & webassembly-WEB Hochleistungsrechnen

asm.js & webassembly-WEB Hochleistungsrechnen

php中世界最好的语言
php中世界最好的语言Original
2017-11-18 14:46:042277Durchsuche

Ich habe Ihnen das WebWorkers-WEB-Hochleistungsrechnen bereits vorgestellt. Deshalb werde ich Ihnen heute etwas über die Beziehung zwischen asm.js und Webassembly und dem WEB-Hochleistungsrechnen erzählen Zuvor müssen wir es lösen. Es gibt zwei Methoden für Hochleistungsrechnen: Eine besteht darin, WebWorker gleichzeitig zu verwenden, und die andere darin, eine statische Sprache auf niedrigerer Ebene zu verwenden.

Im Jahr 2012 hatte Mozilla-Ingenieur Alon Zakai eine plötzliche Idee, als er den LLVM-Compiler studierte: Kann C/C++ in Javascript kompiliert werden und versuchen, die Geschwindigkeit von nativem Code zu erreichen? Deshalb entwickelte er den Emscripten-Compiler, der zum Kompilieren von C/C++-Code in asm.js, einer Teilmenge von Javascript, verwendet wird. Die Leistung beträgt fast 50 % des nativen Codes. Sie können sich dieses PPT ansehen.


Später entwickelte Google Portable Native Client, eine Technologie, die es Browsern ermöglicht, C/C++-Code auszuführen. Später hatte wohl jeder das Gefühl, dass es unmöglich sei, sein eigenes Ding zu machen. Tatsächlich haben Google, Microsoft, Mozilla, Apple und andere große Unternehmen zusammengearbeitet, um ein universelles Binär- und Textformatprojekt für das Web zu entwickeln auf der offiziellen Website ist:


WebAssembly oder wasm ist ein neues tragbares, größen- und ladezeiteffizientes Format, das für die Kompilierung ins Web geeignet ist.


WebAssembly wird derzeit als offener Standard von einer W3C-Community-Gruppe entworfen, der Vertreter aller wichtigen Browser angehören.


Also WebAssembly Sollte eine vielversprechende Lösung sein. Gutes Projekt. Wir können einen Blick auf die aktuelle Browserunterstützung werfen:

asm.js & webassembly-WEB Hochleistungsrechnen



Emscripten installieren


Besuchen Sie https://kripken.github.io/emscripten-site/docs/getting_started/downloads.html


1. Laden Sie das SDK entsprechend der Plattformversion herunter


2. Holen Sie sich die neueste Version des Tools über 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. Fügen Sie Folgendes zur Umgebungsvariablen PATH hinzu

~/emsdk-portable
~/emsdk-portable/clang/fastcomp/build_incoming_64/bin
~/emsdk-portable/emscripten/incoming


4. Andere


Ich führe manchmal einen Fehler aus, der besagt, dass die LLVM-Version falsch war. Später habe ich die Variable LLVM_ROOT anhand der Dokumentation konfiguriert. Wenn keine Probleme auftreten, können Sie sie ignorieren.

LLVM_ROOT = os.path.expanduser(os.getenv('LLVM', '/home/ubuntu/a-path/emscripten-fastcomp/build/bin'))


Überprüfen Sie, ob es installiert ist


Führen Sie emcc -v aus. Folgendes wird angezeigt. Informationen:

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)

Hallo, WebAssembly!


Erstellen Sie eine Datei hallo.c:

#include <stdio.h>
int main() {
  printf("Hello, WebAssembly!\n");
  return 0;
}

C/C++-Code kompilieren:

emcc hello.c

Der obige Befehl generiert eine a.out.js-Datei, die wir direkt mit Node.js ausführen können:

node a.out.js

Ausgabe

Hello, WebAssembly!

Um den Code auf der Webseite auszuführen, werden durch Ausführen des folgenden Befehls zwei Dateien generiert: hello.html und hello.js. Der Inhalt von hello.js und a.out.js ist genau das gleiche.

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

Öffnen Sie dann hello.html im Browser. Sie können die Seite sehen.

asm.js & webassembly-WEB Hochleistungsrechnen

Die zuvor generierten Codes sind alle asm.js Schließlich wurde Emscripten erstmals vom Autor Alon Zakai zum Generieren von asm.js verwendet, sodass es nicht verwunderlich ist, dass asm.js standardmäßig ausgegeben wird. Natürlich kann wasm über die Option generiert werden, und es werden drei Dateien generiert: hello-wasm.html, hello-wasm.js, hello-wasm.wasm.

emcc hello.c -s WASM=1 -o hello-wasm.html

Dann öffnete der Browser hello-wasm.html und fand einen Fehler TypeError: Failed to fetch. Der Grund dafür ist, dass die WASM-Datei asynchron über XHR geladen wird und der Zugriff darauf mit file:/// einen Fehler meldet, sodass wir einen Server starten müssen.

npm install -g serve
serve

Besuchen Sie dann http://localhost:5000/hello-wasm.html und Sie können die normalen Ergebnisse sehen.

Aufrufen von C/C++-Funktionen

Die vorherigen Hallo, WebAssembly! werden alle direkt von der Hauptfunktion typisiert, und der Zweck der Verwendung von WebAssembly ist das Hochleistungsrechnen, das hauptsächlich in durchgeführt wird C/C++ implementiert eine bestimmte Funktion, um zeitaufwändige Berechnungen durchzuführen, kompiliert sie dann in wasm und stellt sie js zum Aufrufen zur Verfügung.


Schreiben Sie den folgenden Code in die Datei 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;
}

Es gibt zwei Möglichkeiten, die Add-Methode für js-Aufrufe verfügbar zu machen.

API über Befehlszeilenparameter verfügbar machen

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

Beachten Sie, dass _ vor dem Methodennamen hinzugefügt werden muss. Dann können wir es in Node.js wie folgt verwenden:

// 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]));

Beim Ausführen von node node-add.js wird 5 ausgegeben. Wenn Sie es auf einer Webseite verwenden müssen, führen Sie Folgendes aus:

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

und fügen Sie dann den folgenden Code zur generierten add.html hinzu:

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

Klicken Sie dann auf die Schaltfläche, um es anzuzeigen das Ausführungsergebnis.

Module.ccall ruft die C/C++-Codemethode direkt auf. Ein häufigeres Szenario ist, dass wir eine verpackte Funktion erhalten, die wiederholt in js aufgerufen werden kann. Die spezifischen Details können angezeigt werden Dokumentation.

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 hinzufügen, wenn Sie die Funktion definieren

Fügen Sie die Datei add2.c hinzu.

#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;
}

Führen Sie den Befehl aus:

emcc add2.c -o add2.html

Fügen Sie auch den Code in add2.html hinzu:

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

Allerdings, wenn Sie auf die Schaltfläche klicken , Fehlerbericht:

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的文章,有兴趣的朋友可以持续关注。


Das obige ist der detaillierte Inhalt vonasm.js & webassembly-WEB Hochleistungsrechnen. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn