Less.js: Версия 3.10.x использует значительно больше памяти и значительно медленнее, чем 3.9.0.

Созданный на 12 сент. 2019  ·  95Комментарии  ·  Источник: less/less.js

Наши сборки недавно начали давать сбой, потому что мы запускаем около 80 сборок Less параллельно в процессе сборки нашего проекта, а новая версия Less.js использует так много памяти, что Node аварийно завершает работу. Мы отследили сбой до обновления Less.js с 3.9.0 до 3.10.3.

Я изменил наш скрипт Less для последовательной компиляции файлов (создание 2 файлов за раз) и произвел выборку использования памяти узлом во время процесса и получил следующие результаты:

less graph

Похоже, что Less.js теперь использует на 130% больше памяти и нам требуется примерно на 100% больше времени для компиляции.

Просто интересно, тестировали ли вы Less.js и видите ли вы аналогичные результаты

Самый полезный комментарий

Хорошо, команда! Я собираюсь опубликовать сборку 3.x, а когда-нибудь позже, может быть, на следующей неделе, опубликую 4.0. У обоих теперь должны быть исправления производительности. Спасибо всем, кто помогал отлаживать!

Все 95 Комментарий

Это странно. Какая у вас версия Node?

@ PatSmuk360 Не могли бы вы протестировать и получить профиль памяти 3.10.0 и посмотреть, отличается ли он?

Мы используем последнюю версию 10 (10.16.3).

Снимок кучи до:

image

Снимок кучи после:

image

Также я пробовал на Node 12.10.0, и это кажется намного хуже, используя 587 МБ памяти за один раз в последовательной сборке.

Профиль процессора до:
CPU-20190916T133934.cpuprofile.zip

image

Профиль процессора после:
CPU-20190916T134917.cpuprofile.zip

image

@ PatSmuk360 Итак, короче и длинно то, что разница между этими версиями заключается в том, что кодовая база была преобразована в синтаксис ES6. Технически это не критическое изменение, поэтому это не была основная версия.

НО ... я подозреваю, что некоторые из преобразований Babel для таких вещей, как синтаксис распределения объектов / массивов, менее эффективны, чем более подробные версии ES5. Вот почему я спрашивал, можете ли вы протестировать 3.10.0, потому что изначально я экспортировал транспилированный пакет, который был совместим с Node 6 и выше, но он нарушил интеграцию с конкретной библиотекой, которая не могла обрабатывать синтаксис класса. Итак, я спустился к узлу 4 без конструкций классов.

Если мы сможем точно определить, какое преобразование ES5 не работает должным образом, мы теоретически могли бы более детально установить эти параметры экспорта Babel для более производительного экспорта.

@ PatSmuk360 Кстати, что это split функция уделяется столько времени?

@ matthew-dean Похоже, это String.prototype.split . Если вы откроете профиль в chrome devtools, вы увидите все данные с цветовой кодировкой. Я пытаюсь изменить профиль так, чтобы он ссылался на https://cdn.jsdelivr.net/npm/[email protected]/dist/less.cjs.js качестве источника, чтобы упростить поиск узких мест. Доступна ли исходная карта для файла от *.cjs.js до *.js ? Кажется, что файл https://cdn.jsdelivr.net/npm/[email protected]/dist/less.min.js.map сопоставляет файл .min с источником ES6. Возможно, мы сможем передать исходную карту инструментам разработки, чтобы выяснить, где транспиляция вызывает узкие места.

Эта строка в конкретном https://github.com/less/less.js/blob/cae5021358a5fca932c32ed071f652403d07def8/lib/less/source-map-output.js#L78, кажется, имеет большое количество времени процессора. Но, учитывая выполняемую им операцию, мне это не кажется неуместным.

У меня нет большого опыта работы с профилями кучи, но что меня выделяет, так это увеличение суммы на (closures), (system), (array), system / Context . Попытка связать это с профилем процессора кажется, что увеличение этих объектов приводит к значительному увеличению времени, затрачиваемого на сборку мусора.

@kevinramharak В общем, преобразование AST, такого как Less, с _n_ глубиной, в сериализованное плоское дерево вывода, такое как CSS, требует создания множества временных объектов. Итак, может случиться так, что при определенных преобразованиях добавляется дополнительное _x_ количество объектов. Даже временно вы можете получить экспоненциальный эффект, когда каждый узел создает даже 2-3x объектов, умноженных на количество узлов, умноженное на количество раз, когда он должен сглаживать правила ... Я мог видеть, как это складывается. Мы, вероятно, были в целом наивны, полагая, что синтаксис ES6 по сути является синтаксическим сахаром синтаксиса ES5. (Вероятно, в этом вообще виноваты разработчики JavaScript.) В действительности, перенос нового синтаксиса может создать не очень производительные шаблоны ES5. Для 99% разработчиков это не имеет большого значения, потому что они не повторяют этот код сотни или тысячи раз в секунду. Но это мое предположение относительно того, что происходит, потому что других серьезных изменений не было.

@kevinramharak Re: исходные строки - это потому, что исходный синтаксический анализатор Less не отслеживает строки / столбцы ввода, поэтому, когда было добавлено сопоставление источника, необходимо было по существу разбить ввод на строки, чтобы выяснить, как он должен отображаться на исходный источник. Это не будет проблемой в 4.x +, но это имеет смысл, почему сейчас он будет проводить там много времени.

Я использую less.min.js в браузере, а 3.10.3 в два раза медленнее, чем 2.7.3, которым я пользовался раньше. И в Chrome, и в Firefox.

@ PatSmuk360 Можете проверить эту ветку? https://github.com/matthew-dean/less.js/tree/3.11.0

