Почему в этом неизменном тесте JS все равно медленно? - PullRequest
0 голосов
/ 04 декабря 2018

Я выполняю некоторые проверки работоспособности в личном проекте, прежде чем обновлять состояние для использования ImmutableJS.У меня есть небольшой тест, который я написал, чтобы убедиться, что Immutable.List.equals работает так, как я ожидал, - O (1).

https://github.com/naddeoa/immutablejs-slow-equals.git

Важная часть ниже

function compareImmutableMany(l1, l2) {
  let result = false;
  for (let i = 0; i < iterations; i++) {
    result = l1.equals(l2);
  }
  return result;
}

i1 = createImmutableList();
i2 = createImmutableList();
console.log("immutable lists are equal", compareImmutableMany(i1, i2));

В ходе теста сравниваются два собственных списка js размером 100 000, а затем два списка Immutable.List размером 100 000, каждый из которых 1000 раз.Я должен что-то упустить.Я вижу, как пример Immutable.List работает очень плохо по сравнению с примером собственного списка.

starting
immutable lists are equal true
Seconds: 11.423
starting
normal lists are equal: true
Seconds: 0.109

Вы можете запустить его локально с помощью следующего.

git clone https://github.com/naddeoa/immutablejs-slow-equals.git
cd immutablejs-slow-equals
npm i
node main.js

Если ясделав простую ошибку, я был бы признателен, если бы я дал понять, где это.Я определенно ожидал, что это будет очень быстро.Тем более, что замена l1.equals(l2) на вызов l1.hashCode() действительно быстрая.

Ответы [ 2 ]

0 голосов
/ 04 декабря 2018

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

Во-первых, .equals() по своему замыслу глубоко равен, и это не то, на что вы должны полагаться при реализации проверок на равенство в чем-то вроде shouldComponentUpdateпозвоните в React.

Во-вторых, чтобы получить максимальную отдачу от ImmutableJS, вам нужно правильно использовать его API.Вот пример.

const Foo = Immutable.Record({a:1})

const foo1 = Foo({a:2})
const foo2 = Foo({a:2})

const cache = Immutable.Map({'key': foo1})

// Doing it this way will force you to either use .equals() or .hashCode() 
// to check for changes later
const wrongWay = cache.set('key', foo2)
console.log('This is false', wrongWay === cache) 
console.log('This is true', wrongWay.hashCode() === cache.hashCode()) 
console.log('This is true and slow', wrongWay.equals(cache))  

// Doing it this way will let you rely in reference equality
const rightWay = cache.mergeDeep({'key': foo2})
console.log('This is true', rightWay === cache) 
console.log('This is true, but not needed', rightWay.hashCode() === cache.hashCode()) 
console.log('This is true and slow, but not needed', rightWay.equals(cache))  

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

Наконец, иногда вам нужно выполнять дорогостоящие функции для состояния вашего приложения в React, чтобы иметь возможность получить реквизиты для вашего компонента.Чтобы избежать этого без всякой причины, вы можете использовать что-то вроде памятки Лодаша в сочетании с равенством ссылок на неизменяемых объектах, как в следующем надуманном примере.

const myFunction = _.memoize((immutableAppState) => {
    return immutableAppState.immutableList.map(/* really expensive stuff returning new immutable objects based on other selectors */)
}, (immutableAppState) => immutableAppState.specificRecordThatChanges )

Если кто-то обнаружит здесь некоторые ошибки (или пропущенные ошибки)), пожалуйста, укажите их, и я обновлю объяснение.

0 голосов
/ 04 декабря 2018

Согласно документам :

Две неизменяемые коллекции считаются равными по значению (через .equals () или is ()), если они представляют одну и ту же коллекцию значений.Это отличается от типичной ссылки JavaScript, равной (через === или ==) для объектов и массивов, которая определяет только, представляют ли две переменные ссылки на один и тот же экземпляр объекта.

Это означает, что он не можетбыть O (1), потому что он должен проверить равенство всех значений в списке.Это будет существенно медленнее (как вы обнаружили).

Компромисс производительности также задокументирован:

При сравнении двух коллекций для равенства значений может потребоваться рассмотрение каждого элемента в каждомколлекция, на O (N) сложность времени.Для больших коллекций значений это может стать дорогостоящей операцией.Хотя, если они не равны и вряд ли похожи, неравенство определяется очень быстро.Напротив, при сравнении двух коллекций с равенством ссылок необходимо сравнивать только исходные ссылки на память, которые не основаны на размере коллекций, что имеет временную сложность O (1).Проверка равенства ссылок всегда выполняется очень быстро, однако то, что две коллекции не равны ссылкам, не исключает возможности того, что они могут быть равными по значению.

...