discord.js
discord.js copied to clipboard
perf(collection): optimisations
.first()
, .firstKey()
The callback variant of Array.from()
is slow in V8. Changing the method of array generation from:
Array.from({ length: amount }, () => ...)
to
const results = new Array(amount)
for (let index = 0; index < amount; index++) results[index] = ...
produces a ~90-95% reduction in execution time, when tested with all current LTS versions of Node.
Note that this is deliberately new Array(n)
and not Array.from({ length: n })
:
- The first of these just creates an empty array with a length property of
n
, and is an O(1) operation. - The second of these is an O(n) operation: it creates an empty array of length
n
, then for alli
in0 <= i < n
, it reads the propertyobj[i]
from the object parameter (which in this case will always be undefined), and setsarray[i]
to that value. This is significantly slower for large arrays, and obviously not what we want here.
Additionally, switches to just using iterable-to-array in the case where amount >= this.size
, which is a fast operation for builtin iterables. This further halves the execution time when compared with the above method.
.last()
, .lastKey()
Only carry out the memory-expensive [...this.<iterable>()]
if needed.
Previously, passing amount <= 0
would still copy the entire collection into a temporary array, even though it's never referenced. This significantly speeds up execution in those cases.
Uses the new .at()
or .keyAt()
(as below) to fetch the last element in the collection in the case where amount === undefined
. This is around 10-20% faster than the previous method of copying into a temporary array and selecting the last element.
.at()
, .keyAt()
Manually iterate to the target index, instead of generating a full array copy of the collection to call Array.prototype.at()
.
The performance of the previous implementation was essentially linear on the size of the collection, and independent of the index being fetched, as the whole collection was copied into a temporary array regardless.
The performance of the new implementation is linear on the index being fetched, and results in a performance improvement of >90% when the target index is close to the start of the collection. In the worst case when the target index is close ot the end of the collection, it still performs well (around 10-20% faster in Node v22, and around 50% faster in Node v18). It also avoids the memory cost of copying the collection into a temporary array.
This contains a small off-by-one fix which represents a technical breaking change. Previously, the implementations of .at()
and .keyAt()
were not compliant with the standard for Array.prototype.at()
specifically when passing a negative non-integer index: the standard dictates that these are truncated (ie. rounded towards zero), whereas the previous implementation used Math.floor()
(ie. rounded these away from zero).
.random()
, .randomKey()
Changes the method of array generation to pushing to a new array, instead of passing a callback to Array.from()
.
Same rationale as .first()
above. This change produces a ~10-40% reduction in execution time when tested with all current LTS versions of Node, depending on the magnitude of the collection size and the number of elements requested.
Additionally, skips copying the collection into a temporary array if amount === 0
, and uses the new .at()
or .keyAt()
(as above) to fetch a single element in the case where amount === undefined
.
.map()
Changes the method of array generation to pushing to a new array, instead of passing a callback to Array.from()
.
Same rationale as .first()
above. This change produces a ~80% reduction in execution time when tested with all current LTS versions of Node.
.merge()
Adjusts the logic for checking hasInSelf
and hasInOther
. Previously, each of these boolean conditions was evaluated twice; this makes a small change to the control flow to deduplicate the checks.
.toSorted()
Removes the redundant closure wrapping compareFunction
and just passes it directly, eliminating a needless call from the stack.
Status and versioning classification:
- Code changes have been tested against the Discord API, or there are no code changes
- I know how to update typings and have done so, or typings don't need updating