ECMAScript и все-все-все

Материал из YourcmcWiki
Перейти к: навигация, поиск
Автор

Виталий Филиппов
Дополнительный нижний колонтитул

ECMAScript и все-все-все

ECMAScript — ассемблер будущего,
бэкенд, фронтенд и все-все-все @@ %%

(Об эволюции и фичах JavaScript)

ES6.jpg

Виталий Филиппов, CUSTIS

О своих предпочтениях @@

«И давно вы занимаетесь программизмом?»

  • Начинал лет в 11 с C/C++ (Turbo C / C++Builder)
  • Потом открыл для себя Linux, свободный софт…
    главное читать логи :)
  • …LAMP (Perl/PHP), HTML и JS
  • Теперь полюбил серверный JS (nodejs)

О чём доклад? @@

  • Почему JS?
  • История JavaScript
  • Обзор языка, производительность
  • Обзор выдумок
    (фреймворки, системы сборки и т.п)
  • Немного демо

Что такое JS? @@

JavaScriptECMAScript

  • От Java только часть названия
  • Скриптота! (динамический язык)
  • Объект/массив/скаляр (JSON)
  • Прототипы, замыкания, колбэки, нет многопоточности
  • ES — язык. А ещё есть окружение (DOM, BOM)
  • Куча новых фич (ES2015-2016-2017)
  • Браузерный — Chrome (V8), Firefox (SpiderMonkey) и даже IE (ChakraCore)
  • Серверный — node.js (V8)

Скриптота vs типизация @@ %%

script_kiddies_demotivator.jpeg

