-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Summary: Use hashed-array-mapped tries instead of built-in Map for atomValues, to avoid copying the map. This should dramatically increase speed for large numbers of atoms. In a benchmark I performed, setting a value in a HAMT (just the data structure, not all of Recoil) compared with copying a map and setting the value is: * 3.7× faster for 10 entries * 28× faster for 100 entries * 325× faster for 1,000 entries * 3,026× faster for 10,000 entries Reading values is slower: * 5× slower for 10 entries * 5× slower for 100 entries * 6× slower for 1,000 entries * 8× slower for 10,000 entries However, in Recoil, we generally only read once per write (since the read value is cached elsewhere until the next write), so this should still be a win overall. Reviewed By: drarmstr Differential Revision: D25487986 fbshipit-source-id: cea056cc298520f1b54f9fb12c896086c4ef82c6
- Loading branch information
1 parent
44e543b
commit b7d1cfd
Showing
12 changed files
with
190 additions
and
45 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
/** | ||
* Copyright (c) Facebook, Inc. and its affiliates. | ||
* | ||
* This source code is licensed under the MIT license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
* | ||
* @emails oncall+recoil | ||
* @flow strict | ||
* @format | ||
*/ | ||
|
||
'use strict'; | ||
|
||
import type {HAMTPlusMap} from 'hamt_plus'; | ||
|
||
const gkx = require('../util/Recoil_gkx'); | ||
const hamt = require('hamt_plus'); | ||
|
||
export interface PersistentMap<K: string, V> { | ||
keys(): Iterable<K>; | ||
entries(): Iterable<[K, V]>; | ||
|
||
get(key: K): V | void; | ||
has(key: K): boolean; | ||
set(key: K, value: V): PersistentMap<K, V>; | ||
delete(key: K): PersistentMap<K, V>; | ||
|
||
clone(): PersistentMap<K, V>; | ||
toMap(): Map<K, V>; | ||
} | ||
|
||
class BuiltInMap<K: string, V> implements PersistentMap<K, V> { | ||
_map: Map<K, V>; | ||
|
||
constructor(existing?: PersistentMap<K, V>) { | ||
this._map = new Map(existing?.entries()); | ||
} | ||
|
||
keys(): Iterable<K> { | ||
return this._map.keys(); | ||
} | ||
|
||
entries(): Iterable<[K, V]> { | ||
return this._map.entries(); | ||
} | ||
|
||
get(k: K): V | void { | ||
return this._map.get(k); | ||
} | ||
|
||
has(k: K): boolean { | ||
return this._map.has(k); | ||
} | ||
|
||
set(k: K, v: V): PersistentMap<K, V> { | ||
this._map.set(k, v); | ||
return this; | ||
} | ||
|
||
delete(k: K): PersistentMap<K, V> { | ||
this._map.delete(k); | ||
return this; | ||
} | ||
|
||
clone(): PersistentMap<K, V> { | ||
return persistentMap(this); | ||
} | ||
|
||
toMap(): Map<K, V> { | ||
return new Map(this._map); | ||
} | ||
} | ||
|
||
class HashArrayMappedTrieMap<K: string, V> implements PersistentMap<K, V> { | ||
// Because hamt.empty is not a function there is no way to introduce type | ||
// parameters on it, so empty is typed as HAMTPlusMap<string, mixed>. | ||
// flowlint-next-line unclear-type:off | ||
_hamt: HAMTPlusMap<K, V> = ((hamt.empty: any).beginMutation(): HAMTPlusMap< | ||
K, | ||
V, | ||
>); | ||
|
||
constructor(existing?: PersistentMap<K, V>) { | ||
if (existing instanceof HashArrayMappedTrieMap) { | ||
const h = existing._hamt.endMutation(); | ||
existing._hamt = h.beginMutation(); | ||
this._hamt = h.beginMutation(); | ||
} else if (existing) { | ||
for (const [k, v] of existing.entries()) { | ||
this._hamt.set(k, v); | ||
} | ||
} | ||
} | ||
|
||
keys(): Iterable<K> { | ||
return this._hamt.keys(); | ||
} | ||
|
||
entries(): Iterable<[K, V]> { | ||
return this._hamt.entries(); | ||
} | ||
|
||
get(k: K): V | void { | ||
return this._hamt.get(k); | ||
} | ||
|
||
has(k: K): boolean { | ||
return this._hamt.has(k); | ||
} | ||
|
||
set(k: K, v: V): PersistentMap<K, V> { | ||
this._hamt.set(k, v); | ||
return this; | ||
} | ||
|
||
delete(k: K): PersistentMap<K, V> { | ||
this._hamt.delete(k); | ||
return this; | ||
} | ||
|
||
clone(): PersistentMap<K, V> { | ||
return persistentMap(this); | ||
} | ||
|
||
toMap(): Map<K, V> { | ||
return new Map(this._hamt); | ||
} | ||
} | ||
|
||
export function persistentMap<K: string, V>( | ||
existing?: PersistentMap<K, V>, | ||
): PersistentMap<K, V> { | ||
if (gkx('recoil_hamt_2020')) { | ||
return new HashArrayMappedTrieMap(existing); | ||
} else { | ||
return new BuiltInMap(existing); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.