nop's blog

In computer science, NOP is an assembly language instruction that effectively does nothing at all.

Иногда комментарии только во вред!

Комментировать код, конечно, полезно. Но иногда это приводит к неожиданным результатам.

Например, сравним две функции:

function add1(x, y) {
    return x + y;
}

function add2(x, y) {
    /*
        ...
        Очень длинный комментарий...
        ...
    */
    return x + y;
}

Они ничем не отличаются, кроме комментария. Большинство js-программистов привыкло, что пробелы и комментарии ни на что не влияют. Но это не так.

В v8 есть ограничение на размер функции, которая может быть заинлайнена:

DEFINE_int(max_inlined_source_size, 600,
    "maximum source size in bytes considered for a single inlining")

При этом считается размер функции в исходном тексте, так что комментарии тоже учитываются. Т.е. слишком развесистые комментарии могут замедлить выполнение программы!

Можно посмотреть вот такой пример. Там вычисляется некое выражение — (x + 1) * (x - 1) — тремя способами. Первый — при помощи нескольких вспомогательных функций, второй — тоже самое, но в эти функции добавлены длинные комментарии. И третий — выражение вычисляется как есть, без каких-либо функций.

Результаты примерно такие:

$ node long-comments-are-bad.js

func w/o comments: 247ms
func w/ long comments: 813ms
expression: 242ms

В первом варианте (в котором функции мелкие и они инлайнятся) результат практически такой же, как и просто в выражении. Т.е. дополнительные функции не дают никакого оверхеда. А второй вариант (с комментариями) в три с лишним раза медленнее.

Пример, конечно, синтетический, но тем не менее.

Выводы такие: код нужно обфусцировать всегда. Все давным давно делают это для javascript’а, который выполняется в браузере. Но, неплохо бы делать это и для серверного кода, который выполняется в node.js.

Интересный момент. Этот же тест на jsperf.com дает другие результаты в Хроме. Первый и второй вариант идентичны. Можно предположить, что обертка, которую добавляет jsperf.com выключает какие-то образом оптимизацию в v8. Скорее всего, код завернут в try-catch или что-то подобное. Т.е. нужно с большой осторожностью делать тесты кода, который должен быть прооптимизирован js-движком.

Проблемы с Markdown в Octopress

Markdown — прекрасный язык разметки и ему нет никаких альтернатив для написания постов в блог, документации и т.д. Но иногда у него есть чудовищные проблемы, главным образом, связанные с блочными элементами внутри списков. В эти моменты хочется взять и переписать все по-своему.

Например:

  * Первый элемент списка.

  * Еще один элемент списка, на этот раз состоящий из нескольких абзацев.
    Это первый абзац. За ним идет блок кода.

        var foo = 42;

    И еще третий абзац с текстом.

Теперь блок кода хочется раскрасить. В Octopress’е есть два способа это сделать: Backtick Code Blocks и Codeblock.

Увы, оба совершенно непригодны.

  • Во-первых, предполагается, что они начинаются в самом начале строки. Т.е. внутри списков, скажем, они выглядят странно. Либо они рушат все ваши инденты, либо же добавляют лишний индент в блок с кодом.

  • Во-вторых, они завершают блочный элемент списка. Т.е. в нашем примере выше третий абзац не будет считаться частью списка, а превратится в блок кода.

Поэтому решение такое: использовать js-ные хайлайтеры. Например, Prism.

В source/_includes/head.html нужно подключить prism.css и prism.js. После чего код раскрашивается добавлением класса к блокам с кодом при помощи директивы {: .language-... }:

  * Первый элемент списка.

  * Еще один элемент списка, на этот раз состоящий из нескольких абзацев.
    Это первый абзац. За ним идет блок кода.

    {: .language-js }
        var foo = 42;

    И еще третий абзац с текстом.

Пересказ видео «Accelerating Oz With V8»

Посмотрел давеча познавательное видео: Accelerating Oz with V8. Это доклад на Google I/O 2013. Речь там идет о некоем веб-приложении (на самом деле это 3d игра/демка), в котором обнаружились проблемы с производительностью и которое докладчики починили.

Видео идет 38 минут, и большая часть там ни о чем. Поэтому сделаю краткую выжимку, чтобы не забыть:

  • Во-первых, если v8 по каким-то причинам не оптимизировала функцию, в которой есть много арифметических вычислений, то все эти арифметические операции создают новые временные объекты, которые потом должны быть прибраны GC.

    var a = p * q;
    var b = x + y;
    var c = t / s;
    point.x = a * b * c;
    ...
    

    в этом коде будет создано 5 лишних временных объектов (каждый бинарный оператор).

  • Во-вторых, обычный цикл for-in деоптимизирует функцию целиком (как какой-нибудь eval или блок try-catch). Пример со слайдов:

    function updateSprites(dt) {
        for (var sprite in sprites) {
            sprite.position.x += sprite.velocity.x * dt;
            //  many more lines of arithmetic.
            //  ...
        }
    }
    

    Внутри цикла много арифметических вычислений, из-за цикла вся функция деоптимизирована. Как следствие — создается очень много временных объектов и GC тратит много времени на их удаление. Что не очень хорошо сказывается на производительности приложения.

    Решение: вынести вычисления (все, что внутри for-in) в отдельную функцию, что позволит v8 ее оптимизировать и не создавать нагрузку на GC.

    function updateSprite(sprite, dt) {
        sprite.position.x += sprite.velocity.x * dt;
        //  many more lines of arithmetic.
        //  ...
    }
    
    function updateSprites(dt) {
        for (var sprite in sprites) {
            updateSprite(sprite, dt);
        }
    }