Холивар!!! @@

  • Шутки в сторону: тема серьёзная
  • Популярных динамических языков много
    можно пытаться это отрицать, но таки удобно
  • Статические… хде они? Java? (C# не в счёт, винда)
  • Компилируемые новые есть: D, Rust, Go, Vala, Swift
    но кто на них пишет-то?

Все хотят одного @@

  • Типизация — не необходимость, как раньше, а лишь один из способов проверки
    что ещё можно проверять?
    Rust = borrow checker
    функциональщина = «purity» checker
  • При этом
    auto уже даже в C++
    тайпчекер (частично) уже даже в PHP (+ Hack)

Проверки — хорошо, но @@ %%

414874_paranojya.jpg

Почему JS? @@

  • Нейтральный C-подобный синтаксис
  • Быстрый! (как вычислительно, так и для I/O)
    Событийная машина node.js — не пустой звук! (1M соединений на одном сервере)
  • Всегда нужен в браузерах (а веб нынче даже 1С)
  • Мощное сообщество и развитие
  • Удобный пакетный менеджер npm
  • Есть тайпчекеры: TypeScript, Dart…

Синтаксис @@ %%

Perl @@

sub _skip_attrs
{
    my ($tag, $attrs) = @_;
    $tag = lc $tag;
    return "<$tag>" if $tag =~ m!^/!so;
    my ($enclosed) = $attrs =~ m!/$!so ? ' /' : '';
    $attrs = { $attrs =~ /([^\s=]+)=([^\s=\'\"]+|\"[^\"]*\"|\'[^\']*\')/gso };
    my $new = {};
    for (qw(name id class style title))
    {
        $new->{$_} = $attrs->{$_} if $attrs->{$_};
    }
    my %l = (a => 'href', blockquote => 'cite', q => 'cite');
    if ($attrs->{$l{$tag}} && $attrs->{$l{$tag}} !~ /^[\"\']?javascript/iso)
    {
        $new->{$l{$tag}} = $attrs->{$l{$tag}};
    }
    return "<$tag".join("", map { " $_=".$new->{$_} } keys %$new).$enclosed.">";
}
  • Спецсимволы захватили мир

PHP @@

$isExact = [];
foreach ([ 'line' => 'l', 'cfo' => 'cc' ] as $k => $t)
{
    if (!isset($specified[$k.'_id']) && !isset($specified[$k.'_id_exact']) && 
        !isset($groups[$k]) && !isset($groups[$k.'_all']))
        $isExact[] = "$posAlias.${k}_id IS NULL";
    elseif ($lastgrp == $k.'all')
        $isExact[] = "$posAlias.${k}_id=$t.id";
}
foreach ([ 'party', 'account', 'paytype' ] as $k)
    if (!isset($specified[$k.'_id']) && !isset($groups[$k]))
        $isExact[] = "$posAlias.${k}_id IS NULL";
return implode(' AND ', $isExact) ?: '1=1';
  • Что за $$$$$?

Python @@

class FileCache:
  def __init__(self, dir):
    self.dir = dir
    if not os.path.isdir(dir):
      os.mkdir(dir)
  def fn(self, key):
    key = re.sub('([^a-zA-Z0-9_\-]+)', lambda x: binascii.hexlify(x.group(1)), key)
    return self.dir+'/'+key
  def clean(self):
    t = time.time()
    for fn in os.listdir(self.dir):
      if t > os.stat(self.dir+'/'+fn).st_mtime:
        os.unlink(self.dir+'/'+fn)
  • Пробелы меняют смысл?!!!!

Ruby @@

module Gitlab
  class SearchResults
    attr_reader :current_user, :query
 
    def objects(scope, page = nil)
      case scope
      when 'projects'
        projects.page(page).per(per_page)
      when 'issues'
        issues.page(page).per(per_page)
      when 'merge_requests'
        merge_requests.page(page).per(per_page)
      when 'milestones'
        milestones.page(page).per(per_page)
      else
        Kaminari.paginate_array([]).page(page).per(per_page)
      end
    end
  • projects — переменная? Фигвам. Метод без аргументов.)

Go @@

func TestChannelStoreSave(t *testing.T) {
        Setup()
 
        teamId := model.NewId()
 
        o1 := model.Channel{}
        o1.TeamId = teamId
        o1.DisplayName = "Name"
        o1.Name = "a" + model.NewId() + "b"
        o1.Type = model.CHANNEL_OPEN
 
        if err := (<-store.Channel().Save(&o1)).Err; err != nil {
                t.Fatal("couldn't save item", err)
        }
 
        if err := (<-store.Channel().Save(&o1)).Err; err == nil {
                t.Fatal("shouldn't be able to update from save")
        }
  • Что за смайлики := <- & *? Где мои скобочки?

Erlang @@

iq_handler(From, _To,
	   #iq{type=set, lang = Lang,
	       sub_el = #xmlel{name = Operation} = SubEl} = IQ, CC)->
    ?DEBUG("carbons IQ received: ~p", [IQ]),
    {U, S, R} = jid:tolower(From),
    Result = case Operation of
        <<"enable">>->
	    ?INFO_MSG("carbons enabled for user ~s@~s/~s", [U,S,R]),
            enable(S,U,R,CC);
        <<"disable">>->
	    ?INFO_MSG("carbons disabled for user ~s@~s/~s", [U,S,R]),
            disable(S, U, R)
    end,
    case Result of
        ok ->
	    ?DEBUG("carbons IQ result: ok", []),
            IQ#iq{type=result, sub_el=[]};
	{error,_Error} ->
	    ?ERROR_MSG("Error enabling / disabling carbons: ~p", [Result]),
	    Txt = <<"Database failure">>,
            IQ#iq{type=error,sub_el = [SubEl, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)]}
end;
  • Ой-ой-ой…
  • Классов нет, есть процессы. Но сетевой, ага!

OCaml O_O @@

let log_client_info c sock = 
  let buf = Buffer.create 100 in
  let date = BasicSocket.date_of_int (last_time ()) in
  Printf.bprintf buf "%-12s(%d):%d -> %-30s[%-14s %-20s] connected for %5d secs %-10s bw %5d/%-5d %-6s %2d/%-2d reqs " 
    (Date.simple date) 
  (nb_sockets ())
  (client_num c)
  (
    let s = c.client_name in
    let len = String.length s in 
    if len > 30 then String.sub s 0 30 else s)
 
  (brand_to_string c.client_brand)
  (match c.client_kind with Indirect_address _ | Invalid_address _ -> "LowID"
    | Direct_address (ip,port) -> Printf.sprintf "%s:%d"
          (Ip.to_string ip) port)
  (last_time () - c.client_connect_time)
  (if c.client_rank > 0 then
      Printf.sprintf "rank %d" c.client_rank
      else "")
  (nwritten sock) (nread sock)
  (if c.client_banned then "banned" else "")
  c.client_requests_received
    c.client_requests_sent
  ;
  • ПОЛИЗ?!!!!

JS @@

TreeGridNode.prototype.setChildren = function(isLeaf, newChildren)
{
    if (!this.tr)
        this.grid.tbody.innerHTML = '';
    else
    {
        var tr = this.tr[this.tr.length-1];
        while (tr.nextSibling && tr.nextSibling._node.level > this.level)
            this.grid.tbody.removeChild(tr.nextSibling);
        if (this.leaf != isLeaf)
        {
            if (isLeaf)
            {
                this.tr[0].cells[0].firstChild.className = 'collapser collapser-inactive';
                removeListener(this.tr[0].cells[0].firstChild, 'click', this._getToggleHandler());
            }
            else
            {
                this.tr[0].cells[0].firstChild.className = this.collapsed ? 'collapser collapser-collapsed' : 'collapser collapser-expanded';
                addListener(this.tr[0].cells[0].firstChild, 'click', this._getToggleHandler());
            }
        }
    }
    this.leaf = isLeaf;
    this.children = [];
    this.childrenByKey = {};
    this.addChildren(newChildren);
}

Синтаксис JS @@ %%

Имхо вполне нейтральненько.

(неприятных рефлексов вызывать не должен)

История JS @@

  • 1995—2004: дремучий лес с партизанами
  • 2004—2008: появление AJAX
  • 2008+: шустрота и современный период

Дремучий период @@

1995—1997

  • Создание и начальная стандартизация
  • DOM ещё нет, только «DOM level 0»
    document.forms, document.images

1998

  • DOM level 1 (DHTML)
  • document.getElementById, все элементы — объекты

2000

  • DOM level 2
  • События и более-менее современный вид объектов
  • pre-AJAX: JSONP, невидимый iframe

Появление AJAX @@

2004

  • Firefox 1.0
  • XmlHttpRequest
  • Первое SPA — Gmail, впервые упомянут термин «AJAX»
  • Начало конца дремучего периода JS, как языка для всплывающих баннеров

Совместимость браузеров ещё плохая, так что

  • 2006 — jQuery

Современное развитие @@

  • 2008 — Google V8
  • 2009 — IE8 (M$ очнулся)
  • 2009 — node.js (2011 — v0.6)
  • 2011 — начало работы над ES6
  • 2012 — Angular 1.0
  • 2013 — React
  • 2015 — ES6 принят

Производительность @@

На ЛОРе до сих пор шутят, что «Java тормозит», а что ж тогда JS?

  • А ничего — он быстрый.
  • Но он же интерпретируемый?
    НЕТ! Интерпретируемых языков уже вообще нет.
    Ну, разве что bash…

Вычислительный бенчмарк @@

  • C++ (g++ 5.3.1) -O2 = 1.124s
  • java8 = 1.428s
  • PyPy (tracing jit) = 1.72s
  • node.js = 2s
  • java7 = ~2.5s
  • PHP 7 = 6.7s
  • Python 3.5 = 19s, 2.7 = 21s
  • Perl = 25s
  • PHP 5.6 = 69s :)))))

Почему V8 такой быстрый? @@

Потому, что 4-слойный JIT!

  • Как уже сказано, интерпретируемых языков нет.
  • 1 слой — LLInt, интерпретатор байткода (быстрый старт)
  • 2 слой — Baseline JIT
  • 3 слой — DFG (Data Flow Graph) JIT
    здесь появляется типизация
  • 4 слой — FTL JIT (LLVM B3)

four_tier_performance.png

Ключевые слова о том, как это всё устроено @@

  • Какой бывает JIT?
    method-based jit (JVM)
    tracing jit (PyPy, TraceMonkey)
  • Девиртуализация
  • Ускорение поиска в хеше (Lua)
  • OSR (On-Stack Replace)

Отступление: PyPy @@ %%

Трассирующий JIT-компилятор для Python, очень медленный

Рисует множество Мандельброта при сборке

2c32601f8747c5e93becc08270fa5127.png

LLVM @@

LLVM (http://llvm.org), ранее «Low Level Virtual Machine»

  • Набор библиотек для построения компиляторов/интерпретаторов
  • Модульный
    исходник → фронтенд (ЯП) → LLVM IR (SSA)
    IR → оптимизатор LLVM → IR
    IR → бэкенд → машинный код
  • А также сами компиляторы (в первую очередь C/C++/ObjC: Clang)
  • На LLVM сделаны компилятор шейдеров Radeon и OpenCL

LLVMCompiler1.png

SSA @@

Пример LLVM IR:

  store i32 1, i32* %e, align 4
  br label %4
; <label>:4                                       ; preds = %29, %0
  %5 = load i32* %a, align 4
  %6 = load i32* %b, align 4
  %7 = add nsw i32 %5, %6
  store i32 %7, i32* %c, align 4
  %8 = load i32* %c, align 4
  %9 = load i32* %a, align 4
  %10 = sub nsw i32 %8, %9
  store i32 %10, i32* %d, align 4
  %11 = load i32* %d, align 4
  %12 = icmp ne i32 %11, 0
  br i1 %12, label %13, label %14
; <label>:13                                      ; preds = %4
  br label %20

V8 JIT @@

  • Первый раз функции запускаются в LLInt
  • 6 вызовов либо 100 повторов строки
    → OSR в Baseline JIT
  • C*66 вызовов либо C*1000 повторов строки
    → OSR в DFG JIT
    C ~ 1, больше для больших функций
  • Нарушение type guard в DFG
    → OSR обратно в Baseline JIT
  • C*6666 вызовов либо C*100000 повторов строки
    → OSR в LLVM/B3 JIT

Производительность @@

  • Итого, V8 — «смешанный» JIT
  • В Firefox — тоже всё шустро
  • Чакра Наделлы тоже очень похожа на V8
  • Постоянная битва :) https://arewefastyet.com/
  • Но язык - ещё не всё! Ещё есть:
    В браузере — DOM (медленный в старых Firefox при формально быстром JS)
    На сервере — ввод/вывод

I/O bound, а не CPU bound @@

  • Типичные веб- и бизнес- приложения сетевые
    «получить из одного места (базы, кэша) и переложить в другое / отдать клиенту»
    nb: все наши веб-фреймворки ни фига не делают :)
    nb: иначе PHP-сайтики были бы неюзабельны
  • Иногда — очень сетевые (C10K / C1M)
    чисто русский термин «хайлоад»
    10gbit ethernet уже среди нас
  • ⇒ что же делать?..

Событийные машины! @@ %%

("event loop")

Как вообще обрабатываются соединения клиентов? @@

Все писали сетевой сервер на C? :)

