2012-09-14 3 views
9

Для предстоящего проекта с node.js мне нужно выполнять различные домашние задания в периодические моменты. В частности, некоторые задачи каждые миллисекунды, другие каждые 20 мс (50 раз в секунду) и третьи каждую секунду. Поэтому я подумал об использовании setInterval(), со смешными результатами: многие вызовы функций были пропущены.node.js: setInterval() пропущенные вызовы

Тест я использовал это следующим образом:

var counter = 0; 
var seconds = 0; 
var short = 1; 
setInterval(function() { 
     counter ++; 
    }, short); 
setInterval(function() { 
     seconds ++; 
     log('Seconds: ' + seconds + ', counter: ' + 
      counter + ', missed ' + 
      (seconds * 1000/short - counter)); 
    }, 1000); 

Существует длинный таймер одной секунды и коротким, что можно регулировать с помощью переменной short, в данном случае 1 мс. Каждую секунду мы печатаем разницу между количеством ожидаемых тиков в коротком цикле и фактическим количеством повторений короткого счетчика.

Вот как он ведет себя, когда короткий таймер 1 мс:

2012-09-14T23:03:32.780Z Seconds: 1, counter: 869, missed 131 
2012-09-14T23:03:33.780Z Seconds: 2, counter: 1803, missed 197 
2012-09-14T23:03:34.781Z Seconds: 3, counter: 2736, missed 264 
... 
2012-09-14T23:03:41.783Z Seconds: 10, counter: 9267, missed 733 

Многие вызовы функций пропускаются. Здесь оно составляет 10 мс:

2012-09-14T23:01:56.363Z Seconds: 1, counter: 93, missed 7 
2012-09-14T23:01:57.363Z Seconds: 2, counter: 192, missed 8 
2012-09-14T23:01:58.364Z Seconds: 3, counter: 291, missed 9 
... 
2012-09-14T23:02:05.364Z Seconds: 10, counter: 986, missed 14 

Лучше, но примерно один вызов функции пропускается каждую секунду. А в течение 20 мс:

2012-09-14T23:07:18.713Z Seconds: 1, counter: 46, missed 4 
2012-09-14T23:07:19.713Z Seconds: 2, counter: 96, missed 4 
2012-09-14T23:07:20.712Z Seconds: 3, counter: 146, missed 4 
... 
2012-09-14T23:07:27.714Z Seconds: 10, counter: 495, missed 5 

Наконец в течение 100 мс:

2012-09-14T23:04:25.804Z Seconds: 1, counter: 9, missed 1 
2012-09-14T23:04:26.803Z Seconds: 2, counter: 19, missed 1 
2012-09-14T23:04:27.804Z Seconds: 3, counter: 29, missed 1 
... 
2012-09-14T23:04:34.805Z Seconds: 10, counter: 99, missed 1 

