Привет, Хабр! Что-то давно мы не говорили о Google Chrome и простых веб-технология. Давайте это исправим.



Как вам всем, наверное, известно, экосистема JavaScript развивается сразу в нескольких направлениях. Среди основных изменений можно выделить, например, прогресс в основных стандартах языка — недавнее закрепление норм ECMAScript 2015. Кроме таких серьёзных изменений, развивается язык и во множестве маленьких экспериментов — например — Strong Mode.

Само собой, для обеспечения растущих потребностей и поддержки новых технологий необходим новый, гибкий динамический (just-in-time) компилятор, и мы усердно работали над ним для нашего JavaScript-движка V8.

TurboFan (прим.: кодовое имя нового компилятора) создан «с нуля», и в процессе разработки мы учитывали новые возможности JavaScript. TurboFan умеет оптимизировать больше кода и делает это успешнее, чем предыдущий JIT-компилятор, поддерживает гибкие и динамические режимы оптимизации. Кроме того, новый компилятор написан с учётом всего накопленного опыта по CrankShaft'у, а значит, его станет намного проще поддерживать и улучшать. Благодаря этим и некоторым другим особенностям мы научили работать TurboFan с теми видами кода, которые бросали серьёзный вызов предыдущему компилятору. Проблемы с оптимизацией были у asm.js, литералов классов, областей видимости (scopes), вычисляемых свойств и циклов for-of.

В текущем исполнении новый динамический компилятор уже показывает многообещающий рост производительности, в том числе увеличение результатов zLib-теста в бенчмарке Octane на 29%.


TurboFan работает для некоторых видов кода в Google Chrome начиная с версии 41, ускоряя как традиционный контент, так и некоторые возможности, обеспечиваемые новыми технологиями в JavaScript.

Cо временем мы планируем подключать к новому компилятору исполнение всё большего числа разного кода JavaScript, и, в конечном итоге, полностью заменить CrankShaft, ускорив выполнение миллионов уже написанных строк кода и готовых скриптов. Оставайтесь на связи, скоро расскажем кое-что ещё. ;)

