Что делает этот скрипт таким эффективным по сравнению с другими подходами?
Есть несколько оптимизаций, которые автор вносит в этот алгоритм. Каждый из них требует достаточно глубокого понимания того, как используются лежащие в основе механизмы (например, Javascript, CPU, регистры, кэш, видеокарта и т. Д.). Я думаю, что есть 2 ключевых оптимизации, которые он делает (остальные просто обледенение):
- Буферизация вывода
- Использование целочисленной математики вместо манипуляций со строками
Я расскажу о буферизации в ближайшее время, так как вы спрашиваете об этом явно. Целочисленная математика, которую он использует, имеет два преимущества в производительности: сложение целых чисел обходится дешевле за операцию, чем манипулирование строками, и использует меньше памяти.
Я не знаю, как JavaScript и веб-браузеры обрабатывают преобразование целого числа в отображаемый глиф в браузере, поэтому может быть штраф, связанный с передачей целого числа в document.write по сравнению со строкой. Однако он выполняет (1,000,000 / 1000) вызовы document.write против 1,000,000 - 1000 целочисленных дополнений. Это означает, что он выполняет примерно на 3 порядка больше операций для формирования сообщения, чем для отправки его на дисплей. Поэтому штраф за отправку целого числа против строки в document.write должен был бы превысить 3 порядка, чтобы компенсировать преимущество в производительности при работе с целыми числами.
Почему буферизация ускоряет процесс?
Особенности того, почему это работает, зависят от используемой платформы, оборудования и реализации. В любом случае, все сводится к балансировке вашего алгоритма с ресурсами, вызывающими узкие места.
Например, в случае файлового ввода-вывода буфер полезен, потому что он использует тот факт, что вращающийся диск может записывать только определенное количество за раз. Дайте ему слишком мало работы, и он не будет использовать каждый доступный бит, который проходит под головкой шпинделя при вращении диска. Дайте ему слишком много, и вашему приложению придется подождать (или перевести в спящий режим), пока диск завершит вашу запись - время, которое может быть потрачено на подготовку следующей записи к записи! Некоторые из ключевых факторов, определяющих идеальный размер буфера для файлового ввода-вывода, включают в себя: размер сектора, размер фрагмента файловой системы, чередование, число головок, скорость вращения и плотность площадей среди прочих.
В случае с ЦП, все дело в том, чтобы конвейер был заполнен. Если вы слишком мало работаете с процессором, он будет тратить время на вращение NO OP, пока он ждет, когда вы его зададите. Если вы слишком много отдаете ЦП, вы не можете отправлять запросы другим ресурсам, таким как диск или видеокарта, которые могут выполняться параллельно. Это означает, что позже ЦПУ придется ждать, пока они вернутся, и ничего не делать. Основным фактором для буферизации в CPU является поддержание всего, что вам нужно (для CPU), как можно ближе к FPU / ALU. В типичной архитектуре это (в порядке убывания близости): регистры, кэш L1, кэш L2, кэш L3, RAM.
В случае записи миллиона чисел на экран речь идет о рисовании полигонов на экране с помощью видеокарты. Думайте об этом так. Предположим, что для каждого добавляемого нового числа видеокарта должна выполнить 100 000 000 операций, чтобы нарисовать многоугольники на экране. В одном крайнем случае, если поместить по одной цифре на страницу за раз, а затем попросить видеокарту записать ее, и вы сделаете это для 1000000 номеров, видеокарте придется выполнить 10 ^ 14 операций - 100 триллионов операций! С другой стороны, если вы возьмете весь миллион номеров и сразу отправите его на видеокарту, потребуется всего 100 000 000 операций. Оптимальная точка где-то посередине. Если вы делаете это один раз, процессор выполняет единицу работы и долго ждет, пока графический процессор обновляет дисплей. Если вы сначала напишите всю строку элемента 1M, то графический процессор ничего не делает, пока процессор уходит.
Как определить, какой размер буфера использовать?
Если вы не нацелены на очень конкретную и четко определенную платформу с определенным алгоритмом (например, написание маршрутизации пакетов для интернет-маршрутизации), вы, как правило, не можете определить это математически. Как правило, вы найдете это опытным путем. Угадайте значение, попробуйте его, запишите результаты, затем выберите другое. Вы можете сделать некоторые обоснованные предположения о том, с чего начать и какой диапазон исследовать, исходя из имеющихся у вас узких мест.
У кого-нибудь здесь есть какие-нибудь хитрости, которые могли бы оптимизировать этот сценарий далее?
Я не знаю, сработает ли это, и я не проверял его, однако, размеры буфера обычно бывают кратными 2, так как нижние пиннинги компьютеров являются двоичными, а размеры слов обычно в кратных два (но это не всегда так!). Например, 64 байта с большей вероятностью будут оптимальными, чем 60 байтов, а 1024 с большей вероятностью будут оптимальными, чем 1000. Одним из узких мест, характерных для этой проблемы, является то, что на сегодняшний день большинство браузеров (Google Chrome является первым исключением, которое я знать о) иметь javascript, запускаемый последовательно в том же потоке, что и остальная механика рендеринга веб-страницы. Это означает, что javascript выполняет некоторую работу по заполнению буфера, а затем долгое время ожидает возврата вызова document.write. Если бы javascript запускался как отдельный процесс, асинхронно, как в chrome, вы, вероятно, значительно ускорились бы. Это, конечно, атакует источник узкого места не алгоритмом, который его использует, но иногда это лучший вариант.
Не так кратко, как хотелось бы, но, надеюсь, это хорошая отправная точка. Буферизация является важной концепцией для всех видов проблем производительности в вычислительной технике. Хорошее понимание основных механизмов, которые использует ваш код (как аппаратное, так и программное обеспечение), чрезвычайно полезно для предотвращения или решения проблем производительности.