Короче говоря, перенос Babel в ES5 ужасен и использует множество вызовов Object.defineProperty для преобразования классов. Я переключил транспиляцию на TypeScript, который дает гораздо более разумный вывод прототипов функций.

Это все нормально, но после этого тесты браузера Less не будут запускаться, потому что он использует очень старый и устаревший PhantomJS, и до сих пор никто (включая меня) не смог успешно перенести тесты с PhantomJS. на Headless Chrome.

Но, по одной проблеме за раз: если у источника dist в этой ветке нет проблем с памятью, тогда, возможно, мы могли бы решить беспорядок, связанный с тестированием браузера.

Мне удалось перенести меньше браузерных тестов на Headless Chrome, но с точки зрения производительности / стабильности мне нужно много отзывов от пользователей, прежде чем объединение станет безопасным, из-за полного изменения конвейера транспиляции с Babel на TypeScript.

Текущие ветки можно найти здесь: https://github.com/less/less.js/pull/3442

По-прежнему в 2 раза медленнее, чем 2,7.

@alecpl Это хорошая информация, но на самом деле я хочу знать, является ли 3.11.0 улучшением по сравнению с 3.10.

Странно то, что _ теоретически_ исходный код Less был преобразован с помощью Lebab, что должно быть противоположностью Babel. Это означает, что ES5 -> ES6 -> ES5 должны быть почти идентичными, но это, очевидно, не так. Поэтому мне нужно будет изучить (если у кого-то еще нет времени, что приветствуется), чем код ES6-> ES5 отличается от исходного кода ES5.

@alecpl

Итак, я провел множество тестов и потратил время на тестирование производительности 3.11.0, 3.10.3, 3.9.0 и 2.7.3 в Headless Chrome.

Я не нашел доказательств того, что компилятор Less работает медленнее для любой версии, не говоря уже о том, что в 2 раза медленнее. Все еще может быть правдой, что 3.10.0 имеет больше накладных расходов на память из-за настроек транспиляции, и, может быть, если система была ограничена по пространству и в результате выполняла больше подкачки памяти или больше сборщика мусора, это могло бы привести к замедлению? Но я не могу это проверить.

Вы можете проверить себя, запустив grunt benchmark в ветке 3.11.0. Они могут сообщать разные числа для каждого отдельного пробега, но если вы пробежите достаточно много раз, вы увидите, что времена примерно одинаковы. Поэтому я не знаю, откуда вы берете свои данные.

@ PatSmuk360 Удалось ли вам протестировать накладные расходы памяти в 3.11.0?

Я не создаю ваш код самостоятельно. Я не использую nodejs. Я просто беру файл less.min.js из папки dist для двух упомянутых мною версий и использую их на своей странице. Затем в консоли я вижу тайминги, напечатанные кодом Less. Мой код less использует несколько файлов, а выходной файл составляет около 100 КБ минимизированного CSS. Это эталон «из реальной жизни». Я говорю о коде Roundcube .

Chrome намного быстрее Firefox, но разница между версиями одинакова.

@alecpl Хм .... может быть, это особая разница для этого кода Less. Это правда, что тест произвольный. Если у меня будет время, я добавлю эти файлы Less для тестирования.

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

Просто хотел опубликовать быстрое обновление: я попробовал 3.11.1, и он был таким же медленным, как 3.10.3. Я посмотрю, смогу ли я приготовить репрезентативный тест для тестирования / профилирования.

Я обновился до 3.11.1, и теперь у меня такие же проблемы с потреблением памяти.
Небольшой по размеру проект требует ~ 600 МБ ОЗУ для сборки через webpack и less-loader .

Я взял временную шкалу распределения кучи, что привело к этому;

heap-timeline

Что-то вызывает сумасшедшие огромные ассигнования, поддерживаемые Ruleset .


[РЕДАКТИРОВАТЬ]
Я понижаюсь до 3.9 и беру временную шкалу выделения из кучи. Посмотрим, замечу ли я что-нибудь радикально иное.

@ Мэтью-Дин
Нашел для вас подсказку.

В 3.11 одним из перечисленных фиксаторов для RuleSet является ImportManager.
В 3.9 это не тот случай.

Если все поддерживается с помощью ImportManager, а ImportManager - это синглтон на протяжении всего процесса компиляции. Ну да; это значительно увеличило бы использование памяти, потому что сборщик мусора ничего не может сделать. Даже промежуточные наборы правил.

@rjgotten Хм ... если у объекта есть ссылка на другой объект, почему это помешает

Вы, кажется, неправильно поняли.

Когда «A является хранителем B», это означает, что A сохраняет ссылки на B, которые предотвращают сборку мусора B. Поэтому, когда я написал, что ImportManager указан как хранитель набора правил, я точно выразил то, что вы также пришли к выводу: ImportManager содержит ссылки на экземпляры набора правил, что предотвращает сборку мусора этих экземпляров набора правил.

@rjgotten О, это так? Интересно, как это изменение произошло? Скорее всего, виноват я, или есть что-то в рефакторинге ES6, который сделал это. Но да, это определенно могло бы сделать это! Спасибо за расследование!

Хорошо, что насчет этой радикальной идеи.

Я создал ветку с вишней, собирающую все, ЗА ИСКЛЮЧЕНИЕМ преобразования ES6. Это было непросто, и это свело бы на нет всю эту работу, но если файл Babelified / Typescript'd не может превзойти существующий собственный JS, то это того не стоит.

Понятия не имею, как мы свяжем историю git, но вот ветка -> https://github.com/less/less.js/tree/release_v3.12.0-RC1. Внедрение конверсии - это большое дело, поэтому я думаю, что эта ветка будет действительно надежным тестом для сравнения.

Интересно, как это изменение произошло? Скорее всего, виноват я, или есть что-то в рефакторинге ES6, который сделал это. Но да, это определенно могло бы сделать это! Спасибо за расследование!

Это конечно странно. Судя по тому, что мне удалось расшифровать, похоже, что ImportManager каким-то образом оказывается фиксатором для наборов правил через gluecode / polyfill, добавленный преобразованием ES6.

Интересно, есть ли здесь решение, которое встречает концы с концами на полпути. Т.е. сохранить сборку ES6 для Node.js, но также иметь перенесенную цель браузера. Было бы огромной потерей избавиться от ясности кода, которую добавляет преобразование ES6. 😢

@rjgotten

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

Это может быть не так плохо, как кажется, по двум причинам:

  1. Конфигурация накопительного пакета все еще существует, и ее можно извлечь из фиксации, если кто-то захочет ее изучить.
  2. Некоторое время я активно работал над Less 4.0 на основе TypeScript. Я лучше потрачу на это свое время, чем буду отслеживать регресс производительности. Это предварительный рефакторинг, поэтому он никоим образом не близок, но по природе TS вряд ли будут эти разовые мутации объекта, которые сохраняют ссылки и предотвращают сборку мусора. И код становится более понятным . Так вот что.

Кажется, что откатить все обратно на ES5 только для того, чтобы исправить эту проблему, _bit_ тяжеловесно, но это понятно, учитывая, что сейчас выполняется перезапись. Тем не менее, мы должны подумать, как долго нам придется жить с этим решением. Как упоминал @rjgotten , мы

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

@ matthew-dean, кстати, вы пытались скомпилировать с Babel в свободном режиме, чтобы увидеть, производит ли он какой-нибудь более разумный код?

@seanCodes Я полностью за то, чтобы не использовать ядерный вариант, если у кого-то есть время, чтобы выяснить, как / почему вывод Rollup / Typescript работает хуже и имеет эти дополнительные эффекты памяти. Частично это будет связано с просмотром исходного кода, сравнением с выводом и поиском подсказок.

Одно предположение, которое я имею, заключается в том, что в некоторой логике деструктуризации есть много шаблонов для создания объектов.

По сути, кто-то должен был бы добровольно взять на себя эту проблему.

@ matthew-dean, кстати, вы пытались скомпилировать с Babel в свободном режиме, чтобы увидеть, производит ли он какой-нибудь более разумный код?

@seanCodes Код компилируется с использованием TypeScript, а не Babel. Моя идея заключалась в том, чтобы постепенно добавлять типы JSDoc, чтобы усилить проверку типов, но TS в v4 достаточно переписать, я не уверен, что это необходимо. Итак, кто-то может поэкспериментировать с Babel v. TypeScript (и разными настройками для каждого), чтобы увидеть, производит ли Babel более производительный код. Просто помните об этой проблеме, которая была вызвана изначально созданием сборки, отличной от ES5, для Node.js: https://github.com/less/less.js/issues/3414

@seanCodes Кроме того, мне все еще трудно ПРОВЕРИТЬ разницу в производительности. Никто не подготовил PR / шаги, чтобы окончательно ДОКАЗАТЬ разницу в производительности. В этой теме есть несколько анекдотов, но без воспроизводимого кода или шагов я не уверен, как кто-то исследует это. В идеале должен быть PR, который запускает инструмент профилирования (через отладчик Chrome или иным образом) для вывода числа в систему, усредненного по ряду тестов. Так как это до сих пор не воспроизводимо на 100%, насколько кто-либо мог предложить шаги воспроизводства, отчасти поэтому я лично не хотел спускаться в эту кроличью нору. (Другими словами, отзыв «Я использовал отладчик Chrome» не является набором шагов воспроизведения. Об этом полезно знать, но это не помогает кому-то исследовать. Нам нужно знать, что мы отслеживаем и почему / каков результат ожидал.)

Итак, если люди хотят улучшить меньшую производительность, используя текущую кодовую базу, вероятно, потребуется несколько добровольцев и один человек, чтобы решить эту проблему.

Лично я не заметил особой разницы в производительности, как в _speed_, но разница в потреблении памяти поразительна и, кажется, коррелирует с объемом импорта, что, как показывает мой беглый анализ кучи, также связано с проблемой.

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

@rjgotten

Если это верно, вы сможете собрать тестовый проект с большим количеством импортированных файлов, каждый из которых содержит несколько наборов правил, и также увидеть разницу.

«Вы» здесь - важная часть. 😉

«Вы» здесь - важная часть. 😉

Неудачный выбор слов. Я имею в виду это в общем смысле - то есть «кто угодно».
Сам бы копался дальше, но у меня уже слишком много тарелок крутится.

@rjgotten Можете ли вы дать мне лучшее представление о том, что такое «много импортированных файлов»? Будет ли это 100 отдельных файлов, или мы говорим о 1000?

Кроме того, как вы впервые заметили проблему? Сборка завершилась неудачно из-за потребления памяти или вы смотрели на использование памяти? Или вы заметили, что ваша машина замедлилась?

Я еще не пытался воспроизвести это, но я все еще надеюсь узнать, что именно искать, чтобы мне не пришлось прибегать к догадкам и проверкам.

100 или около того сделали бы это. Это примерно соответствует размеру реального проекта, в котором я заметил проблему.

Я впервые заметил это, когда сборка, запущенная в нашей корпоративной среде CI, начала давать сбой. Мы запускаем их внутри контейнеров Docker с настроенными ограничениями памяти.

