ホームページ >ウェブフロントエンド >jsチュートリアル >ブラウザはどのくらい高速に動作できるのでしょうか?
React.js は効率的な UI レンダリングで有名です。重要な理由の 1 つは、ユーザーが仮想 DOM を直接操作できることです。ブラウザ DOM の操作を最小限に抑え、DOM を手動で大量に変更することによるパフォーマンスの低下を回避します。待って、明らかに中間にレイヤーが追加されているのに、なぜ結果が速くなるのでしょうか? React.js の中心的な考え方は、DOM 操作が遅いため、全体的なパフォーマンスの向上と引き換えに DOM 操作を最小限に抑える必要があるということです。 DOM 操作が遅いのは誰の目にも明らかですが、他の JavaScript スクリプトは高速に実行する必要がありますか?
V8 が登場する前、この質問に対する答えはノーでした。 Google の初期のビジネス モデルは Web に基づいていました。Gmail のような非常に複雑な Web アプリをブラウザで作成したとき、主に JavaScript の実行速度が遅すぎることが原因で、ブラウザの耐えられないパフォーマンスに気づくことはできませんでした。 。 2008 年 9 月、Google は JavaScript エンジン V8 を構築することでこの状況を変えることを決定しました。 V8を搭載したChromeブラウザが市場に登場したとき、その速度は当時のどのブラウザにも遠く及ばなかった。ブラウザのパフォーマンスが前例のないほど向上したため、複雑な Web アプリが可能になります。
過去7年間、ブラウザの性能はCPUの性能とともに上昇し続けてきましたが、2008年に経験したような画期的な成長を達成することはできませんでした。 V8 では JavaScript のパフォーマンスを大幅に向上させるためにどのようなテクノロジーが使用されているのでしょうか?
V8の最適化
JavaScriptを高速化する方法について話すには、まずなぜJavaScriptが遅いのかについて話さなければなりません。周知のとおり、JavaScript は Brendan Eich によって 1 週間以上で開発されました。Apple のチームが 4 年間かけて開発した現在の Swift と比較すると、そもそも JavaScript に大きな期待を寄せるべきではありません。 。実際、ブレンダン・アイヒは、自分がそのような規模の言語を開発しているとは知りませんでした。プログラマーが柔軟に記述できるようにするために、彼は JavaScript を弱い型指定言語として設計し、オブジェクトのプロパティは実行時に追加および削除できます。 C++ における継承、ポリモーフィズム、テンプレート、仮想関数、動的バインディングなど、大勢の人々を困惑させる概念は、JavaScript にはもう存在しません。それで、誰がその仕事をするのでしょうか?当然ながらエンジンはJavaScriptのみです。変数の型がわからないため、実行時に多くの型の導出が行われます。パーサーが作業を完了し、抽象構文ツリー (AST) を構築すると、エンジンは AST をバイトコード (バイトコード) に変換し、実行のためにバイトコード インタープリターに渡します。パフォーマンスを最も低下させるステップの 1 つは、インタープリターがバイトコードを実行する段階です。当時を振り返ると、通訳のパフォーマンスが低いことは誰もが知っていたのではないでしょうか?実は、この設計の理由は、JavaScript はデザイナー向けに開発された言語 (フロントエンド エンジニアは寒く感じているでしょうか?) として、コスト効率が高く、パフォーマンスもそれほど要求されないという一般的な考えがあったからです。要求。 。
V8が行う主な作業は、エンジンを遅くするこの部分を削除することであり、ASTからCPU実行可能マシンコードを直接生成します。このジャストインタイムコンパイル技術をJIT(Just in time)と呼びます。十分に興味があるなら、これはいったいどのようにして行われるのかと自然に考えるでしょう。
例を挙げてみましょう:
function Foo(x, y) { this.x = x; this.y = y; } var foo = new Foo(7, 8); var bar = new Foo(8, 7); foo.z = 9;
属性の読み取り
最初はデータ構造です。オブジェクトのプロパティにインデックスを付けるにはどうすればよいでしょうか? JSON の key:value データ構造についてはすでによく知っていますが、メモリ内のキーによってインデックスを付けることはできるでしょうか?メモリ内の値の位置を特定できますか?もちろん、各オブジェクトのテーブルを維持している限り、メモリ内の各キーに対応する値の場所を保存することは可能です。
ここでの落とし穴は、オブジェクトごとにそのようなテーブルを維持する必要があることです。なぜ? C言語でどのように行われるかを見てみましょう。
struct Foo { int x, y; }; struct Foo foo, bar; foo.x = 7; foo.y = 8; bar.x = 8; bar.y = 7; // Cant' set foo.z
大学の教科書についてよく考えてください。foo.x と foo.y のアドレスは直接計算できます。これは、メンバー x と y の型が決まっており、foo.x = "Hello" は JavaScript では十分に可能ですが、C 言語では不可能であるためです。
V8 不想给每个对象都维护一个这样的表。它也想让 JavaScript 拥有 C/C++ 直接用偏移就读出属性的特性。所以它的解决思路就是让动态类型静态化。V8 实现了一个叫做隐藏类(Hidden Class)的特性,即给每个对象分配一个隐藏类。对于 foo 对象,它生成一个类似于这样的类:
class Foo { int x, y; }
当新建一个 bar 对象的时候,它的 x 和 y 属性恰好都是 int 类型,那么它和 foo 对象就共享了这个隐藏类。把类型确定以后,读取属性就只是在内存中增加一个偏移的事情了。而当 foo 新建了 z 属性的时候,V8 发现原来的类不能用了,于是就会给 foo 新建一个隐藏类。修改属性类型也是类似。
Inline caching
由上可知,当访问一个对象的属性的时候,V8 首先要做的就是确定对象当前的隐藏类。但每次这样做的开销也很大,那很容易想到的另一个计算机中常用的解决方案,就是缓存。在第一次访问给定对象属性的时候,V8 将假设所有同一部分代码的其他对象也都使用了这个对象的隐藏类,于是会告诉其他对象直接使用这个类的信息。在访问其他对象的时候,如果校验正确,那么只需要一条指令就可以得到所需的属性,如果失败,V8 就会自动取消刚才的优化。上面这段话用代码来表述就是:
foo.x
# ebx = the foo object cmp [ebx,<hidden class offset>],<cached hidden class> jne <inline cache miss> mov eax,[ebx, <cached x offset>]
这极大提升了 V8 引擎的速度。
随着 Intel 宣布 Tick-Tock 模型的延缓,CPU 处理速度不再能像之前一样稳步增长了,那么浏览器还能继续变快吗?V8 的优化是浏览器性能的终点吗?
JavaScript 的问题在于错误地假设前端工程师都是水平不高的编程人员(如果不是,你应该不会读到这里),岂图让程序员写得舒服而让计算机执行得痛苦。在现代浏览器引擎已经优化到这个地步的时候,我们不禁想问:为什么一定是 JavaScript ?前端工程师是不是可以让出一步,让自己多做一点点事情,而让引擎得以更高效地优化性能?JavaScript 成为事实上的浏览器脚本标准有历史原因,但这不能是我们停止进步的借口。
当 Web Assembly 正式宣布的时候,我才确定了不仅仅是我一个名不见经传的小程序员有这样的想法,那些世界上最顶级的头脑已经开始行动了。浏览器在大量需求的驱动下正在朝着一个高性能的方向前进,浏览器究竟可以有多快,2015 可能是这条路上另一个转折点。