По любой причине, алгоритмы, которые я пишу, имеют дело с кортежами чисел (представьте себе строки фиксированной длины, но каждый символ - это число). Например, если сегодня длина моих кортежей равна 4, я хочу, чтобы абстрактные данные, подобные
// Think of Vector<Tuple<4>>
const vector_of_tuples = [
[0, 0, 0, 0],
[0, 1, 4, 6],
[9, 8, 7, 2],
[5, 6, 7, 8],
];
Кортежи чисел (фиксированной длины), теперь были моим базовым c типом данных, и я хочу чтобы сделать из них векторы, использовать их в качестве ключей на картах и так далее. (Использование их в качестве ключей в картах - вопрос для другого дня.)
Если мой код много раз создает новые векторы кортежей, он создает много мусора, поскольку каждый кортеж выделяет свое собственное хранилище. Создание вектора с n кортежами приводит к выделению O (n). Очевидной победой на этом фронте было бы «упаковать» кортежи в один вектор (снимите квадратные скобки в приведенном выше примере):
const packed_vector_of_tuples = [
0, 0, 0, 0,
0, 1, 4, 6,
9, 8, 7, 2,
5, 6, 7, 8
];
Это большой выигрыш с точки зрения распределения (O ( 1) вместо O (n) для вектора, содержащего n кортежей), но большие потери с точки зрения удобства использования. Очевидно, теперь мне нужно как-то запомнить длину кортежей (не проблема, легко решаемая), но абстракция становится намного сложнее. Предположим, что теперь я хочу перебрать свой упакованный вектор кортежей, подобно тому, как изначально работал vector_of_tuples.forEach(...)
. Одна идея состоит в том, чтобы сделать это следующим образом:
function forEach_packed_vector(packed_vector, tuple_length, func) {
for (let i = 0; i < packed_vector.length; i += tuple_length)
func(packed_vector.slice(i, i + tuple_length));
}
Однако это означает, что каждый раз, когда я выполняю итерацию, я делаю O (n) распределений, именно то, чего я пытался избежать. (Как V8, так и SpiderMonkey, по-видимому, не могут передать короткий вектор в стеке или, по крайней мере, в достаточный запасной анализ для работы с моим кодом.) пробел:
function forEach_packed_vector_2(packed_vector, tuple_length, func) {
const temporary = [];
for (let i = 0; i < packed_vector.length; i += tuple_length) {
// Copy packed_vector[i : i + tuple_length] into temporary.
for (let j = 0; j < tuple_length; j++)
temporary[j] = packed_vector[i + j];
// Now call the iteration function.
func(temporary);
}
}
На самом деле это работает довольно хорошо, не выделяется более одного раза и невероятно быстро. Тем не менее, я продолжаю сталкиваться с ошибками, где func
содержит ссылку на переданный вектор (вместо того, чтобы делать защитную копию, как это должно быть), что приводит к некоторым очень очень странным ошибкам, проявляющимся где-то еще в моем коде. Так что это решение великолепно с точки зрения производительности и довольно ужасно с точки зрения возможности выстрелить себе в ногу.
Вопрос: Как я могу написать качественно, мусорную лампу код, подобный этому, не устанавливая для себя ловушки на потом?
Эта проблема в основном заключается в том, чтобы просить тип среза, например Span<T>
в C#, или срезы в Go, et c. Но я был бы открыт для любых предложений о том, как создать эффективные структуры данных, подобные этой, чтобы она прекрасно подходила для абстракции, не оставляя при себе орудий.