LINUX.ORG.RU

JavaScript: быстрая конкатенация строк

 


0

2

Хочу разобраться в этом вопросе. Задача: сначала идёт процесс накопления строк, потом нужен результат в виде одной строки - конкатенации всех строк. Собственно варианта основных, как я понимаю, два: либо делать «в лоб» через +=, либо собирать строки в массив строк и потом делать arr.join(""). Первичное тестирование выявило, что оба варианта дают примерно похожую скорость. Тем не менее хотелось бы понять, почему. Асимптотику выяснить не получилось: тупо памяти не хватает почему-то на тех объёмах, где время работы алгоритма начинается от 10 секунд. На всяких SO рекомендуют писать «в лоб», но я SO в таких вопросах не доверяю. Но как разобраться - не знаю.

Сроки в JS иммутабельны. Логично предположить, что это всё похоже на Java. В Java подход с конкатенацией строк даст O(N^2), то бишь работать будет только для малого N. Мб там JIT умеет оптимизировать какие-то простые случаи, но на это полагаться глупо. StringBuilder-а в JS нет.

Вроде бы подход с массивом строк должен быть оптимален. Массив строк это по сути массив указателей на строку. Наверняка там есть capacity >= length и push в итоге даёт O(1) амортизировано. Метод join может работать в 2 прохода: сначала выяснить длину результата, выделить память и во втором проходе построить результат за O(N).

По факту я не вижу разницы между этими подходами. И вынужден заключить, что либо JS резервирует для иммутабельных строк память порядка length * 2, чтобы потом оптимизировать конструкцию str += s за счёт этого. Либо метод Array.prototype.join реализован неправильно и работает с O(N^2). Оба вывода грустные. Помогите разобраться, если кто копал тут на низком уровне.

★★★★★

Первичное тестирование выявило, что оба варианта дают примерно похожую скорость.

Скорее всего конкретная реализация JS оптимизирует, попробуй померить на другой.

А вообще, сделай бенчмарки по скорости и памяти на разных браузерных движках. Если нет ощутимой разницы — бери вариант, который лучше читается и больше подходит по смыслу.

theNamelessOne ★★★★★
()
Ответ на: комментарий от theNamelessOne

Вообще, где-то читал, что хотя обычные строки в V8 представлены примерно как иммутабельный массив байтов, при конкатенации он создаёт пару-cons (Lisp-like) конкатенируемых строк, и делает «flatten» для этой пары только тогда, когда нужно (например, при индексации).

theNamelessOne ★★★★★
()
Последнее исправление: theNamelessOne (всего исправлений: 1)
Ответ на: комментарий от theNamelessOne

Интересный подход, спасибо, это может объяснять наблюдаемое поведение.

Legioner ★★★★★
() автор топика

там есть несколько моделей конкатенации элементов массива, так же массив может иметь внутренне представление как обычный массив, а если элементы массива разрежены это может быть словарь, просто посмотри исходные тексты v8 например, гуглится легко.

anonymous
()

Давно попадался тест, где + на порядок быстрее, на втором месте concat и потом только join.

vvn_black ★★★★★
()

У строк есть разные внутренние форматы хранения - целиком и нарезками. И JIT может их перекидывать на ходу по мере накопления статистики.

+= наверное должно быть быстрее, но накладные расходы на массив не особо велики, если ты в него только пушишь.

Это конечно при условии что ты не будешь клеить строку и одновременно пытаться читать из нее рандомные позиции.

https://mrale.ph/ - тут неплохое чтиво, что в JIT творится.

Vit ★★★★★
()

Оба вывода грустные.

Да горе оптимизаторы в js страдают из-за того что «оптимизировать» там не надо, а зачастую вредно. js тормозит не из-за строк.

vtVitus ★★★★★
()
Ответ на: комментарий от Vit

+= работает быстрее пока память есть, но он в некоторых случаях (например, при приклеивании посимвольно) может построить очень большое представление, а дальше всё тормозит об gc. Ну и попытка сделать что-то со строкой дальше очень тормозит.

Вообще, всё от кейса зависит.

А ещё это могут оптимизировать в разных версиях.

ChALkeR ★★★★★
()
Последнее исправление: ChALkeR (всего исправлений: 1)
Ответ на: комментарий от ChALkeR

Если выбирать между посимвольной склейкой и посимвольным массивом, массиву тоже будет грустно.

А вообще конечно продгребать к жабаскриптовому JIT-у, вооружившись логикой сишних и жабьих бенчмарков - затея абсолютно безнадежная.

Vit ★★★★★
()

Не ясно, что ты хочешь выяснить, залавая такие вопросы, и говоря при этом жс, когда у него только активных постоянно регулярно используемых движков три штуки + еще куча менее часто испольщуемых.

Лень оасписывать и приводить ссылки, тысячу раз обсосано и проанализировпно. В v8 прямая конкатенация строк работает быстрее, но ест больше памяти. Джойн масства экономит память, но работает примерно в 1.5-2раза медленнее.

anonymous
()

StringBuilder-а в JS нет.

пиши в Uint8Array/Buffer

Метод join может работать в 2 прохода:

var i = 1, o = { length: 2, get [0]() { return 2**i++; } }; [Array.prototype.join.call(o), Array.prototype.join.call(o)];

не может, в V8 кстати глючит, надо зарепортить лол.

drsm ★★
()
Последнее исправление: drsm (всего исправлений: 1)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.