WebAssemblyの経験、またはC ++の未定義の動作がどのように影響を与えたか

前回のC ++ Russia 2018で、WebAssemblyへの切り替えの経験、UBにつまずいた方法とそれがどのように勇敢に強化されたのか、テクノロジー自体とそれがさまざまなデバイスでどのように機能するかについて話しました。 カットの下には、UBに関連するすべてのテキストバージョンがあります。 使用したテストのコードはGitHubで入手できます









プロジェクト概要

クライアントコード

ビジネスロジックは、フレームワークを使用してC ++で記述されています。 Luaサポート付きのC ++で書かれています。 プラットフォームに依存するコードがあるため、JavaScriptとDOMを使用する必要があります。







たとえば、デスクトッププラットフォームで動画を見るには、Googleのコーデックを使用した動画プレーヤーが使用され、ブラウザは動画タグを使用します







28

タイプintのオーバーフローが発生し、 -2 147 483 648



が表示されると想定するのは論理的です。







29日

いいえ、アプリケーションはクラッシュします。 同時に、すべてがasm.jsおよびデスクトップアプリケーションで動作します。







30

プログラムのtrunc_s



は、コードtrunc_s



またはtrunc_u



ます。 これに関するドキュメントには、次のことが記載されています。







元のfloatが整数範囲に収まらない場合、またはNaNである場合、intでfloatを切り捨てると例外がスローされる場合があります。

float



およびdouble



からint



への変換が行われるすべての問題領域をキャッチするか、何らかの方法で制御する必要があります。







31

この問題は何度も議論されてきましたが、現在、 nontrapping-float-to-int-conversionsと呼ばれるWebAssembly標準のために、このトピックに関する新しい仕様が開発されています。 現在、 BINARYEN_TRAP_MODE



オプションをclamp



値とともに使用することが提案されています。 float



int



に変換するすべてのケースで、変換の可能性をチェックするラッパーが使用されます。 これが不可能な場合、最大値または最小値に切り捨てられます。







32

clamp



オプションを使用すると、アプリケーションは安定して動作しました。 clamp



モードの状況を明確に示す簡単なテストが行われました。 1回のパスでは、 float



およびdouble



int



1億回変換します。 clamp



オプションなしのアセンブリと比較してパフォーマンスが約40%低下し、当時利用可能であったバージョン1.37.17の場合、この数値はさらに高かったため、スライドでは既にclamp



モードの最適化バージョンですが、それでもギャップは顕著です。 ここでは、Mac OS X用Chromeバージョン65のCore i7 2.2 Ghzプロセッサーで数値を測定しました。







クランプモード

clamp



モードは、このテストに基づいて、 エクリプセン開発者であるkripkenとして知られるAlon Zakaiによって最適化されました。 現在、 clamp



パフォーマンスはasm.jsとほとんど区別できません。 619ミリ秒のジャンプ。asm.jsの場合、これはJIT最適化が実行されたテストの最初の反復にすぎません。







33

Samsung Edge S8 +およびiPhone Xモバイルデバイスでの同じテスト。 wasmの場合のギャップは約20倍でした。 これは、JITコンパイルを本当に最適かつ効率的にする方法のデモンストレーションです。







iPhone Xでは、 clamp



モードの有無は約40%です。 Androidでは、このような結果はこのデバイスだけに適用されるわけではありません。 他のデバイスでは、結果は同様またはさらに悪いものです。







iOSにはまだWebAssemblyがサポートされておらず、実際には非アクティブなasm.jsバージョンとしか比較できないため、6か月前はこれらの数値は完全に異なっていました。







34

integer



float



double



切り捨てにより、コード内の問題のある領域をキャッチすることが決定されました。 最も簡単なオプションは、 float



の値をnumeric_limits<int>



境界と比較し、値がNaN



かどうかを確認することです。







35

キーの場所であるisfinite



のチェックは、切り捨て後に生成されました。 オプティマイザーは、 切り捨てを危険な操作と見なさないため、これを実行できると判断しました。 この場合、最適化は-3



ですが、最適化を行うとそのようなコードが生成されるため、これは重要ではありません。







すべてのケースが考慮されていても、壊れたコードを取得します。 現時点では、この問題はコンパイラに残っており、既知であり、その解決策は開発中です。これはnontrapping-float-to-int-conversionsです。







36

WebAssemblyの 切り捨て 現在安全でないため、コード生成にはより多くの制御が必要です。 たとえば、 -0



オプションを使用すると、関数のすべての値チェックは書き込まれた順序と同じ順序になりますが、これは出力ファイルのパフォーマンスとサイズに影響します。







optnone



属性を指定することで、この関数の最適化のみを制限できます。 模擬テストの結果を見ると、関数を使用した場合と使用しない場合のパフォーマンスの違いは非常に顕著であり、約70%になります。 この場合、1つの操作が実行されますint



2000万回int



変換されます。 clamp



オプションを使用した結果は、機能を使用した場合よりも生産的です。 テスト







37

モバイルデバイスでは、値検証機能を継続して使用するとパフォーマンスも低下します。 同じテスト。 このギャップはAndroidではっきりと見え、iOSでは80%以上、70%以上です。 これらは、関数を使用するための非常に重要な結果です。 したがって、本当に必要な場合にのみ使用することをお勧めします。







38

ほとんどの変換では、プログラムのinteger



へのfloat



特定の境界を超えることができないことを知っています。 これらの値は、 float



でチェックバックすることも、受信データによって何らかの形で最初に制限されることもあります。 clamp



モードの使用は冗長であり、 integer



型の境界を超えるリスクがある部分のみをチェックするだけで十分です。







次のテストでは、行列が乗算され、点が平面に投影されます。 テスト結果は9つの値の切り捨てであり、そのうちの1つは関数によってチェックされます。 ご覧のとおり、このオプションを使用すると、すでにclamp



と比較して約15%のパフォーマンスが向上します。 しかし、違いはデスクトップ上でそれほどはっきりとは見えません。







39

モバイルデバイスでは、結果がより顕著になります。 clamp



と比較すると、AndroidとiOSは20%強の増加を示しています。







それはすべて、アプリケーションとそのパフォーマンスに依存します。この場合、値をチェックする機能を備えたオプションが望ましいことが判明しました。 ただし、 clamp



モードはより最適化されています。 今すぐWebAssemblyに切り替えた場合は、アプリケーションの違いが目立たないため、それを使用しました。







40

もう1つの問題は、ファイルからデータを読み取ることです。 Emscriptenを使用すると、通常のファイルと同じ方法でC ++のファイルを操作できます。 これを行うには、ファイルパッケージを作成し、JavaScriptにマウントします。 アプリケーションの操作中に、多くの場合、新しい要素が作成され、必要に応じてファイルからロードされます。 ある時点で、これらのファイルのデータは無効になりました。







42

デフォルトでは、データファイルをロードした後、データがヒープにコピーされ、この領域へのポインターが使用されます。 ヒープに配置すると、ファイルへのアクセス速度が向上しますが、メモリを実装する場合( ALLOW_MEMORY_GROWTH



オプションがALLOW_MEMORY_GROWTH



)、ポインターはALLOW_MEMORY_GROWTH



になるため、ファイルから読み取るものは何らかのゴミです。







当時(バージョンv1.37.17)、これは既知の問題でした。 解決策:ファイルシステムにヒープを使用しないでください。 この場合、xhr応答のオブジェクトが直接使用されます。つまり、ランタイムは、ヒープにコピーせずに、既存のバッファーへのポインターを単に受け取ります。 現在、ツールチェーンの現在のバージョンでは、 ALLOW_MEMORY_GROWTH



オプションがALLOW_MEMORY_GROWTH



いる場合、 no-heap-copy



フラグが自動的に指定されます。







44

アプリケーションが一定量以上のメモリを消費することはないと想定できますが、大量のデータを操作する場合、これにより問題が発生し、ほとんどの場合、メモリが無駄になります。







この場合、アプリケーションのすべての要素は必要に応じて動的に作成されます。たとえば、ユーザーはリアルタイムで見積もりを受け取る最大9つのチャートを同時に開くことができます。 ただし、通常1〜2個のグラフィックスが使用され、アプリケーションの開始時にはこのメモリ量は使用されません。







起動時により多くのメモリが必要になると、モバイルデバイスで起動しないというリスクが発生します。 モバイルブラウザは、コンパイル時にメモリ不足で例外をスローします。







62

私たちが何に到達し、どのような結論が出されたか。 このテクノロジーは進化を続けており、コンパイラーの修正および改善を伴う更新が定期的に出されています。 GitHubで開発者と対話することもできます。これにより、新たな問題の解決策をすばやく見つけることができます。







切り捨ての問題は未解決のままであり、いつ修正されるかは不明です。 現時点でこの問題の解決策を使用すると、アプリケーションの速度が著しく低下する可能性があります。







システムAPIを操作したり、既製のラッパーを使用したりする他の方法がないため、JavaScriptで記述する必要があります。







デスクトップバージョンがない場合、ブラウザでのwasmのデバッグは非常に困難です。 これにより、開発が非常に難しくなります。 一部のツールチェーンの更新は、アプリケーションを破壊する変更を加えます。







iOSの状況は明確ではありません。 SafariでWebAssemblyがサポートされるようになりましたが、その後の更新でWebAssemblyが壊れて修復されました。 現時点では、Safariがasm.jsの実行を大幅に最適化し、はるかに高速に実行を開始することを防ぎます。 したがって、現時点では、iPhoneのメインバージョンとして使用します。







63

この技術は非常に有効であり、完成したプロジェクトでの使用に十分なレベルで実装されています。 私たちにとっての主なプラスは、モバイルおよび「弱い」デバイスでのパフォーマンスの顕著な向上です。 2017年9月からWebAssemblyを使用しています。







また、会議を開催してくれたセルゲイプラトノフセルプにも感謝します。








All Articles