为什么在不可变JS测试中等于慢?

时间:2018-12-03 21:53:17

标签: javascript performance immutable.js

在对某些状态进行更新以使用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));

测试是比较两个大小为100,000的本机js列表,然后比较两个大小为100,000的Immutable.List,每个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 :(得分:1)

根据docs

  

如果两个不可变集合表示相同的值集合,则它们被视为值相等(通过.equals()或is())。这不同于JavaScript的对象和数组的相等引用(通过===或==),后者仅确定两个变量是否表示对同一对象实例的引用。

这意味着它不能为O(1),因为它需要检查列表中所有值的相等性。正如您所发现的,这将大大降低速度。

性能折衷也记录在案:

  

比较两个集合时,值相等性可能需要考虑每个集合中的每个项目,时间复杂度为O(N)。对于大量的价值收集,这可能会成为一项昂贵的操作。尽管如果两者不相等且几乎不相似,则可以很快确定不平等。相反,在比较具有引用相等性的两个集合时,仅需要比较对内存的初始引用,这不是基于集合的大小,而是具有O(1)时间复杂度。检查引用相等性总是非常快,但是,仅仅因为两个集合不是引用相等的,并不排除它们可能是值相等的可能性。

答案 1 :(得分:0)

我认为这可以归为用户错误。我习惯了语言的不变性,因为它是一等公民,并且没有将ImmutableJS映射到JS。我的错误(至少)如下。

首先,.equals()在设计上是一个很深的平等,这不是在React中像shouldComponentUpdate调用之类的实现性能相等检查的依据。

第二,要充分利用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的应用程序状态上执行昂贵的功能,才能获取组件的支持。为了无缘无故地避免这样做,可以将Lodash的备忘录与不可变对象上的引用相等性结合使用,如下面的示例所示。

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

如果有人在这里发现一些错误(或遗漏了错误),请指出来,我会更新说明。