Pull to refresh

Comments 55

Проблема только в том, что в реальных условиях Server.ping будет выполняться на 50000% медленнее и эти 30%, изрядно подпортившие читаемость кода, будут никому не нужны
Не говоря уже о том, Server.ping — это операция, которая большую часть времени ждет. Правильная оптимизация цикла — распараллелить его.
Не то чтобы «ждет». Это JavaScript и такие операции обычно асинхронные.

Хотя кто его знает что за Server.ping кроется.
Судя по изначальной форме цикла, операция все-таки синхронная.
Вот именно это меня в этом коде и смущает :) Я подозреваю что подобные вещи нужны чтобы динамически статус подключения показывать и если этот код синхронный и «повиснет», повесив интерфейс, пока от серверов ответов не дождется то это плохо.
По словам разработчика одного из самых быстрых MySQL driver'ов для Node.js, этот подход помог ему выиграть эти же 30% при парсинге полей в ответах с сервера.
Естественно этот метод далеко не панацея, и всё очень сильно зависит от ситуации.
Вполне может быть. Создавать объект eval-ом по записям БД, полученным сервера — не такая плохая идея, по сравнению с перебором полей в цикле. Особенно результат запроса «широкий» и записей много.

Но в коде вашего примера меня более всего смущает синхронный пинг :)
Всех смущает не пинг, а название метода. Нужно было назвать его ".isOk()" :P
Не JS но:
p = Net::Ping::External.new('195.54.2.1')

Benchmark.bm do |x|
  x.report {100.times do;p.ping;end}
end

user system total real
0.100000 0.220000 0.320000 ( 1.258235)

1.25 секунд на 100 пингов в 1 поток.
Итого Server.ping будет выполняться примерно 80 пингов в секунду
Всего в 805,6625 раз медленнее чем 64453, а это соответствует 80566,25%
Ваши расчеты не верны :)
Зануда! (это комплимент)
Надо еще IPшник в двоичном формате передавать. Вы хоть представляете сколько времени строчка парсится?
'195.54.2.1'.split('').map(&:ord).map{|i| i.to_s(2)}
=> [«110001», «111001», «110101», «101110», «110101», «110100», «101110», «110010», «101110», «110001»]? :)

Простите мою неграмотность, а что за язык? Ruby?
Ога, только я не считаю это «неграмотностью». Ruby входит в большую группу интерпретируемых ООП языков, умение отличать один от другого, по моему, бесполезный навык.
Просто синтаксис довольно интересен, особенно конструкции вида "&:ord"
Унарный амперсанд чудесен, дааа…
habrahabr.ru/post/111722/
Ruby вообще богат чудесными идиоматическими приемами, редко встречающимся в программировании.

User.first do |user|
… туткакойтакод
end.attributes.values
выдаст значение аттрибутов
но
применение методов к оператору закрытия блока разнесло мне мозг вдребезги при первой
Собственно, eval — зло только когда вы включаете в него данные, которые не контролируете — пользовательский ввод и так далее.
не только. Еще он сильно мешает оптимизации JS-кода по размеру для сложных веб-приложений: их не получается автоматически просчитать на область видимости переменных
Согласен. Но если в большом приложении существенную долю занимает eval, то проблемы начнутся задолго до минификации файлов.
Уже предвижу, как некоторые новички побегут писать код при помощи eval, потому что «я читал на хабре, что он так будет быстрее работать».
Я бы такую технику отнёс к разряду экспертных, и использовать ее можно только когда вы точно знаете, что вам это нужно, и понимаете ВСЕ плюсы и минусы, а также имеете тесты и бенчмарки, чтобы проверить, что оно действительно помогло.
Также есть замечания по самому бенчмарку:
1) Date.now() — заметно быстрее, чем new Date().getTime()
2) Вычислять дату на каждую итерацию — накладно. Лучше делать это для тысяч итераций, чтобы минимально вносить погрешности.
У меня получилось так:
    var stamp = Date.now();
    var iterations = 0;
    while(Date.now() - stamp <= 1000){
        for (var i = 0; i < 1000; i++) {
            instance.check();
        }
        iterations += 1000;
    }
    console.log((iterations * 1000 / (Date.now() - stamp)).toFixed(2) + ' iterations per second for ' + note);

Соотв-но, в Вашей версии скорость была
449194 iterations per second for self
80822 iterations per second for casual

в моей:
738261.74 iterations per second for self
91179.39 iterations per second for casual

это на ubuntu 11.04 + node.js 0.8.14
а еще можно вынести Server.ping в Server.prototype.ping и скорости будут 1 411 588 и 109 126, соответственно.
Спасибо за правку. Изначально скорость мерил у себя на живом проекте, а бенчамарк метод накидал специально для этого поста. Потом конечно спохватился по некоторым моментам, но посчитал что уже поздно, тем более основной посыл был в другом, и вы всё идеально расписали.
Идею подхватил в одном из докладов на JSConf EU 2012.

Ссылочки не найдётся?
А что в JS циклы настолько тормозят? Генеренный код точно так же словарь заполняет, единственная разница что нет цикла.

А если тоже самое сделать на замыканиях?
Проблема — не в тормозящем цикле, а в том, что тело цикла ничего не делает.
Если внимательно смотреть его доклад, то он явно говорит, что «Рад бы не использовать этот костыль потому как это очень не явный метод оптимизации» Да и он сам оптимизировал свой код экспериментально(алхимия), а не на основе структурированных знаний о V8 (химия). И в этом, конечно, нет ничего плохого потому, что все знать нельзя.

Статическое наполнение объекта быстрее динамического:
function (names, fields) {
    var result = {};

    for (var i = 0, c = names.length; i < c; i++) {
        result[names[i]] = fields.fetch();
    }

    return result;
}

Вот этот код будет работать быстрее
function (names, fields) {
    return {
        "name1": fields.fetch(),
        "name2": fields.fetch(),
        "name3": fields.fetch()
    };
}

потому как (если я не ошибаюсь) в этом месте V8 в качестве представления объектов JavaScript будет использовать скрытые классы, а в первом случае словарь. // cc mraleph

В 95% случаев вам не нужны оптимизации, в 4.5% случаев вас спасает JIT, а вот только в 0.5% случаев необходимо использовать вот такое извращение, притом не абы где, а на основе испытаний.

Используйте eval с умом! Он плохо себя ведет только в не умелых руках, поэтому его и не советуют использовать ;-) Ну и не забывайте о преждевременной оптимизации
он сам оптимизировал свой код экспериментально(алхимия), а не на основе структурированных знаний о V8 (химия)
Это как раз то что подвигло меня поделится этим подходом со всеми. Если об этом можно было прочитать в методичке, я бы хмыкнул, и продолжил бы листать :)
Кстати, именно таким подходом, я обнаружил что, например в той же мозиле, eval будет работать быстрее чем new Function, так что для наглядности решил оставить его.
Хотя, в моем коде на Node.js, я использовал исключительно new Function подход.
Какой из 2-х eval-ов? Контекста функции или глобальный?
скрытые классы всегда используются. словарное представление тоже имеет скрытый класс, который собственно и говорит что поля представлены словарем.

превратится-ли result в словарь зависит от количества полей и некоторых других факторов.

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

ну и eval'а на V8 нужно избегать как огня — произведенный им код не оптимизируется. хотя в данном конкретном случае это и не важно, потому что основное время тратится в клонировании object literal boilerplate, которое выполняется большей частью в стабе.
> произведенный им код не оптимизируется

Я когда то уже спрашивал, но переспрошу еще раз. Во всех случаях? Или «хороший» eval сработает?

new Function('global', 'global.alert(global.localStorage.item);')(window);

Наружу ничего не лезет, все работает в том же скоупе.
Никакой произведенный evalом код не оптимизируется. Но new Function — не eval, поэтому произведенный им код оптимизируется.
[зануда-мод]
Почему сначала вы приводите пример с new Function, а в результате используете eval? Это не одно и то же.

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

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

Про реализацию конструкторов я вообще молчу.
[/зануда-мод]
Прочитал комментарий выше, первый вопрос снят :)
Выше уже писал, как оказалось, в той же мозиле, прирост с new Function оказался почти не заметным, даже чуть ли не отрицательным. В данном посте решил, для наглядности, использовать eval который более привычен широкому кругу пользователей JS. Но намеренно упомянул возможность использования new Function.
Жалею, что не был более информативным по всем этим моментам в посте, так что очень рад таким замечательным правкам в комментариях.
А какие еще есть применения у подобного eval, с озвученными плюсами, кроме создания объекта без итерирования? Я что-то не могу придумать.
gist.github.com/4130117
интересно в случаях, когда структура клонируемого объекта известна заранее
дял теста либо нужен lodash/underscore.js, либо можно удалить секцию.
Ого, серьезный инкрейс. Не ожидал.
Ну описанный тут метод все равно заруливает clone из v8. Но по сравнению с ним, применимость очень ограничена.
Ну смысл модуля совсем в другом, и не имеет прямого отношения к этой статье.
Да это хэш и создавать-то не обязательно — достаточно сварганить прокси:

var LazyObserver = function( ){
var hashTable= { }
this.add= function( server ){
hashTable.__defineGetter__( server.name, function( ){
return server.ping()
})
return this
}
this.check= function( ){
return hashTable
}
}
Откуда этот фанатизм? Я о выдавливании из себя критических комментариев. Тут есть действительно отличные замечания, я даже поблагодарил за них и проплюсовал. Но иногда бывают бессмысленные замечания, чуть ли не на уровне — «В JS это не нужно!». Вы ведь, я полагаю, понимаете, что ваша реализация тут не уместна. Скрипт занимается мониторингом серверов, и если, например, нужно сделать 1000 снапшотов состояний серверов за определенный промежуток времени, такая реализация, по крайней мере в таком виде, будет совсем не уместна.
И это естественно, ведь для демонстрации этого приёма, я подогнал не реализацию под ситуацию, а наоборот. Может, и не совсем успешно.
Eval — зло. Это утверждение можно принять на веру. Но если подумать…
С другой стороны не всегда есть время подумать.
Если нет времени подумать, то eval — зло.
Фееричная ерунда, уж простите за откровенность.

Мало того, что экономите на спичках, так еще и вешаете на стену ружжо, которое обязательно станцует и споет, как это принято по законом индийского кино :)
Eval — зло, with — зло. Если хочешь быть приличным командным кодером — это догматы.

Кроме того, JIT весьма обидчив, и если, вымахиваясь, вы его перехитрите, то он на вам обидется и оставит без сладкого, в одиночестве, с неоптимизированным кодом.
Sign up to leave a comment.

Articles