Heim >Web-Frontend >js-Tutorial >Wie schnell kann ein Browser sein?
React.js ist für sein effizientes UI-Rendering bekannt. Einer der wichtigen Gründe ist, dass es ein virtuelles DOM verwaltet. React.js verwendet den Diff-Algorithmus, um die Minimaloperationen des Browsers zu bestimmen das DOM und vermeidet so den Leistungsverlust, der durch die manuelle Änderung des DOM in großen Mengen verursacht wird. Warten Sie, offensichtlich wird in der Mitte eine Ebene hinzugefügt. Warum wird das Ergebnis schneller? Die Kernidee von React.js besteht darin, dass DOM-Vorgänge langsam sind, sodass DOM-Vorgänge im Austausch für allgemeine Leistungsverbesserungen möglicherweise minimiert werden müssen. Es ist für alle klar, dass DOM-Operationen langsam sind, aber andere JavaScript-Skripte müssen schnell laufen?
Bevor V8 auf den Markt kam, lautete die Antwort auf diese Frage „Nein“. Das frühe Geschäftsmodell von Google basierte auf dem Web, als Google eine äußerst komplexe Webanwendung wie Gmail im Browser schrieb . Im September 2008 beschloss Google, diese Situation durch die Entwicklung einer JavaScript-Engine – V8 – zu ändern. Als der mit V8 ausgestattete Chrome-Browser auf den Markt kam, lag seine Geschwindigkeit weit hinter allen damaligen Browsern zurück. Beispiellose Verbesserungen der Browserleistung ermöglichen komplexe Web-Apps.
In den letzten sieben Jahren ist die Browserleistung zusammen mit der CPU-Leistung weiter gestiegen, hat jedoch nie das bahnbrechende Wachstum von 2008 erreicht. Welche Art von Technologie verwendet V8, um die Leistung von JavaScript so stark zu verbessern?
V8-Optimierung
Um darüber zu sprechen, wie man JavaScript schneller macht, sollten wir zunächst darüber sprechen, warum es langsam ist. Wie wir alle wissen, wurde JavaScript von Brendan Eich in mehr als einer Woche entwickelt. Verglichen mit dem aktuellen Swift, das das Ergebnis einer vierjährigen Arbeit eines Teams bei Apple ist, sollten Sie zunächst keine hohen Erwartungen daran haben . Tatsächlich war Brendan Eich nicht bewusst, dass er eine Sprache dieser Größenordnung entwickelte. Damit Programmierer beim Schreiben flexibel sind, hat er JavaScript als schwach typisierte Sprache konzipiert, und die Eigenschaften von Objekten können zur Laufzeit hinzugefügt und gelöscht werden. Konzepte wie Vererbung, Polymorphismus, Vorlagen, virtuelle Funktionen und dynamische Bindung in C++, die eine große Gruppe von Menschen verblüffen, gibt es in JavaScript nicht mehr. Wer wird also die Arbeit erledigen? Natürlich gibt es nur eine JavaScript-Engine. Da es den Variablentyp nicht kennt, führt es zur Laufzeit viele Typableitungen durch. Wenn der Parser seine Arbeit abschließt und einen abstrakten Syntaxbaum (AST) erstellt, übersetzt die Engine den AST in Bytecode (Bytecode) und übergibt ihn zur Ausführung an den Bytecode-Interpreter. Einer der Schritte, die die Leistung am meisten verlangsamen, ist die Phase, in der der Interpreter den Bytecode ausführt. Wusste im Rückblick nicht jeder, dass die Dolmetscherleistung gering war? Eigentlich nein, der Grund für dieses Design war, dass die Leute damals allgemein glaubten, dass JavaScript als eine für Designer entwickelte Sprache (fühlen sich Front-End-Ingenieure kalt?) keine allzu hohe Leistung erfordert Nachfrage. .
Die Hauptarbeit von V8 besteht darin, diesen Teil zu entfernen, der die Engine verlangsamt. Es generiert direkt von der CPU ausführbaren Maschinencode aus AST. Diese Just-in-Time-Kompilierungstechnologie wird JIT (Just in Time) genannt. Wenn Sie neugierig genug sind, ist die Frage natürlich: Wie um alles in der Welt wird das gemacht?
Geben wir ein Beispiel:
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;
Attributlesung
Das erste ist die Datenstruktur. Wie werden die Eigenschaften des Objekts indiziert? Wir sind mit der Datenstruktur key:value in JSON bereits zu vertraut, aber kann sie nach Schlüssel im Speicher indiziert werden? Kann der Speicherort des Werts bestimmt werden? Natürlich können Sie das, solange Sie für jedes Objekt eine Tabelle pflegen, in der die Position des Werts gespeichert ist, der jedem Schlüssel im Speicher entspricht, oder?
Die Falle besteht darin, dass Sie für jedes Objekt eine solche Tabelle pflegen müssen. Warum? Mal sehen, wie es in der C-Sprache gemacht wird.
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
Denken Sie sorgfältig über die Lehrbücher im College nach. Die Adressen von foo.x und foo.y können direkt berechnet werden. Dies liegt daran, dass die Typen der Mitglieder x und y bestimmt sind und foo.x = „Hello“ in JavaScript vollständig möglich ist, in der Sprache C jedoch nicht möglich ist.
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 可能是这条路上另一个转折点。