Обычный (блокирующий) ввод/вывод:

  • forking: socket(), accept(), fork(), дочерний процесс работает с клиентом
  • threading: socket(), accept(), создаём поток, дочерний работает с клиентом
  • prefork / thread pool: создаём N процессов/потоков заранее
  • Потоки и процессы — объекты ОС
    разница только в изоляции памяти
    1000 потоков — уже тяжело (context switch)
    «Проблема C10K» (обработать 10000 соединений на 1 сервере)

Событийная машина @@

Неблокирующий ввод/вывод:

  • socket()
  • select() / poll() / epoll / kqueue
    говорим ОС: «разбуди, когда в любом из сокетов что-то произойдёт»
    сокет пока один (слушающий)
  • новое соединение => добавляем в select и спим дальше
  • кто-то прислал запрос => читаем, быстро обрабатываем, отвечаем, спим дальше
  • всё в один поток (или в несколько по числу ядер CPU)

Так работает Nginx… @@ %%

…и весь современный Web (фронтенды)

Nginx-logo.png

(плюс zero-copy и раздача статики через sendfile())

А если пойти дальше? @@

Кроме HTTP-запросов клиентов ещё есть:

  • СУБД/кэши
  • Файлы
  • REST сервисы
  • Websocket’ы
  • Сетевые серверы (IMAP?)

Почему бы всё это не обрабатывать в одном цикле?

⇒ Это и будет «событийная машина»

Событийные машины @@

Почти везде опциональная сущность:

  • Python: Twisted
    более-менее популярен
  • Java: Vert.x, Webbit
    мало кто использует, ынтырпрайз же, а тут хипстота какая-то
    да, внутри wildfly тоже event loop — но только для HTTP
    в JEE — потоки во все поля
  • PHP: kak.serpom.po.yaitsam/phpdaemon
    почти никто не использует
  • Механизмы разные в разных ОС ⇒ libevent, libev

Событийная машина node.js @@

  • Отличительная черта: вообще нет блокирующего I/O
    тут-то колбэки JS и становятся преимуществом
  • Реально работает: тест 1M соединений на 1 сервере
  • Кроме node.js такое есть только в Erlang
    и, кстати, успешно переваривает 1M соединений, причём на продакшне (у WhatsApp)
    но... erlang. и медленный он по сравнению с V8