Дорогая, я уменьшил пакет npm

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

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

По сути, это не что иное, как сжатый gzip'ом архив. При разработке программного обеспечения исходный код почти всегда поставляется в виде файлов .tar.gz или .tgz. Сжатие gzip поддерживается каждым HTTP-сервером и веб-браузером. Но вот что самое интересное: gzip начинает устаревать, уступая место новым алгоритмам сжатия вроде Brotli и ZStandard. Теперь представьте себе мир, в котором npm использует один из этих новых алгоритмов. В этом посте я углублюсь в вопросы сжатия и исследую возможности модернизации стратегии сжатия npm.

Что насчёт конкуренции?

Двумя основными игроками в этой сфере являются Brotli и ZStandard (сокращённо zstd). Brotli был выпущен Google в 2013 году, а zstd — запрещённой соцсетью на букву F в 2016 году. С тех пор они были стандартизированы в RFC 7932 и RFC 8478 соответственно и получили широкое распространение во всей индустрии программного обеспечения.

На самом деле именно объявление Arch Linux о том, что они собираются сжимать свои пакеты с помощью zstd по умолчанию, заставило меня призадуматься. Arch Linux ни в коем случае не был первым и тем более единственным проектом. Но чтобы выяснить, имеет ли это смысл для экосистемы Node, нужно провести несколько тестов. А это значит, что нужно пробиться через tar.

Сравнительный анализ, часть 1

Я собираюсь запустить tar и посмотреть, какие параметры для сравнения я могу получить, переключаясь gzip, Brotli и zstd. Я протестирую сам пакет npm, так как он довольно популярен, в среднем его загружают более 4 миллионов раз в неделю, а ещё он большой: около 11 МБ в распакованном виде.

$ curl --remote-name https://registry.npmjs.org/npm/-/npm-9.7.1.tgz
$ ls -l --human npm-9.7.1.tgz 
-rw-r--r-- 1 jamie users 2.6M Jun 16 20:30 npm-9.7.1.tgz 
$ tar --extract --gzip --file npm-9.7.1.tgz
$ du --summarize --human --apparent-size package
11M	package

gzip сразу даёт хорошие результаты, сжимая с 11 МБ до 2,6 МБ со степенью сжатия около 0,24. Но на что способны  конкуренты? На данный момент я буду придерживаться параметров по умолчанию:

$ brotli --version
brotli 1.0.9
$ tar --use-compress-program brotli --create --file npm-9.7.1.tar.br package
$ zstd --version
*** Zstandard CLI (64-bit) v1.5.5, by Yann Collet ***
$ tar --use-compress-program zstd --create --file npm-9.7.1.tar.zst package
$ ls -l --human npm-9.7.1.tgz npm-9.7.1.tar.br npm-9.7.1.tar.zst 
-rw-r--r-- 1 jamie users 1.6M Jun 16 21:14 npm-9.7.1.tar.br
-rw-r--r-- 1 jamie users 2.3M Jun 16 21:14 npm-9.7.1.tar.zst
-rw-r--r-- 1 jamie users 2.6M Jun 16 20:30 npm-9.7.1.tgz 

Ух ты! Без конфигурирования и Brotli, и zstd обходят gzip, но Brotli явный победитель. Он обеспечивает степень сжатия 0,15, в то время как у zstd только 0,21. В реальном выражении это означает экономию около 1 МБ. Вроде пустяковая разница, но при 4 миллионах загрузок в неделю это даёт экономию 4 ТБ в пропускной способности в неделю.

Сравнительный анализ, часть 2: Электрическое бугалу

Степень сжатия — лишь половина дела. А на самом деле даже треть, но скорость сжатия не имеет особого значения. Сжатие пакета происходит только один раз, когда пакет публикуется, но распаковка происходит каждый раз при запуске npm install. Таким образом, именно экономия времени на распаковке пакетов повышает скорость установки или сборки.

Чтобы проверить это, я собираюсь использовать Hyperfine — инструмент для сравнительного анализа командной строки. Распаковка каждого из пакетов, которые я ранее создавал 100 раз, должна дать мне хорошее представление об относительной скорости распаковки.

$ hyperfine --runs 100 --export-markdown hyperfine.md \
  'tar --use-compress-program brotli --extract --file npm-9.7.1.tar.br --overwrite' \
  'tar --use-compress-program zstd --extract --file npm-9.7.1.tar.zst --overwrite' \
  'tar --use-compress-program gzip --extract --file npm-9.7.1.tgz --overwrite'

Команда

Средний [мс]

Минимальный [мс]

Максимальный [мс]

Относительный

tar –use-compress-program brotli –extract –file npm-9.7.1.tar.br –overwrite

51,6 ± 3,0

47,9

57,3

1,31 ± 0,12

tar –use-compress-program zstd –extract –file npm-9.7.1.tar.zst –overwrite

39,5 ± 3,0

33,5

51,8

1.00

tar –use-compress-program gzip –extract –file npm-9.7.1.tgz –overwrite

47,0 ± 1,7

44,0

54,9

1,19 ± 0,10

На этот раз вперёд вырывается zstd, за ним следуют gzip и Brotli. Это логично, поскольку «сжатие в реальном времени» — одна из главных функций, рекламируемых в документации zstd. Хотя Brotli на 31% медленнее по сравнению с zstd, в реальном выражении это всего лишь 12 мс. И по сравнению с gzip он медленнее всего на 5 мс.

На практике это означает, что вам понадобится соединение со скоростью более 1 Гбит/с, чтобы компенсировать потерю 5 мс при распаковке по сравнению с 1 МБ, который будет сэкономлен за счёт размера пакета.

Сравнительный анализ, часть 3. На этот раз все серьёзно

До сих пор я брал настройки Brotli и zstd по умолчанию, но у обоих есть множество крутилок и регуляторов, которые вы можете настроить, чтобы изменить степень сжатия, а также скорость сжатия или распаковки. Здесь мне помог отраслевой стандарт lzbench. Он может протестировать каждый архиватор и в конце выдать красивую таблицу со всеми данными.

Здесь я должен вас предостеречь от нескольких вещей. Во‑первых, lzbench не может сжимать весь каталог, например tar, поэтому для этого теста я решил использовать lib/npm.js. Во‑вторых, в состав lzbench не входит инструмент gzip. Вместо этого он использует zlib, базовую библиотеку gzip. И, в‑третьих, версии каждого архиватора не совсем актуальны. Последняя актуальная версия zstd — 1.5.5, выпущенная 4 апреля 2023 г., тогда как lzbench использует версию 1.4.5, выпущенную 22 мая 2020 г. Последняя версия Brotli — 1.0.9, выпущенная 27 августа 2020 г., тогда как lzbench использует версию, выпущенную 1 октября 2019 года.

$ lzbench -o1 -ezlib/zstd/brotli package/lib/npm.js

Архиватор

Сжатие

Распаковка

Размер файла

Соотношение

Имя файла

memcpy

117330 MB/s

121675 MB/s

13141

100.00

package/lib/npm.js

zlib 1.2.11 -1

332 MB/s

950 MB/s

5000

38.05

package/lib/npm.js

zlib 1.2.11 -2

382 MB/s

965 MB/s

4876

37.11

package/lib/npm.js

zlib 1.2.11 -3

304 MB/s

986 MB/s

4774

36.33

package/lib/npm.js

zlib 1.2.11 -4

270 MB/s

1009 MB/s

4539

34.54

package/lib/npm.js

zlib 1.2.11 -5

204 MB/s

982 MB/s

4452

33.88

package/lib/npm.js

zlib 1.2.11 -6

150 MB/s

983 MB/s

4425

33.67

package/lib/npm.js

zlib 1.2.11 -7

125 MB/s

983 MB/s

4421

33.64

package/lib/npm.js

zlib 1.2.11 -8

92 MB/s

989 MB/s

4419

33.63

package/lib/npm.js

zlib 1.2.11 -9

95 MB/s

986 MB/s

4419

33.63

package/lib/npm.js

zstd 1.4.5 -1

594 MB/s

1619 MB/s

4793

36.47

package/lib/npm.js

zstd 1.4.5 -2

556 MB/s

1423 MB/s

4881

37.14

package/lib/npm.js

zstd 1.4.5 -3

510 MB/s

1560 MB/s

4686

35.66

package/lib/npm.js

zstd 1.4.5 -4

338 MB/s

1584 MB/s

4510

34.32

package/lib/npm.js

zstd 1.4.5 -5

275 MB/s

1647 MB/s

4455

33.90

package/lib/npm.js

zstd 1.4.5 -6

216 MB/s

1656 MB/s

4439

33.78

package/lib/npm.js

zstd 1.4.5 -7

140 MB/s

1665 MB/s

4422

33.65

package/lib/npm.js

zstd 1.4.5 -8

101 MB/s

1714 MB/s

4416

33.60

package/lib/npm.js

zstd 1.4.5 -9

97 MB/s

1673 MB/s

4410

33.56

package/lib/npm.js

zstd 1.4.5 -10

97 MB/s

1672 MB/s

4410

33.56

package/lib/npm.js

zstd 1.4.5 -11

37 MB/s

1665 MB/s

4371

33.26

package/lib/npm.js

zstd 1.4.5 -12

27 MB/s

1637 MB/s

4336

33.00

package/lib/npm.js

zstd 1.4.5 -13

20 MB/s

1601 MB/s

4310

32.80

package/lib/npm.js

zstd 1.4.5 -14

18 MB/s

1582 MB/s

4309

32.79

package/lib/npm.js

zstd 1.4.5 -15

18 MB/s

1582 MB/s

4309

32.79

package/lib/npm.js

zstd 1.4.5 -16

9.03 MB/s

1556 MB/s

4305

32.76

package/lib/npm.js

zstd 1.4.5 -17

8.86 MB/s

1559 MB/s

4305

32.76

package/lib/npm.js

zstd 1.4.5 -18

8.86 MB/s

1558 MB/s

4305

32.76

package/lib/npm.js

zstd 1.4.5 -19

8.86 MB/s

1559 MB/s

4305

32.76

package/lib/npm.js

zstd 1.4.5 -20

8.85 MB/s

1558 MB/s

4305

32.76

package/lib/npm.js

zstd 1.4.5 -21

8.86 MB/s

1559 MB/s

4305

32.76

package/lib/npm.js

zstd 1.4.5 -22

8.86 MB/s

1589 MB/s

4305

32.76

package/lib/npm.js

brotli 2019-10-01 -0

604 MB/s

813 MB/s

5182

39.43

package/lib/npm.js

brotli 2019-10-01 -1

445 MB/s

775 MB/s

5148

39.18

package/lib/npm.js

brotli 2019-10-01 -2

347 MB/s

947 MB/s

4727

35.97

package/lib/npm.js

brotli 2019-10-01 -3

266 MB/s

936 MB/s

4645

35.35

package/lib/npm.js

brotli 2019-10-01 -4

164 MB/s

930 MB/s

4559

34.69

package/lib/npm.js

brotli 2019-10-01 -5

135 MB/s

944 MB/s

4276

32.54

package/lib/npm.js

brotli 2019-10-01 -6

129 MB/s

949 MB/s

4257

32.39

package/lib/npm.js

brotli 2019-10-01 -7

103 MB/s

953 MB/s

4244

32.30

package/lib/npm.js

brotli 2019-10-01 -8

84 MB/s

919 MB/s

4240

32.27

package/lib/npm.js

brotli 2019-10-01 -9

7.74 MB/s

958 MB/s

4237

32.24

package/lib/npm.js

brotli 2019-10-01 -10

4.35 MB/s

690 MB/s

3916

29.80

package/lib/npm.js

brotli 2019-10-01 -11

1.59 MB/s

761 MB/s

3808

28.98

package/lib/npm.js

Это в значительной степени подтверждает то, что я показал ранее. zstd способен обеспечить более высокую скорость распаковки, чем gzip или Brotli, а также немного превосходит gzip по степени сжатия. Brotli, с другой стороны, имеет сопоставимую скорость распаковки и сопоставимую степень сжатия с gzip на низких уровнях качества, но на уровнях 10 и 11 он способен дать более высокую степень сжатия, чем gzip и zstd.

Всё уже было в Симпсонах

Теперь, когда я закончил тестирование производительности, нужно вернуться назад и взглянуть на мою первоначальную идею о замене gzip на другой стандарт сжатия npm. Как выяснилось, у Эвана Хана в 2022 году возникла аналогичная идея, которую он изложил на собрании npm RFC. Он предложил использовать Zopfli, обратно совместимую библиотеку сжатия gzip и старшего (и более крутого

Источник: https://habr.com/ru/companies/cloud4y/articles/765602/


Интересные статьи

Интересные статьи

В заметке кратко описан функционал пакета gMWT, который реализовывает обобщенный тест Манна-Уитни. Описано его применение для проверки гипотез о равенстве законов распределения для случая двух и трех ...
Надеюсь, ни для кого не секрет, что вследствие определённых причин интернет начал сильно преображаться. Например, привычные сервисы становятся недоступны в силу блокировок или собственного решения к...
️ Эта статья берет за основу Go 1.14.Go предоставляет механизмы синхронизации памяти, такие как канал (channel) или мьютекс (mutex ), которые помогают решать различные пр...
Nuget-пакет — это не только архив с переиспользуемыми сборками, но и контент с target-скриптами, которые задают поведение MsBuild при сборке приложения. Это дает нам возможность рассм...
От редактора блога Google: Интересовались ли вы когда-нибудь тем, как инженеры Google Cloud Technical Solutions (TSE) занимаются вашими обращениями в техподдержку? В сфере ответственн...