В этом случае он пропускает очень мало звонков (разрыв увеличился до 2 после 33 секунд и 3 после 108 секунд

.

Номера отличаются друг от друга, но на удивление согласуются между прогонами: запуск первого теста 1 мс три раза дал задержку через 10 секунд 9267, 9259 и 9253.

Я не нашел ссылок для этой конкретной проблемы. Существует much cited Ressig post и множество связанных с ним вопросов JavaScript, но большинство полагают, что код работает в браузере, а не в node.js.

Теперь о страшном вопросе: что здесь происходит? Просто шутка; очевидно, что вызовы функций пропускаются. Но я не вижу картины. Я думал, что длинные циклы могут препятствовать коротким, но это не имеет никакого смысла в случае 1 мс. Вызов функций короткого цикла не перекрывается, поскольку они просто обновляют переменную, а процесс node.js составляет около 5% CPU даже с коротким циклом в 1 мс. Средняя нагрузка высокая, хотя около 0,50. Я не знаю, почему тысячи звонков очень сильно подчеркивают мою систему, поскольку node.js обрабатывает many more clients perfectly; должно быть верно, что setInterval() is CPU intensive (или я делаю что-то не так).

Очевидным решением является группировка вызовов функций с использованием более длинных таймеров, а затем многократное выполнение коротких циклов для имитации более короткого таймера. Затем используйте длинный цикл как «универсал-метла», который пропускает любые вызовы в нижних интервалах. Пример: настройка 20 мс и 1000 мс setInterval() вызовов. Для вызовов 1 мс: вызовите их 20 раз в обратном вызове 20 мс. Для вызова 1000 мс: проверьте, сколько раз функция 20 мс была вызвана (например, 47), выполните все оставшиеся вызовы (например, 3). Но эта схема будет немного сложной, поскольку вызовы могут накладываться интересными способами; также он не будет регулярным, хотя он может выглядеть так.

Реальный вопрос: возможно ли это сделать лучше, либо с помощью setInterval(), либо с помощью других таймеров в node.js? Заранее спасибо.

ответ

10

Функции SetInterval в javascript не точны. Вы должны попытаться использовать таймер с высоким разрешением. Building accurate Timers in javascript

+0

Как? Какие таймеры разрешения, библиотека? – alexfernandez

+0

Есть много сценариев таймера высокого разрешения в google.http: //www.sitepoint.com/creation-exact-timers-in-javascript/ – zer02

+0

Это не решит текущую проблему. –

8

Посмотрите на этот документ: http://nodejs.org/api/timers.html#timers_settimeout_callback_delay_arg

Важно отметить, что ваш обратный вызов не будет, вероятно, можно назвать точно задержать миллисекунды - Node.js не дает никаких гарантий относительно точных сроков, когда обратного вызова будет срабатывать , ни заказывающие вещи не будут срабатывать. Обратный вызов будет вызван как можно ближе к указанному времени.

Это происходит потому, что код приложения блокирует цикл события. Все таймеры и события ввода-вывода могут обрабатываться только на nextTick.

Вы можете увидеть это поведение с помощью этого кода:

setInterval(function() { 
    console.log(Date.now()); 
    for (var i = 0; i < 100000000; i++) { 
    } 
}, 1); 

Попробуйте изменить подсчет итераций и увидеть результаты.

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

+0

Я читал эту ссылку, но это не объясняет почему. В моем тестировании у меня нет кода приложения, блокирующего цикл события. Ссылка на nextTick интересна, спасибо. Однако это только толкает проблему на один уровень: как часто происходит процесс .nextTick() fire? Почему, и может ли это быть изменено? – alexfernandez

+0

Нет, я не использовал 'process.nextTick()' как решение. Я хотел сказать, что нет возможности обрабатывать таймеры и события ввода-вывода чаще, чем время выполнения одной итерации цикла событий. –

+0

Понял. Но как долго проходит одна итерация цикла событий, при отсутствии нагрузки? – alexfernandez

2

Ответ является комбинацией тех, которые даны Вадимом и Цер02, поэтому я оставляю здесь рецензию. Как сказал Вадим, система не может справиться со слишком частыми обновлениями, и добавление некоторой нагрузки в систему не поможет. Вернее, время выполнения не может справиться; система должна быть более чем способна активировать обратный вызов каждые миллисекунды, если это необходимо, но по какой-то необъяснимой причине часто это не хочется.

Решение должно использовать accurate timers, как прокомментировал zer02. Не вводите в заблуждение имя; используемый механизм является одним и тем же setTimeout(), но задержка настраивается в зависимости от времени, оставшегося до срабатывания таймера. Итак, если время закончится, то «точный таймер» вызовет setTimeout (обратный вызов, 0), который запускается немедленно. На удивление, системная нагрузка меньше, чем с setInterval(): около 2% процессора вместо 5%, в моем очень ненаучном образце.

Эта простая функция может пригодиться:

/** 
* A high resolution timer. 
*/ 
function timer(delay, callback) 
{ 
    // self-reference 
    var self = this; 

    // attributes 
    var counter = 0; 
    var start = new Date().getTime(); 

    /** 
    * Delayed running of the callback. 
    */ 
    function delayed() 
    { 
     callback(delay); 
     counter ++; 
     var diff = (new Date().getTime() - start) - counter * delay; 
     setTimeout(delayed, delay - diff); 
    } 

    // start timer 
    delayed(); 
    setTimeout(delayed, delay); 
} 

Для использования, просто позвоните new timer(delay, callback);. (Да, я изменил порядок параметров, так как обратный вызов вначале очень раздражает.)

Последнее замечание: setTimeout (обратный вызов, задержка) не использует рекурсию, как я боялся (как в: ждать некоторое время, затем вызовите обратный вызов), он просто помещает обратный вызов в очередь, которая будет вызываться средой выполнения, когда ее очередь приходит, в глобальном контексте.

Смежные вопросы