Files
shaka-player/lib/util/array_utils.js
T
Ivan c86ce869c3 perf(media): replace filter calls in SegmentIndex with binary search (#9880)
This PR replaces `Array.filter` calls in
`SegmentIndex.merge()`,`mergeAndEvict()`, and `evict()` with more
efficient alternatives. The key addition is `binarySearch` helper: it
repeatedly checks the midpoint and discards half the array each time.
The idea is the same as `Array.findIndex` but exploiting the sorted
order to skip most of the work. `merge()` and `evict()` use this to find
their truncation/expiry boundary; `mergeAndEvict()` uses a simple
forward scan that stops at the first valid reference since stale refs
are always bunched at the front. This is done to reduce iteration during
playback (especially livestream with DVR)

- no big new array creations by default - we don't create one when for
example there is nothing to evict
- fewer comparisons — binary search finds the cutoff without scanning
the whole array
- slice just copies the kept elements and that's it
2026-03-25 10:00:52 +01:00

180 lines
3.8 KiB
JavaScript

/*! @license
* Shaka Player
* Copyright 2016 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
goog.provide('shaka.util.ArrayUtils');
/**
* @namespace shaka.util.ArrayUtils
* @summary Array utility functions.
*/
shaka.util.ArrayUtils = class {
/**
* Returns whether the two values contain the same value. This correctly
* handles comparisons involving NaN.
* @param {T} a
* @param {T} b
* @return {boolean}
* @template T
*/
static defaultEquals(a, b) {
// NaN !== NaN, so we need to special case it.
if (typeof a === 'number' &&
typeof b === 'number' && isNaN(a) && isNaN(b)) {
return true;
}
return a === b;
}
/**
* Remove given element from array (assumes no duplicates).
* @param {!Array<T>} array
* @param {T} element
* @template T
*/
static remove(array, element) {
const index = array.indexOf(element);
if (index > -1) {
array.splice(index, 1);
}
}
/**
* Count the number of items in the list that pass the check function.
* @param {!Array<T>} array
* @param {function(T):boolean} check
* @return {number}
* @template T
*/
static count(array, check) {
let count = 0;
for (const element of array) {
count += check(element) ? 1 : 0;
}
return count;
}
/**
* Determines if the given arrays contain equal elements in any order.
*
* @param {!Array<T>} a
* @param {!Array<T>} b
* @param {function(T, T):boolean=} compareFn
* @return {boolean}
* @template T
*/
static hasSameElements(a, b, compareFn) {
if (!compareFn) {
compareFn = shaka.util.ArrayUtils.defaultEquals;
}
if (a.length != b.length) {
return false;
}
const copy = b.slice();
for (const item of a) {
const idx = copy.findIndex((other) => compareFn(item, other));
if (idx == -1) {
return false;
}
// Since order doesn't matter, just swap the last element with
// this one and then drop the last element.
copy[idx] = copy[copy.length - 1];
copy.pop();
}
return copy.length == 0;
}
/**
* Partitions an array into two arrays based on a predicate.
* Returns [matching, notMatching].
*
* @param {!Array<T>} array
* @param {function(T):boolean} predicate
* @return {!Array<!Array<T>>}
* @template T
*/
static partition(array, predicate) {
const yes = [];
const no = [];
for (const element of array) {
if (predicate(element)) {
yes.push(element);
} else {
no.push(element);
}
}
return [yes, no];
}
/**
* Returns the index of the first element for which `predicate` returns
* false. Assumes the array is partitioned so that predicate returns true
* for a contiguous prefix followed by all-false (i.e. a standard
* lower-bound / partition-point binary search).
*
* @param {!Array<T>} array
* @param {function(T):boolean} predicate
* @return {number}
* @template T
*/
static binarySearch(array, predicate) {
let lo = 0;
let hi = array.length;
while (lo < hi) {
const mid = (lo + hi) >>> 1;
if (predicate(array[mid])) {
lo = mid + 1;
} else {
hi = mid;
}
}
return lo;
}
/**
* Determines if the given arrays contain equal elements in the same order.
*
* @param {Array<T>} a
* @param {Array<T>} b
* @param {function(T, T):boolean=} compareFn
* @return {boolean}
* @template T
*/
static equal(a, b, compareFn) {
if (a === b) {
return true;
}
if (!a || !b) {
return a == b;
}
if (!compareFn) {
compareFn = shaka.util.ArrayUtils.defaultEquals;
}
if (a.length != b.length) {
return false;
}
for (let i = 0; i < a.length; i++) {
if (!compareFn(a[i], b[i])) {
return false;
}
}
return true;
}
};