@rjgotten У нас все еще проблема с памятью 3.11.3? Я удалил любое кеширование (ссылки) AST в импорте в предыдущем выпуске, поэтому, если импорт удерживался, и деревья AST удерживались в нем, тогда это увеличило бы использование памяти, но если мы отбросим деревья из удержания, это решает это?

@ matthew-dean Да, 3.11.3 проблема все еще существует.

Я попытаюсь создать доказательство концепции, но у меня много дел. Я внесу это в свой список дел, когда у меня появится дополнительное время.

@ matthew-dean Я хочу протестировать это на относительно большом проекте, который ранее терпел неудачу. Что-нибудь мне нужно знать? Стоит ли использовать эту ветку https://github.com/less/less.js/tree/release_v3.12.0-RC1 ?

@nfq Вы можете попробовать это, но преобладающая мудрость в этом потоке - не взрывать преобразование ES6, как это делает эта ветка, а вместо этого получить дополнительную информацию для правильной диагностики проблемы.

Между прочим, я попытался проверить объект less во время компиляции в попытке найти какие-либо постоянные объекты, но не смог их найти. 🤷‍♂️

У меня тоже проблемы с производительностью. Для того же набора тестов:

| Версия | Время |
| - | - |
| v3.9.0 | ~ 1,6 с |
| v3.10.0 ~ v3.11.1 | ~ 3,6 с |
| v3.11.2 + | ~ 12сек |

Помимо обсуждаемых здесь 3.9.0 → 3.10.0, похоже, наблюдается значительное снижение производительности при v3.11.2 . Это изменение в журнале изменений кажется подозрительным:

