Всегда ли браузеры обрабатывают строки и числа в Javascript как неизменяемые? - PullRequest
3 голосов
/ 06 мая 2020

В Javscript интерпретаторы времени выполнения браузера всегда обрабатывают строки и числа как неизменяемые?

Конечно, в случаях, когда это доказуемо безвредно, они будут оптимизировать и рассматривать их как изменяемые. А если нет, то почему бы и нет?

Например, рассмотрим скромное значение для l oop.

for (let i = 0; i < 1000000000000; i++) {
 console.log(i)
}

Поскольку область действия i ограничена l oop, и никакому коду в l oop никогда не нужны «старые значения» переменной i, для браузера было бы разумно просто увеличивать число, которое символ i указывает на каждую итерацию. В противном случае поток новых байтов памяти будет занят новыми значениями i без какой-либо мыслимой причины («кому-то могут понадобиться эти старые значения i!»). У нас будет ненужная гонка между for l oop (создание новых значений i в памяти) и сборщиком мусора (уничтожение всех старых значений i), которую l oop обычно выиграет. , и у нас будет переполнение стека.

Ой, вот что происходит, не правда ли? Если да, то почему браузеры так глупы, когда они настолько умны в оптимизации кода другими способами?

Аналогичная ситуация и со строками. Рассмотрим следующее.

{
   let completeWorks = "This string dictates the complete works of William Shakespeare. To be or not to be that is the question whether it is nobler in the mind..."
   completeWorks += "The End."  // <-- what happens here?
}

Строка completeWorks ограничена областью видимости блока и предположительно находится только в этом блоке. Поэтому, конечно, когда браузер встречает инструкцию completeWorks += "The End", он просто изменяет completeWorks. Если нет, то почему? Вероятно, это - это веская причина, по которой они этого не делают, и я хотел бы это изучить.

1 Ответ

3 голосов
/ 06 мая 2020

(здесь разработчик V8 - поэтому я очень мало знаю о других браузерах / движках.)

На этот вопрос нет простого ответа; реализации сложны.

Строки в V8 всегда неизменяемы (после создания). Одна из причин заключается в том, что при размещении объектов в куче обычно нет свободного места после объекта, поэтому мы не можем просто добавлять символы в существующую строку. Другая причина заключается в том, что отслеживание того, какие строки могут быть безопасно мутированы, добавило бы необычайной сложности (помимо нескольких более простых для обнаружения нишевых случаев, но если бы поддерживались только они, то этот механизм обеспечил бы гораздо меньшую ценность).

В V8 есть несколько изящных уловок для манипуляций со строками в рукаве: когда вы берете подстроку из большей строки, никакие символы не копируются; новая строка - это просто ссылка, в которой говорится: «Я часть длины X той другой строки, начиная с индекса Y». Аналогично, при объединении двух строк, таких как ваш пример completeWorks, новая строка является ссылкой, которая говорит: «Я - объединение этих двух других строк». (Для полноты я упомяну, что существует минимальное количество символов, ниже которого эти уловки не применяются, потому что простое копирование символов, по крайней мере, столь же эффективно.)

Числа более чувствительны к производительности, и с ними легче работать. чем струны. В общем, номера, размещенные в куче, всегда неизменны; но это не конец истории. V8 широко использует специальное представление для «Smis» («маленькие целые числа»), потому что многие числа в программах JavaScript попадают в эту корзину. Smis - это не куча объектов; создание нового так же дешево, как и его изменение, и фактически неотличимо (как int в C ++). Для чисел вне диапазона Smi оптимизирующий компилятор также выполняет «escape-анализ» и может «распаковывать» неоткрывающиеся числа, что означает сохранение их в регистре ЦП (как простой 64-битный float) вместо того, чтобы размещать их в куче. во-первых, что снова даже лучше, чем изменение неизменяемых в противном случае объектов кучи. Для особого случая чисел, хранящихся в свойствах объекта, V8 также (в некоторых случаях) использует изменяемое хранилище.

Итак, ответ на ваш вопрос - «да» (например, при генерации неоптимизированного кода V8 не тратить время на выполнение анализа, поэтому код должен консервативно предполагать, что где-то нужно какое-то старое значение) и «нет» (для оптимизирующего компилятора ваша интуиция верна, что этого можно избежать; однако это все еще не означает что любые числа, которые были выделены в куче, будут там изменены).

Так как переменная i ограничена l oop

Область действия JavaScript сложно. Во-первых, нет int i. Теперь рассмотрим это:

for (var i = 0; i < 100; i++) {
  // Use i here, or don't.
}
console.log(i);  // Prints "100".

Если вы имели в виду let i, то, конечно, у вас была бы переменная с блочной областью видимости. В этом примере производительность будет такой же.

У нас будет ненужная гонка между for l oop (создание новых значений i в памяти) и сборщиком мусора (уничтожение все старые значения i), которые l oop обычно выигрывает

Нет. Сборщик мусора очень адаптивен, в частности, он выполняет больше работы, когда происходит больше выделений. Нет возможности "обогнать" его. При необходимости выполнение программы останавливается, пока сборщик мусора пытается найти память, которую можно освободить.

, и у нас будет переполнение стека.

Нет, стек переполняется не имеют ничего общего с распределением объектов, сборкой мусора или кучной памятью в целом.

...