簡體   English   中英

Javascript ES6 集合的計算/時間復雜度

[英]Javascript ES6 computational/time complexity of collections

ES6 規范為 Keyed Collections(Set、Map、WeakSet 和 WeakMap)提供了什么時間復雜度(以 big-O 表示法)?

我和大多數開發人員的期望是,規范和實現將使用廣泛接受的高性能算法,在這種情況下, Set.prototype.hasadddelete在平均情況下都是 O(1) 。 MapWeak–等價物也是如此。

對我來說,實現的時間復雜度是否在ECMAScript 2015 Language Specification - 6th Edition - 23.2 Set Objects 中是強制要求的,我並不完全清楚。

除非我誤解了它(我當然很有可能會誤解),它看起來 ECMA 規范要求實現(例如Set.prototype.has )使用線性時間( O(n) )算法。 讓我感到非常驚訝的是,規范不會強制要求甚至允許更高性能的算法,而且我對解釋為什么會這樣非常感興趣。

那段你鏈接到:

Set 對象必須使用 [機制] 來實現,該機制平均提供與集合中元素數量次線性的訪問時間。

你會發現MapsWeakMapsWeakSets有相同的句子。

看起來 ECMA 規范要求實現(例如 Set.prototype.has)使用線性時間( O(n) )算法。

不:

Set對象規范中使用的數據結構僅用於描述 Set 對象所需的可觀察語義。 它不是一個可行的實施模型。

可觀察語義主要與可預測的迭代順序有關(仍然可以 高效和快速實現)。 盡管也允許使用樹(具有對數訪問復雜性),但規范確實期望實現使用哈希表或類似的具有常量訪問的東西。

對於任何好奇的人,我做了一個非常快速的基准測試:

const benchmarkMap = size => {
  console.time('benchmarkMap');
  var map = new Map();
  for (var i = 0; i < size; i++) map.set(i, i);
  for (var i = 0; i < size; i++) var x = map.get(i);
  console.timeEnd('benchmarkMap');
}

const benchmarkObj = size => {
  console.time('benchmarkObj');
  var obj = {};
  for (var i = 0; i < size; i++) obj[i] = i;
  for (var i = 0; i < size; i++) var x = obj[i];
  console.timeEnd('benchmarkObj');
}

var size = 1000000;

benchmarkMap(size);
benchmarkObj(size);

我運行了幾次並產生了以下結果:

(2017 款 MacBook Pro,2.5 GHz i7 帶 16G RAM)

benchmarkMap: 189.120ms
benchmarkObj: 44.214ms

benchmarkMap: 200.817ms
benchmarkObj: 38.963ms

benchmarkMap: 187.968ms
benchmarkObj: 41.633ms

benchmarkMap: 186.533ms
benchmarkObj: 35.850ms

benchmarkMap: 187.339ms
benchmarkObj: 44.515ms

問題是 Set.has() 方法 O(1) 和 Array.indexOf O(n) 嗎? 被列為此副本的副本,但並不完全正確(我已投票決定重新開放)。 無論如何,我將在此處添加這些基准,因為對該問題的答復中的基准未能顯示Set#hasArray#indexOf之間的全部性能差異。

以下內容適用於 Chrome 93:

您會發現對於較小的數據集, Array#indexOf實際上優於Set#hasMap#has 但是,對於較大的數據集, Set#hasMap#has的速度要快多個數量級。 這與您對 O(n) 與 O(1) 操作的期望非常一致。

有趣的是,盡管兩者都是 O(n),但對於小數據集, Array#includesArray#indexOf慢得多,但對於大數據集的表現非常相似。 據推測, Array#indexOf利用了Array#includes沒有的一些優化。

同時, Object#hasOwnProperty在所有情況下都略勝於Set#hasMap#has (至少在 Chrome 93 中)。

基准代碼

 const [small, medium, large] = [1e3, 1e5, 1e7] const configs = [ { size: small, iterations: large }, { size: medium, iterations: medium }, { size: large, iterations: small }, ] for (const { size, iterations } of configs) { const arr = Array.from({ length: size }, (_, i) => String(i)) const obj = Object.fromEntries(arr.map(k => [k, true])) const set = new Set(arr) const map = new Map(Object.entries(obj)) const valsToTest = Array.from( { length: iterations }, (_, i) => String(Math.floor(Math.random() * size)), ) const title = `dataset size: ${size.toLocaleString()}; iterations: ${iterations.toLocaleString()}` console.log(`\\n-> ${title}`) for (const [target, method] of [ [arr, 'indexOf'], [arr, 'includes'], [set, 'has'], [map, 'has'], [obj, 'hasOwnProperty'], ]) { const subtitle = `${target.constructor.name}#${method}` console.time(subtitle) for (const val of valsToTest) { target[method](val) } console.timeEnd(subtitle) } }

我的結果(Chrome 93)


-> dataset size: 1,000; iterations: 10,000,000
Array#indexOf: 185.100ms
Array#includes: 11302.700ms
Set#has: 367.400ms
Map#has: 375.000ms
Object#hasOwnProperty: 252.800ms

-> dataset size: 100,000; iterations: 100,000
Array#indexOf: 10794.100ms
Array#includes: 10476.800ms
Set#has: 6.600ms
Map#has: 6.800ms
Object#hasOwnProperty: 1.900ms

-> dataset size: 10,000,000; iterations: 1,000
Array#indexOf: 12798.900ms
Array#includes: 12435.400ms
Set#has: 0.800ms
Map#has: 0.800ms
Object#hasOwnProperty: 0.300ms

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM