Хабрахабр

Когда TypeScript превосходит JavaScript в тестах на скорость

Этот пост я пишу в ответ на этот, где сравниваются разные тесты производительности, в том числе одних и тех же алгоритмов, написанных на TypeScript и JavaScript. Как известно многим, первый при релизе переводится во второй. У TypeScript нет своей нативной поддержки в браузерах, нет собственного движка. Более того, многие плюшки этого языка при транспилировании отбрасываются, чтобы получить чистый JS, который можно запускать во всех браузерах (если хотите, даже в Explorer). Хорошо. А теперь смотрите на картинку.

Код практически одинаковый, единственное отличие — в JS-версии отсутствует информация о типах переменных. Как вы думаете, что произошло? Но разрыв в скорости — фундаментальный.

Но нет, просто под Хромом версия на JS работала 250 секунд, а транспилированная из TS — 15 секунд. Сначала я тестировал на 10 миллиардах циклов и мне показалось, что браузер просто завис. Это может взорвать мозг и мне это действительно взорвало, хотя я уже знал об этой особенности TypeScript.

Вот код на TypeScript в текстовом виде:
Давайте посмотрим, что произошло.

const fn = (x : number, y : number) => x+y;
console.log(`start `);
let t1 = Date.now();
let sum = 0;
for(let i=0;i<1000000000;i++){ sum = fn(sum, i);
}
console.log(`end. time $ seconds`);

Я взял функцию суммирования, так как она была приведена mayorovp в комментариях к исходному посту о тестах. Действительно, я полностью согласен, что следующие виды записи в TypeScript при переводе в продакшен, то есть в JS-виде, ничем не будут отличаться:

const fn = (x : number, y : number) => x+y;
const fn = (x , y) => x+y;

Я немного сократил тот пример, чтобы показать, что информациях о типах действительно отбрасывается и в финальном билде на JS мы получим одно и то же. И вот то, что мы получаем, и является секретом высокой производительности TypeScript в тестах:

console.log("start ");
for(var n=Date.now(),r=0,o=0;o<1e9;o++)r=r+o; // вот она, наша функция sum
console.log("end. time "+(Date.now()-n)/1e3+" seconds")

Да, это известный прием — инлайнинг функций в цикле, то есть запись непосредственно их кода вместо вызова. Вызов функции TypeScript-транспилер превратил в простую операцию суммирования прямо в теле цикла, что и вызвало превосходство в производительности над кодом, написанным просто в JS. Так можно писать и самому, но код от инлайнинга разбухает и во время коде-ревью
становится так жарко, что месячный запас розог уходит за два часа, а ведь впереди еще дедлайны!

Эти операции часто не очевидны и, к примеру, const не является аналогом inline в С++ (хотя для данных, не для функций, подстановка по месту происходит практически всегда вместо обращения к переменной — это имеет значение, так как скорость обращения к локальным и глобальным переменным отличается). Создатели TypeScript думают над оптимизацией кода при транспайлинге — по крайней мере, на данном этапе развития языка, за что им огромное спасибо.

Более того, если из одной функции вызывать другую — скорее всего, одна из функций не будет развернута и будет вызываться в цикле. Для функций подстановка по месту вызова не зависит от const — функция суммирования из примера в цикле будет превращаться просто в оператор сложения при разных формах записи, включая не-стрелочную форму. Некоторые операции очевидны, некоторые — непонятны. Я предлагаю самостоятельно изучить разные короткие алгоритмы в TypeScript и посмотреть, в какой код на js они превращаются в итоге.

Вот это и есть ответ на вопрос, заданный zolotyh — как именно может typescript работать быстрее, чем javascript, и при этом кушать в несколько раз меньше памяти? Но эти оптимизации — есть, и именно они могут создавать эффект превосходства вроде бы тех же самых алгоритмов.

Хотя скорее всего, это не единственная причина и отнюдь не гарантия ультимативного превосходства вашего проекта на TypeScript по сравнению с проектом коллеги, пишущего на ES6.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть