Эта короткая статья является результатом того, что я спрашивал своих юниоров и товарищей по команде из постоянных игроков о том, как лучше использовать функцию сокращения. Я хочу описать вам проблему, которую мы хотим решить, и методы измерения эффективности решения.
Проблема:
Некоторые уменьшающие петли неэффективны. Скрипты, которым необходимо анализировать большой объем данных, могут зависать. Как работать лучше?
Предыстория проблемы
Как программистам (неважно, над фронтендом вы работаете или с бэкендом) нам нужно помнить об использовании памяти. Каждый раз, когда вы помещаете что-либо в переменную, мы создаем адрес памяти - для объекта, который уже существует (ссылка), или для нового выделенного объекта. Итак, если мы время от времени помещаем в память новые объекты, мы загружаем их, а если память перегружена, то процессор может работать медленнее.
Предлагаемое решение - напомнить каждой функции, как распределять память.
Решения: сохранить ссылку на значение аккумулятора
Типичная конструкция функции сокращения в JS:
arr.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])
Итак, давайте сделаем два варианта:
1. это выглядит красиво, но неэффективно.
arr.reduce((acc, curr) => ({ ...acc, [curr.id]: curr }), {});
2. это работает быстро, но выглядит «непривлекательно»
arr.reduce((acc, curr) => { acc[curr.id] = curr; return acc; }, {});
На первый взгляд все то же самое, но на первый взгляд мне кажется лучше. Но давайте посмотрим, как работают эти функции. Репо с тестами производительности уже делал. Для запуска тестовых наборов используйте Benchmark.js, который позволяет мне делать простые тесты, и фейкер для имитации данных.
Эксперимент:
const Benchmark = require('benchmark'); const faker = require('faker'); const ROUNDS = 100; console.log(`Generating ${ROUNDS} sets of data...`); const fixture = new Array(ROUNDS).fill(null).map(() => faker.helpers.userCard()); const suite = new Benchmark.Suite; console.log(`Measurement...`) suite .add('Reduce - left references', () => { fixture.reduce((acc, curr) => { const { city } = curr.address; return { ...acc, [city]: (acc[city] || 0) + 1 } }, {}); }) .add('Reduce - save reference', () => { fixture.reduce((acc, curr) => { const { city } = curr.address; acc[city] = (acc[city] || 0) + 1; return acc; }, {}); }) // add listeners .on('cycle', function(event) { console.log(String(event.target)); }) .on('complete', function() { console.log(`Fastest is "${this.filter('fastest').map('name')}"`); }) .run();
Результаты:
Generating 100 sets of data... Measurement... Reduce - left references x 4,357 ops/sec ±0.95% (91 runs sampled) Reduce - save reference x 132,828 ops/sec ±1.10% (94 runs sampled) Fastest is "Reduce - save reference"
Как видите, если мы сохраним ссылку, процессор сможет выполнять больше операций в секунду - это НА 30 РАЗ БЫСТРЕЕ! Хотите попробовать? Клонируйте мое репо и запустите npm run test:reduce