Комментарии (29)


  1. arestov
    09.07.2015 11:23
    +2

    Планируются ли оптимизации что бы можно было создавать генерируемые конструкторы, которые не сильно бы уступали в скорости инициализации обычным?

    Так что бы инициализация 1000 штук generatedClass1 или generatedClass2 не уступала OldClass

    var OldClass = function() {
      this.track = null;
      this.artist = null;
      this.full_title = null;
      this.url = null;
    }
    new OldClass();
    


    var getSetter = function(prop, callback) {
      return function(obj){
        obj[prop] = null;
        callback(obj);
      };
    };
    
    var realDyn = function(array) {
      // на основе списка свойств функция генерирует
      // функцию, которая не использует итерация
      var curSetter = function(){};
      for (var i = array.length - 1; i >= 0; i--) {
        curSetter = getSetter(array[i], curSetter);
      }
      return curSetter;
    };
    
    
    var gen = realDyn(['track', 'artist', 'full_title', 'url']);
    
    var GeneratedClass1 = function(){
      gen(this);
    };
    
    new GeneratedClass1();
    
    
    


    
    
    var make = function(array) {
      return function () {
        for (var i = 0; i < array.length; i++) {
          this[array[i]] = null;
        }
      };
    };
    
    var GeneratedClass2 = make(['track', 'artist', 'full_title', 'url']);
    new GeneratedClass2();
    
    


    1. Eternalko
      09.07.2015 13:40
      +3

      Зачем такие конструкции?


      1. arestov
        09.07.2015 16:46
        +1

        Для того чтобы автоматически создавать эффективные констукторы на базе которых будет создана тысяча другая объектов, набор полей которых ограничен (задаётся разработчиком не напрямую, но может быть определён с помощью алгоритма), но для которых крайне неудобно или невозможно написать тело на момент написания кода.

        Либо eval. Что не всегда возможно и просто небезопасно

        var evaledConstr = function(props) {
        
            var body = '"use strict";\n';
            for (var i = 0; i < props.length; i++) {
                    body += 'this["' + props[i] + '"] = null;\n';
            }
            return new Function(body);
        };
        
        var EvaledConstr = evaledConstr(['track', 'artist', 'full_title', 'url']);
        
        new EvaledConstr();
        


        (как вы знаете браузерные движки очень хорошо оптимизируют такой код)
        var Contr = function(){
           this.myName = null;
        };
        


        Либо извращения с предварительной компиляцией.

        Либо оптимизации движком.


        1. speakingfish
          09.07.2015 22:13

          del


        1. Eternalko
          10.07.2015 15:37

          Все оптимизации уже есть.
          Код ниже должен исполнятся на реальных данных так же быстро как и «нативный».

          var artistProps = {
            track: { writable: true },
            artist: { writable: true },
            full_title: { writable: true },
            url: { writable: true}
          };
          
          var ArtistProto = Object.create(Object.prototype, artistProps);
          
          var artists = [];
          for (var i = 0; i < 1000; i++) {
            artists.push(Object.create(ArtistProto));
          }
          


          В JS нету «классов». Это неуместный сахар для людей из других языков.
          Проще сразу думать via prototype-chain и не возвращаться к «классам».


          1. arestov
            11.07.2015 11:46

            Спасибо за совет, не знал про второй аргумент Object.create. Думаю, это то что нужно для моей задачи, осталось проверить производительность. (а наследование для этой задачи не нужно)


            1. Eternalko
              12.07.2015 01:07
              -1

              Не думаю что есть смысл думать о производительности:
              The Black Cat of Microbenchmarks

              Прочитайте весь блог разработчика


            1. arestov
              13.07.2015 10:58
              +1

              Оказалось, что Object.create для этой задачи бесполезен

              var mes = function(callback) {
                var start = new Date();
                callback();
                console.log('measure', new Date() - start);
              };
              
              var OldClass = function() {
                this.track = null;
                this.artist = null;
                this.full_title = null;
                this.url = null;
              };
              
              var artistProps = {
                track: { writable: true },
                artist: { writable: true },
                full_title: { writable: true },
                url: { writable: true}
              };
              
              mes(function() {
              	var items = [];
              	for (var i = 0; i < 1000000;i++) {
              		items.push(new OldClass());
              	}
              });
              
              mes(function() {
              	var items = [];
              	for (var i = 0; i < 1000000;i++) {
              		items.push(Object.create(null, artistProps));
              	}
              });
              


              measure 441
              measure 5436


              1. Eternalko
                13.07.2015 13:45
                -1

                Ну напишете так:

                Alt code
                _ = require('lodash');
                
                var mes = function(callback) {
                  var start = new Date();
                  callback();
                  console.log('measure', new Date() - start);
                };
                
                var OldClass = function() {
                  this.track = null;
                  this.artist = null;
                  this.full_title = null;
                  this.url = null;
                };
                
                
                mes(function() {
                	var items = []
                    for (var i = 0; i < 1000000;i++) {
                        items.push(new OldClass());
                    }
                });
                
                
                var artistProps = {
                    track: null,
                    artist: null,
                    full_title: null,
                    url: null
                }
                
                var MainArtist = function() {};
                
                _.forEach(artistProps, function(value, key){
                	MainArtist[key] = value;
                })
                
                mes(function() {
                    var items = [];
                    for (var i = 0; i < 1000000;i++) {
                        items.push(new MainArtist());
                    }
                });
                
                var r = {};
                


                1. arestov
                  13.07.2015 16:55
                  +1

                  var MainArtist = function() {};
                  
                  _.forEach(artistProps, function(value, key){
                      MainArtist[key] = value;
                  })
                  


                  а какой смысл в этом коде, если MainArtist[key] и (new MainArtist())[key] не имеют никакой связи?


                  1. Eternalko
                    13.07.2015 18:54
                    -1

                    MainArtist.prototype[key] = value; и связь через прототипы.


                    1. arestov
                      13.07.2015 20:24
                      +1

                      всё таки

                      var MainArtist = function() {};
                      
                      _.forEach(artistProps, function(value, key){
                          MainArtist[key] = value;
                      })
                      

                      это не
                      var MainArtist = function() {};
                      
                      _.forEach(artistProps, function(value, key){
                          MainArtist.prototype[key] = value;
                      })
                      


                      а наследование для задачи не нужно — нужны быстрые конструкторы


                      1. Eternalko
                        14.07.2015 00:04

                        Prototype Chain всегда будет самым быстрым в JS. Особенно на реальных данных, а не на тестах кторые непонятно что показывают.

                        Запустите себе

                        node --trace-hydrogen --trace-deopt --code-comments --print-opt-code constr.js > code.asm
                        

                        и загрузите данные из вашего скрипта в IR Hydra.
                        И там можно посмотреть как v8 ваш measure обманывает.


                        1. arestov
                          14.07.2015 09:09
                          +2

                          «Prototype Chain всегда будет самым быстрым в JS»
                          конечно же нет! youtu.be/tCG0aPNvkTs?t=10m39s
                          всегда быстрей взять свойство непосредственно с объекта, чем с его прототипа

                          особенно бессмысленно его использовать когда нужно создать сразу правильный hidden class для объектов у которых общий набор полей но значения полей у всех разные

                          www.youtube.com/watch?v=tCG0aPNvkTs

                          опять таки: задача сделать констуктор сравнительно эффективный такой же как

                           function Point(x, y) {
                             this.x = x;
                             this.y = y;
                          }
                          


                          при условии, что на этапе написания кода нет набора полей (this.x = x;), но он будет и будет ограничен после выполнения некоторого кода


                          1. Eternalko
                            14.07.2015 16:35
                            -1

                            «конечно же нет!» Конечно же да. Вы же конкретно пишете «нужны быстрые конструкторы».

                            Задача «задача сделать констуктор сравнительно эффективный» решается, в том числе и через прототипы.

                            Ваш бенчмарк меряет только скорость конструктора со свойствами. Не понимаю зачем вы меняете тему.


                            1. arestov
                              15.07.2015 14:07

                              не быстрые, а эффективные.

                              задача чтобы и конструктор быстро исполнялся и сразу создавался правильный hidden class для объекта. задачи наследования, задачи получения доступа к неким общим свойствам не стоит


                              1. Eternalko
                                15.07.2015 15:55

                                Ну вы уже определитесь. Под какой конкретно результат разогнать хотите.

                                Если дело чисто в теории, то достаточно будет разогнать Object.assign({},props) или его polyfill

                                Если подцеплять методы то только через прототипы.
                                Если быстрое создание и разумное чтение запись то опять через протипы.


    1. Developers_Relations Автор
      13.07.2015 15:36
      +1

      Инженеры Chrome отвечают, что подобная конструкция (с созданием генерируемых конструкторов) довольно сложна для оптимизации производительности до уровня конструкции с OldClass. Для решения подобной задачи стоит подумать об использовании eval-а для создания функции конструктора, которая напрямую установит желаемые свойства.


  1. datacompboy
    09.07.2015 11:42
    +1

    А раз он переписан с нуля, может V8 в V9 переименовать пора? ;)


    1. Kolonist
      09.07.2015 12:47

      TurboFan — это компилятор исходного кода в байт-код, а V8 — это виртуальная машина, которая этот байт-код исполняет.


    1. SelenIT2
      09.07.2015 15:22
      +1

      Разве бывают 9-цилиндровые моторы? Тогда уже V12 логичнее..:)


      1. Serator
        11.07.2015 05:02

        Звездообразные вполне себе бывают. К примеру, М-63.


        1. SelenIT2
          13.07.2015 10:25

          Звездообразный, насколько я понимаю — по определению не «V»…


  1. ilmirus
    09.07.2015 11:46

    А что насчет остальных тестов из октана, а не только zlib? А других бенчмарков, вроде SunSpider?


    1. a553
      09.07.2015 11:52
      +2

      В данный момент Turbofan сильно тормознутее предыдущего их движка, поэтому, видимо, для статьи был выбран отдельный бенчмарк, где виден прирост производительности. Хотя AWFY с этим не согласен.


      1. Shirixae
        09.07.2015 12:24
        +2

        Я думаю, в первую очередь, TurboFan сейчас ещё просто на сравнительно ранней стадии разработки. Что успели хорошо отладить и вылизать — то поставили в продакшен на исполнение реального кода и сбор статистики. А остальное — наверстают со временем, не просто же так они с нуля переделывают JIT-компилятор.


      1. Arilas
        09.07.2015 15:12

        Как написано в статье, TurboFan сейчас заточен на ограниченный перечень фич, который CrankShaft не может оптимизировать нормально. И постепенно Turbofan будет пополнять список этих фич, и заменит CrankShaft. Те тесты запускаются чито на TurboFan, без CrankShaft


  1. Aingis
    09.07.2015 16:20
    +3

    Итог статьи: ничего не понятно, но типа круто.


    1. 4p4
      09.07.2015 19:35
      +4

      V8 не работает как чистый интерпретатор, навроде Бейсика, вместо этого он компилирует функции в момент когда они вызываются в первый раз. Компиляция происходит очень быстро, используется очень простой компилятор, оптимизированный на время компиляции, а не на время исполнения.

      Для каждой функции запоминается количество вызовов, и когда оно превышает некий порог, вызывается «более крутой» компилятор, оптимизирующий, который имеет имя собственное CrankShaft, что в переводе значит, кажется, «коленчатый вал» (передающий вал двигателя).

      CrankShaft компилирует долго, старательно оптимизирует. Его написали далеко не сразу, в первых версиях v8 его не было. Он вообще долгое время считался экспериментальной фичей.

      Прошли годы, и в базе идей для CrankShaft накопилась куча предложений по улучшению, и вот, наконец, решили сделать следующий логический шаг: переписать «с нуля». Хотя, я подозреваю, что много кода, всё-таки перетащат.

      Кто именно сейчас возглавляет работу не знаю, изначальный дизайн, вроде, был Ларса Бака, известного гуру по виртуальным машинам и оптимизации компиляторов.