3498 Удалить кеширование дерева в диспетчере импорта (# 3498)

Мне то же.

Сроки для одинаковых сборок (все на узле 10.19.0):

  • v3.9: 29.9 сек.
  • v3.10: 76.0 сек.
  • v3.12: 89,3 секунды

@ jrnail23

Есть ли у вас репо, которое может продемонстрировать эти результаты?

@ matthew-dean У меня есть один для https://github.com/less/less.js/issues/3434#issuecomment -672580467: https://github.com/ecomfe/dls-tooling/tree/master/packages/ less-plugin-dls (это монорепозиторий, включающий плагин Less.)

Извини, @ matthew-dean, я не знаю. Эти результаты получены от продукта моего работодателя.

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

По той же причине я не могу предоставить файлы. 😞

@rjgotten @ jrnail23 @Justineo - Просто любопытно, бывают ли у вас случаи, когда один и тот же импорт импортируется несколько раз в разные файлы?

В моем случае у нас есть плагин, который вводит оператор @import и предоставляет набор настраиваемых функций. Импортированный файл импортирует другие части проекта, которые, в конечном итоге, будут производить на 1000+ меньше переменных.

@ matthew-dean, у нас есть файл base.less который импортирует обычные вещи (например, переменные цвета, типографику и т. д.).
Некоторые быстрые поисковые запросы нашего приложения показывают, что ссылка base.less (т.е. <strong i="8">@import</strong> (reference) "../../../less/base.less"; ) импортируется 66 другими файлами less , специфичными для компонентов / функций, и некоторые из этих файлов могут также импортировать другие файлы, специфичные для компонентов / функций (которые сами могут ссылаться на base.less ).
Интересно, что у нас, по-видимому, есть еще один файл less который (вероятно, случайно?) Импортирует себя в качестве ссылки.

@rjgotten @ jrnail23 @Justineo - Просто любопытно, бывают ли у вас случаи, когда один и тот же импорт импортируется несколько раз в разные файлы?

Для меня это «да». Проект, в котором я впервые столкнулся с проблемами, - это решение с изменяемым оформлением, в котором используется централизованный файл переменных, включенный во все остальные. Более того, мы используем дизайн на основе компонентов, где у нас есть фабрики миксинов, которые производят, например, определенные типы кнопок, значков шрифтов и т. Д., И их части также импортируются в несколько файлов.

По сути; каждый зависимый настроен для строгого импорта своих зависимостей, и мы полагаемся на компилятор Less для устранения дублирования и упорядочения импорта в правильном порядке и обеспечения правильного вывода CSS.

@rjgotten

Я опубликовал и удалил его (потому что я думал, что понял это, но я все еще не мог его воспроизвести), по-прежнему нет никаких шагов для воспроизведения в том, что люди сообщают, в том числе о том, какой процесс.

Даже что-то простое:

В 3.11 одним из перечисленных фиксаторов для RuleSet является ImportManager.

Я не могу найти никаких доказательств этого, но я также не знаю, как вы смогли это определить. Я недостаточно знаком с Chrome DevTools, чтобы знать, как можно определить такую ​​вещь, как то, что сохраняется, и это достаточно расплывчатая тема, и Google мне не помог. Мол, присоединяются ли люди к процессу Node? Запускаете его в браузере и устанавливаете точки останова? Какие шаги?

Короче говоря, за год, когда был открыт этот выпуск, ни один из отчетов не содержит шагов для воспроизведения. Я уверен, что все эти анекдоты означают ЧТО-ТО, но как ВЫ определили, что именно ВЫ нашли? Или вы можете придумать установку, которая продемонстрирует это? Я хотел бы помочь разобраться в этом, но я не знаю, как это воспроизвести.

@Justineo В своем репо, какие шаги вы предприняли, чтобы определить размер памяти или время компиляции? Что вы использовали для измерения?

@Justineo Поскольку вы указали на удаление кеша (что, возможно, было плохой идеей), можете ли вы протестировать эту ветку и посмотреть, помогает ли она вашей скорости сборки? https://github.com/less/less.js/tree/cache-restored

Просто чтобы дать несколько обновлений о сегодняшних экспериментах / тестировании:

shell:test раз Гранта

  • Менее 3,9–1,8 с
  • Менее 3,12 (Вавилон, перенесенный на ES5) - 9,2 с
  • Менее 3,12 (Вавилон, перенесенный на ES6) - 3,1 с
  • Менее 3,12 (TypeScript-перенесен на ES5) - 3,2 с

Итак, я думаю, что краткое и длинное состоит в том, что транспилированный код всегда медленнее, и мы были наивны, чтобы думать иначе. Я немного удивлен, потому что транспиляция теперь стала такой «стандартной» в мире JavaScript. Есть ли другие исследования, подтверждающие это?

@Justineo В своем репо, какие шаги вы предприняли, чтобы определить размер памяти или время компиляции? Что вы использовали для измерения?

npm run test покажет общее прошедшее время. А в других проектах мы испытываем OOM при переходе на 3.12.

Если мы посмотрим на код, сгенерированный TypeScript, мы получим что-то вроде:

Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
var node_1 = tslib_1.__importDefault(require("./node"));
var variable_1 = tslib_1.__importDefault(require("./variable"));
var property_1 = tslib_1.__importDefault(require("./property"));
var Quoted = /** <strong i="6">@class</strong> */ (function (_super) {
    tslib_1.__extends(Quoted, _super);
    function Quoted(str, content, escaped, index, currentFileInfo) {
        var _this = _super.call(this) || this;
        _this.escaped = (escaped == null) ? true : escaped;
        _this.value = content || '';
        _this.quote = str.charAt(0);
        _this._index = index;
        _this._fileInfo = currentFileInfo;
        _this.variableRegex = /@\{([\w-]+)\}/g;
        _this.propRegex = /\$\{([\w-]+)\}/g;
        _this.allowRoot = escaped;
        return _this;
    }

против:

var Node = require('./node'),
    Variable = require('./variable'),
    Property = require('./property');

var Quoted = function (str, content, escaped, index, currentFileInfo) {
    this.escaped = (escaped == null) ? true : escaped;
    this.value = content || '';
    this.quote = str.charAt(0);
    this._index = index;
    this._fileInfo = currentFileInfo;
    this.variableRegex = /@\{([\w-]+)\}/g;
    this.propRegex = /\$\{([\w-]+)\}/g;
};

Итак, я предполагаю, что все эти дополнительные определения функций и вызовы со временем складываются? Если это не отвлекающий маневр, но я не знаю, к чему еще это отнести, кроме транспиляции. Я не понимаю, почему TS не может создать код, близкий к оригиналу.

ОБНОВИТЬ:

Если я попытаюсь сопоставить тесты 3.9 с тем, что было в 3.12, то я получу 1,2 с против 1,3. Так что я больше не уверен в этой разнице, потому что тесты изменились. Это должно быть запущено с тем же самым меньшим количеством файлов.

@Justineo @rjgotten Я https://github.com/less/less.js/tree/cache-restored

@ matthew-dean 👍 Спасибо! Попробую сегодня позже.

Я тестировал ветку cache-restored и она намного быстрее, чем v3.11.2 +, примерно с той же скоростью, что и v.3.1.0 ~ 3.11.1.

@Justineo

Я тестировал ветку с восстановлением кеша, и она намного быстрее, чем v3.11.2 +, примерно с той же скоростью, что и v.3.1.0 ~ 3.11.1.

Что ж, это многообещающе. Давайте получим отзывы от @rjgotten @ jrnail23 и других в этой теме. Этот кеш был удален после публикации этой ветки (3.11.2); на самом деле это была попытка удалить часть накладных расходов на память, но в случаях, когда вы импортируете один и тот же файл несколько раз, это определенно могло ухудшить ситуацию.

Короче говоря, я все еще не уверен в исходной проблеме или ее причине (кроме ЧТО-ТО, что произошло при преобразовании кода), и мне было бы любопытно узнать, есть ли в этой ветке все еще эти проблемы, но, как я уже упоминал ранее , нет четких шагов воспроизведения, поэтому я не уверен.

@ matthew-dean, у меня проблемы с использованием ветки cache-restored (я не совсем понимаю, что нужно сделать, чтобы использовать ее локально, так как npm link не работает меня).
Можете ли вы опубликовать канареечную / предварительную версию, которую я могу попробовать?

@ jrnail23

Я думаю, это сработало. Попробуйте удалить Less и установить с помощью npm i [email protected]+84d40222

@ matthew-dean, я только что попробовал эту версию, и она мне все еще плохо.
Для свежих сборок webpack (без кеширования) требуется 62,4 секунды для v3.9, но 121 секунда для v3.13.1.
Для кешированных сборок v3.9 занимает 30 секунд, а v3.13.1 - 83-87 секунд.

@ jrnail23 Можете ли вы попробовать удалить все модули узлов и установить [email protected]+b2049010

Минус 3,9 эталон:
Screen Shot 2020-12-05 at 2 36 09 PM

Менее 4.0.1-alpha.0:
Screen Shot 2020-12-05 at 1 12 26 PM

Менее 4.0.1-alpha.2:
Screen Shot 2020-12-05 at 2 35 20 PM

@Justineo @rjgotten Ты

@ jrnail23 @Justineo @rjgotten

Обратите внимание, что это сборка 4.0, поэтому вы можете столкнуться с ошибками, если в вашем коде есть следующие критические изменения:

  • Скобки необходимы для вызовов миксинов (например, .mixin; не допускается)
  • Математический режим по умолчанию для Less теперь представляет собой разделение скобками, поэтому косые черты (предназначенные как математические) должны быть в круглых скобках.

Итак, хотя я теперь более оптимистичен, я исправил проблему на основе последних тестов, но мне еще предстоит выяснить, почему эта проблема возникает. Если производительность останется у всех остальных, я могу рассказать, как я, наконец, сузил проблему и что нашел. Другими словами, я думаю, что нашел _ где_ проблема / была, но не обязательно почему.

Результат того же набора тестов, что и https://github.com/less/less.js/issues/3434#issuecomment -672580467:

| Версия | Время |
| - | - |
| v3.9.0 | ~ 1,6 с |
| v3.10.0 ~ v3.11.1 | ~ 3,6 с |
| v3.11.2 + | ~ 12сек |
| 4.0.1-alpha.2 + b2049010 | ~ 1,6 с |

Я могу подтвердить, что для моего конкретного набора тестов уровень производительности повысился до уровня v3.9.0. Очень признателен Мэтт! Пока я не уверен в изменении перерыва в математическом режиме. Изменение этого может привести к поломке многих наших приложений, поэтому мы можем застрять на версии 3.9.0, даже если проблема с производительностью будет решена.

@Justineo

Пока я не уверен в изменении перерыва в математическом режиме.

Вы можете явно скомпилировать в режиме math=always чтобы получить предыдущее математическое поведение. Это просто другое значение по умолчанию.

Разбивка вопроса

TL; DR - Остерегайтесь шаблона класса

Проблема заключалась в переносе классов как в Babel, так и в TypeScript. (Другими словами, у обоих были одинаковые проблемы с производительностью с транспилированным кодом, при этом Babel был немного хуже.) Теперь, много лет назад, когда был представлен шаблон класса, мне сказали - и до этой проблемы я считал, что этот класс наследование в JavaScript - это синтаксический сахар для функционального наследования.

Короче говоря, это не так. _ (Edit: ну .... это так, а это не так. Это зависит от того, что вы подразумеваете под "наследованием" и как вы это определили, как вы вскоре увидите ... т.е. есть несколько шаблонов для создают «наследование» в цепочке прототипов в JavaScript, и классы представляют только один шаблон, но этот шаблон несколько отличается от всех остальных, отсюда потребность TS / Babel во вспомогательном коде для «имитации» этого шаблона с использованием специализированных функций.) _

Меньший код выглядел так:

var Node = function() {
  this.foo = 'bar';
}

var Inherited = function() {
  this.value = 1;
}
Inherited.prototype = new Node();

var myNode = new Inherited();

Теперь предположим, что вы хотели переписать это, используя "современный" JS. Фактически, вы можете автоматизировать этот процесс, что я и сделал, но в любом случае я бы написал то же самое, а именно:

class Node {
  constructor() {
    this.foo = 'bar';
  }
}
class Inherited extends Node {
  constructor() {
    super();
    this.value = 1;
  }
}
var myNode = new Inherited();

То же самое, правда? Вообще-то, нет. Первый создаст объект со свойством { value: 1 } , а в его цепочке прототипов будет объект { foo: 'bar' } .

Второй, поскольку он вызовет конструктор как для Inherited и для Node , создаст объект со структурой типа { value: 1, foo: 'bar' } .

Теперь для _user_ это действительно не имеет значения, потому что в любом случае вы можете получить доступ как к value и к foo из myNode . _Функционально_ они, кажется, ведут себя одинаково.

Вот где я ввожу чистые предположения

Из того, что я помню в статьях о JIT-движках, таких как V8, структуры объектов на самом деле очень важны. Если я создаю группу структур, таких как { value: 1 } , { value: 2 } , { value: 3 } , { value: 4 } , V8 создает внутреннее статическое представление этой структуры. По сути, вы сохраняете структуру + данные один раз, а затем данные еще 3 раза.

НО, если я добавляю к этому каждый раз разные свойства, например: { a: 'a', value: 1 } , { b: 'b', value: 2 } , { c: 'c', value: 3 } , { d: 'd', value: 4 } , то это 4 разные структуры с 4 наборами данных. , даже если они были созданы из одного и того же набора исходных классов. Каждая мутация объекта JS деоптимизирует поиск данных, а шаблон класса, транслируемый в функции, вызывает (возможно) более уникальные мутации. (Честно говоря, я понятия не имею, верно ли это для поддержки собственных классов в браузерах.)

AFAIK, что также верно, так это то, что чем больше свойств у вас есть на объекте, тем больше времени занимает поиск отдельного свойства.

Опять же для конечных пользователей это редко имеет значение, потому что движки JS очень быстрые. Buuuuut говорят, что вы поддерживаете движок, который как можно быстрее создает и ищет свойства / методы для МНОГО объектов. (Ding ding ding.) Внезапно эти небольшие различия между способом, которым TypeScript / Babel «расширяет» объекты, и встроенным функциональным прототипным наследованием складываются очень быстро.

Я написал простую реализацию узла и наследующей функции, используя старый синтаксис Less и шаблон класса, перенесенный с помощью TS. Сразу после этого унаследованный узел потребляет на 25% больше памяти / ресурсов, и это ДО того, как будут созданы какие-либо экземпляры унаследованного узла.

Теперь учтите, что некоторые узлы наследуются от других узлов. Это означает, что представление классов в памяти начинает умножаться, как и их унаследованные экземпляры.

Опять же, это предположение

Я должен подчеркнуть, что воспринимаю все это с долей скептицизма, потому что я никогда не слышал, чтобы преобразование в классы было настолько пагубным для производительности. Я думаю, что происходит некоторая особая комбинация поисков объектов / экземпляров, которую использует Less, которая серьезно деоптимизирует JIT. _ (Если какой-нибудь эксперт по движкам JavaScript знает, почему транспилированные классы в этом случае работают намного хуже, чем собственные методы наследования JS, я хотел бы знать.) _ Я попытался создать показатель производительности для создания тысяч унаследованных объектов с использованием обоих методов, и Я никогда не видел стабильной разницы в производительности.

Итак, прежде чем вы откажетесь от слов «классы в JavaScript - это плохо», это может быть какой-то другой действительно неудачный шаблон в кодовой базе Less в сочетании с разницей в транспилированных классах по сравнению с собственным JS, который вызвал это падение производительности.

Как я наконец понял это

Честно говоря, я никогда не подозревал о шаблоне классов. Я знал, что исходный код ES5 работает быстрее, чем код, преобразованный в обратный Babelified, но я подозревал, что что-то связано со стрелочными функциями или где-то распространенным синтаксисом. У меня все еще была обновленная ветка ES5, поэтому однажды я решил снова запустить lebab и использовать только эти преобразования: let,class,commonjs,template . Именно тогда я обнаружил, что у него снова проблемы с производительностью. Я знал, что это не шаблон строки или let-to-var; Я подумал, что, возможно, требует от импорта что-то, поэтому я немного поигрался с этим. Остались классы. Итак, догадываясь, я переписал все расширенные классы обратно в функциональное наследование. Бам, спектакль вернулся.

Поучительная история

Так! Урок выучен. Если ваш проект основан на старом коде ES5, и вы жаждете этого «современного» совершенства Babelified или TypeScripted, помните, что все, что вы транспилируете, вы не писали.

Я все еще мог переписать классы во что-то более «классовое», с немного более удобным в обслуживании шаблоном , но более или менее сохраняющим исходный шаблон функционального наследования Less node, и я оставлю сильное примечание в проекте для будущий сопровождающий никогда не сделает этого снова.

Вы можете явно скомпилировать в режиме math = always, чтобы получить предыдущее математическое поведение. Это просто другое значение по умолчанию.

Я знаю об этом. У нас есть много кодовых баз Less в разных командах, поэтому несанкционированные изменения даже в параметрах компиляции по умолчанию могут увеличить затраты на коммуникацию. Я не уверен, стоит ли польза от поломки.

Спасибо за подробную разбивку!

Я видел использование Object.assign в скомпилированном выводе, что означает, что он работает только в браузерах, поддерживающих собственный синтаксис ES class если теперь не требуются полифилы. Итак, можем ли мы просто использовать собственный синтаксис без переноса на ES5, если мы намерены отказаться от поддержки старых сред (например, IE11, Node 4, ...)?

В то же время, я думаю, будет лучше, если мы сможем разделить исправление снижения производительности и критические изменения, что означает закрепление исправления производительности в версии 3 и включение критических изменений только в версию 4.

@ Мэтью-Дин
Тот факт, что виноваты классы ES, безумно пугает.
Спасибо за подробный анализ.

Показывает: _композиция важнее наследования_ 😛

Хотя к вашему сведению; если вам нужна более чистая цепочка прототипного наследования, вам действительно следует сделать это немного иначе, чем в вашем примере.
Если вы используете Object.create вот так:

var Node = function() {
  this.foo = 'bar';
}
Node.prototype = Object.create();
Node.prototype.constructor = Node;

var Inherited = function() {
  Node.prototype.constructor.call( this );
  this.value = 1;
}
Inherited.prototype = Object.create( Node.prototype );
Inherited.prototype.constructor = Inherited;

var myNode = new Inherited();

затем вы сглаживаете свойства на самих экземплярах, которые затем имеют ту же форму; прототипы имеют общую форму; _и_ вам не нужно ползать по цепочкам прототипов для каждого доступа к свойству. 😉

@Justineo

Я видел использование Object.assign в скомпилированном выводе, что означает, что он работает только в браузерах, поддерживающих собственный синтаксис класса ES, если теперь не требуются полифилы. Итак, можем ли мы просто использовать собственный синтаксис без переноса на ES5, если мы намерены отказаться от поддержки старых сред (например, IE11, Node 4, ...)?

Я не думаю, что это правильно, т.е. Object.assign приземлился незадолго до реализации классов, но ваша точка зрения принята. Я просто надеялся избежать повторения [Something].prototype.property снова и снова: /

Технически все еще транспилируется, так что я не знаю. Первоначальной целью была более удобная в обслуживании / читаемая кодовая база. Если вам нужен полифилл Object.assign в какой-то среде, пусть будет так. Это будет версия чего-то, что Less не поддерживает.

В то же время, я думаю, будет лучше, если мы сможем разделить исправление снижения производительности и критические изменения, что означает закрепление исправления производительности в версии 3 и включение критических изменений только в версию 4.

Я думал об этом, и, думаю, это тоже справедливо. Я просто пытаюсь заставить свою голову не взорваться, строя / поддерживаю 3 основные версии Less.

@rjgotten AFAIK , это то, что должен делать шаблон класса, точно так, как вы его определили. Если только я не вижу критической разницы. Итак 🤷‍♂️. Я не собираюсь снова пытаться изменить шаблон наследования узлов Less, если он работает хорошо (кроме того, что я могу удалить этот вызов Object.assign как предложил @Justineo .)

@Justineo @rjgotten Можете ли вы попробовать это: [email protected]+b1390a54

Просто попробовал:

| Версия | Время |
| - | - |
| v3.9.0 | ~ 1,6 с |
| v3.10.0 ~ v3.11.1 | ~ 3,6 с |
| v3.11.2 + | ~ 12сек |
| 4.0.1-alpha.2 + b2049010 | ~ 1,6 с |
| 3.13.0-alpha.10 + b1390a54 | ~ 4,7 с |

Пс. Протестировано с помощью Node.js v12.13.1.

Я что.

Не было времени для настройки отдельных тестов.
Но с точки зрения тестирования интеграции, вот некоторые реальные цифры: совокупные результаты проекта Webpack производственного уровня, в котором используется Less.

Версия | Время | Пиковая память
: ------------------------ | --------: | ------------:
3.9 | 35376 мс | 950 МБ
3.11.3 | 37878 мс | 920 МБ
3.13.0-alpha.10 + b1390a54 | 34801 мс | 740 МБ
3.13.1-alpha.1 + 84d40222 | 37367 мс | 990 МБ
4.0.1-alpha.2 + b2049010 | 35857 мс | 770 МБ

Для меня 3.13.0 дает _самые_ результаты ... 🙈

3.11.3 также, похоже, не использует неконтролируемую память, которую я видел раньше с версией 3.11.1.
Но бета-версии 4.0.1 и 3.13.0 еще лучше.

Версия 3.13.1, которая восстановила кеш, не помогает мне улучшить реальное время компиляции и просто увеличивает использование памяти.

@rjgotten Да, я думаю, проблема в том, что в этой ветке люди измеряют разные вещи, и до сих пор у всех есть личные данные, которые невозможно реплицировать (без выполнения бенчмаркинга Less или перевода этого в PR). У Less есть тестовый тест, который я мог бы использовать для разных клонов репозитория, чтобы проверить для себя различия во времени синтаксического анализа / оценки, но там, где кеширование (в настоящее время) не применяется.

Вот опубликованная сборка с изменениями наследования в дереве и восстановленным кешем дерева синтаксического анализа: [email protected]+e8d05c61

Прецедент

https://github.com/ecomfe/dls-tooling/tree/master/packages/less-plugin-dls

Полученные результаты

| Версия | Время |
| - | - |
| v3.9.0 | ~ 1,6 с |
| v3.10.0 ~ v3.11.1 | ~ 3,6 с |
| v3.11.2 + | ~ 12сек |
| 4.0.1-alpha.2 | ~ 1,6 с |
| 3.13.0-alpha.10 | ~ 4,7 с |
| 3.13.0-alpha.12 | ~ 1,6 с |

Версия Node.js

Версия 12.13.1


Мне кажется, что эта версия работает отлично. Попрошу коллег попробовать и проверить.

У меня работает немного хуже, чем alpha.10 , но также может быть дисперсией:

Версия | Время | Пиковая память
: ------------------------ | --------: | ------------:
3.9 | 35376 мс | 950 МБ
3.11.3 | 37878 мс | 920 МБ
3.13.0-alpha.10 + b1390a54 | 34801 мс | 740 МБ
3.13.0-alpha.12 + e8d05c61 | 36263 мс | 760 МБ
3.13.1-alpha.1 + 84d40222 | 37367 мс | 990 МБ
4.0.1-alpha.2 + b2049010 | 35857 мс | 770 МБ

@ matthew-dean, похоже, мой код еще не совместим с изменениями 4.0.1-alpha.2+b2049010 . Я посмотрю, смогу ли я решить свои проблемы, и попробую.

@rjgotten Да, для вашего тестового случая разница кажется незначительной.

Я думаю, что в результате я могу подготовить релизы как 3.x, так и 4.0. Мне не хотелось выпускать 4.0 с нерешенной проблемой. К счастью, похоже, что это было так.

Другой пункт, который я сделаю, - это открыть задачу, которую может схватить любой, а именно вставить конвейер CI, который не работает Less, если он падает ниже определенного порогового значения.

@ jrnail23 Было бы здорово, если бы вы могли внести изменения для запуска альфа-версии 4.0, но можете ли вы пока попробовать [email protected]+e8d05c61 ?

@ matthew-dean, для своей сборки я получаю следующее:

  • v3.9: 98 секунд
  • v3.10.3: 161 секунда
  • v3.13.0-alpha.12: 93–96 секунд

Так что, похоже, ты вернулся туда, где хочешь быть! Хорошая работа!

Хорошая работа!

Да, я иду вторым; в третьих; и в-четвертых, что.
Это был неприятный, неприятный спад производительности, который действительно потребовал много усилий, чтобы очистить его.

Работа _очень хорошо_ сделана.

Хорошо, команда! Я собираюсь опубликовать сборку 3.x, а когда-нибудь позже, может быть, на следующей неделе, опубликую 4.0. У обоих теперь должны быть исправления производительности. Спасибо всем, кто помогал отлаживать!

Кстати, в настоящее время я конвертирую кодовую базу Less.js в TypeScript и планирую использовать возможность для настройки / рефакторинга производительности. Я готов помочь, если кому-то интересно! https://github.com/matthew-dean/less.js/compare/master...matthew-dean : следующий

Что-нибудь конкретное, где вы бы предпочли дополнительные глаза? PR очень большой, поэтому я не знаю, с чего начать

@kevinramharak Это справедливый вопрос. Честно говоря, я столкнулся с неожиданными препятствиями при преобразовании в TypeScript (ошибки, которые стало трудно разрешить), и теперь я вообще переосмысливаю это. Less уже работает нормально, и многие причины, по которым я хотел преобразовать (чтобы упростить рефакторинг / добавление новых языковых функций), теперь неактуальны, поскольку я решил перенести свои идеи относительно новых языковых функций в новую предварительную версию. язык обработки. Я не хочу чрезмерно рекламировать себя, поэтому напишите мне в Twitter или Gitter, если хотите подробностей.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги