Давайте посмотрим, как реализован useMemoCache в компайлере:
const $empty = Symbol.for("react.memo_cache_sentinel");
export function c(size: number) {
return React.useState(() => {
const $ = new Array(size);
for (let ii = 0; ii < size; ii++) {
$[ii] = $empty;
}
[$empty] = true;
return $;
})[0];
}Вопрос: Почему они используют именно массив, а не Map?
Как пришли к такому решению?
В одном из первых коммитов в React Compiler я нашёл ссылку на бенчмарк, в котором разработчики сравнили производительность разных способов наполнения массивов элементами, остановившись в итоге цикле for.
Изначально в useMemoCache использовался Array.prototype.fill, но он оказался примерно на 60% медленнее цикла с for.
Будет ли Map быстрее?
Я решил тоже написать свой бенчмарк и проверить))
Зададим те же условия, как и в оригинальном бенчмарке:
const sentinel = Symbol.for('sentinel');
let n=10;И сравним победивший кейс из React Compiler
let a = new Array(n); for (let i=0; i<n; ++i) a[i] = sentinel;С таким же кейсом, но на Map():
let a = new Map(); for (let i=0; i<n; ++i) a.set(i, sentinel);Внезапно, массив отработал в 6 раз быстрее, чем Map().
Тут ещё важно учесть, что я нарочно добавлял в Map() элементы по одному. Я так сделал для того, чтобы максимально приблизиться к текущей реализации в хуке.
Но ведь Map() можно создавать и без цикла for. Можно ведь просто сразу передать в Map() заранее подготовленный iterable.
Этот кейс я тоже решил перепроверить в отдельном бенчмарке)) Результат меня удивил: наполнение Map по одному элементу через цикл for отработало на 11% быстрее, чем new Map(iterable). Причём, этот iterable ещё тоже надо сначала подготовить, это тоже займёт некоторое время (и память). А массив у нас уже и так есть.
Поэтому, кажется, обычный Array конкретно для этого случая всё-таки побеждает. Или я что-то упускаю?
P.S. Пост появился благодаря отличному комментарию из предыдущего поста.