Визуализация динамического процесса на графике — один из способов получения новой/дополнительной информации. В публикации показана простая утилита, с помощью которой можно увидеть работу JavaScript-таймеров. С одной стороны, JavaScript-таймеры хорошо подходят для организации циклического процесса. С другой стороны, они, в общем виде, наглядно демонстрируют поведение однопоточной JavaScript-многозадачности, что, в ряде случаев, тоже может быть полезным.

Изображение - JavaScript-таймеры

График позволяет:
  • Увидеть работу JavaScript-таймера в реальном времени.
  • Смоделировать разные условия нагрузки.
  • Сравнить работу разных браузеров.
  • Даёт информацию к размышлению (некоторые результаты оказались любопытными).


Введение


По горизонтальной оси (X) измеряется количество пройденных циклов. По вертикальной оси (Y) измеряется время в миллисекундах (мс). Начало — по кнопке «СТАРТ». Завершение — происходат автоматически, после визуального заполнения данных по оси X.

Измеряемые значения:
  • Красный: промежуток времени от начала одного цикла до начала другого (время полного цикла).
  • Белый: промежуток времени от окончания одного цикла до начала другого (время задержки).
  • Синий: время видеонагрузки. Видеонагрузкой является графическое отображение этого графика.
  • Циан: время псевдонагрузки. Псевдонагрузкой является простое циклическое повторение JavaScript-кода.

Работоспособность проверялась:
  • Windows: FF, OPERA, CR, IE.
  • Linux: FF, OPERA.

Код на GitHub.
Демо на GitHub (так-же работает с диска).

Типы таймеров


setTimeout

setTimeout используется для разовых задержек исполнения кода и для организации циклов. Цикл на основе setTimeout:
MyFunc = function () {
   
   // прикладной код
     
   setTimeout(function () {
            MyFunc();
       }, 100);
}

Здесь всё, вроде-бы, очевидно: 100 — это задержка в миллисекундах (мс), через которую будет запущен следующий цикл задания. Но, при организации цикла, интуитивно ожидаешь, что код будет выполняться именно с этим периодом. На самом деле (видно на графике), фактическое время цикла будет состоять из времени выполнения кода плюс времени задержки. То есть, чем сложнее код — тем длиннее будет фактическое время цикла.

setInterval

setInterval используется для исполнения кода через определённые промежутки времени. Цикл на основе setInterval:
MyTimer = function () {
    setInterval(function () {
            MyFunc(); // прикладной код
        }, 100);
}

На графике видно, что setInterval контролирует время полного цикла. При изменении времени выполнения кода, «интеллектуально» корректируется время между вызовами так, чтобы время полного цикла оставалось стабильным. Если время выполнения кода, превышает заданный период, то таймер, исчерпав возможности по уменьшению времени между вызовами, начинает вызываться с удлинённым периодом.

requestAnimationFrame

requestAnimationFrame используется для циклической перерисовки экрана, которая синхронизирована с циклами перерисовки браузера. Цикл на основе requestAnimationFrame:
MyFunc = function () {
   
   // прикладной код
     
    requestAnimationFrame(function () {
            MyFunc();
        });
}


setTimeout/setInterval + requestAnimationFrame

Теоретически, гибридные решения должны были совместить возможности точного таймирования и синхронизации отрисовки с циклом обновления экрана. Цикл на основе setTimeout + requestAnimationFrame:
MyFunc = function () {
   
   // прикладной код

    setTimeout(function () {
       requestAnimationFrame(function () {
              MyFunc();
           });
      }, 100);
}

Цикл на основе setInterval + requestAnimationFrame:
MyTimer = function () {
    setInterval(function () {
        requestAnimationFrame(function () {
               MyFunc(); // прикладной код
            });
        }, 100);
}

Функционально код выполняется, но, при этом, наблюдается повышенная нестабильность периодов циклов.

Наблюдения:
  • Для разных браузеров поведение таймеров может отличаться. Особенно это проявляется при низких значениях времени задержки и/или при повышенных нагрузках.
  • Если в браузере, с закладки с графиком перейти на другую закладку (в том-же окне), то работа таймеров останавливается, и снова возобновляется после активизации закладки с графиком. Если же открыть в браузере другое окно, то работа таймеров не прекращается.


Видеонагрузка


В качестве видеонагрузки используются функции отрисовки графика на Canvas.

При выборе простой функции (APELSERG.CANVA.Paint), сетка графика отрисовывается один раз, при старте. И, далее, в процессе построения графика, выводится только текущий результат (четыре точки) и счётчик циклов.

При выборе сложной функции (APELSERG.CANVA.PaintComplex), все результаты сохраняются в массивы. Каждый раз при отрисовке, заново обновляется сетка графика и выводятся все сохранённые результаты из всех массивов.

Псевдонагрузка


Псевдонагрузка — это простой цикл на JavaScript, в котором выбирается случайное значение:
APELSERG.MAIN.VirtCalc = function () {
    for (var n = 0; n < APELSERG.CONFIG.SET.TypeVirtCalc ; n++) {
        var q = Math.round(Math.random() * 100);
    }
}

Цикл вынесен в отдельную функцию, чтобы проще можно было подставить другой код. Можно выбрать количество циклов от 0 до 1 000 000 000. Результат тестирования под псевдонагрузкой, в разных браузерах, немного удивил.

Масштабирование и сохранение графика


Чтобы изменить масштаб сетки, надо:
  1. Изменить масштаб отображения браузера (Ctrl-, Ctrl+).
  2. Перезагрузить страницу.
  3. Выбрать размер вывода значения (от 1 до 4 px).


Полученный график можно сохранить как стандартное изображение:
  1. Правая кнопка мыши на графике.
  2. Сохранить изображение как...


Полезные ссылки


Основной цикл в Javascript
О том, как работают JavaScript таймеры

Комментарии (6)


  1. YNile
    16.06.2015 17:14

    MyTimer = function () {
        setInterval(function () {
                MyFunc(); // прикладной код
            }, 100);
    }
    

    Тут точно так?
    Может имелось ввиду так?
    MyTimer = function () {};
    setInterval(MyFunc, 100);
    


    Или не понятно. У вас после каждого интервала объявляется ещё один интервал.


    1. apelserg Автор
      16.06.2015 18:13

      Функциональной ошибки нет. Функция MyTimer должна вызываться один раз для старта setInterval (в исходном коде это хорошо видно — функция APELSERG.MAIN.Timer).

      Согласен, что правильнее было бы её назвать, например, StartMyTimer.


  1. kahi4
    16.06.2015 19:38

    Не могу понять, почему прыгает (иногда сильно) белая линия, если она должна быть прямой и показывать уровень (в 40 мс по умолчанию)?


    1. DenimTornado
      16.06.2015 23:06

      Подозреваю, что в том, что 40мс там далеко не всегда 40.

      var start_time = new Date().getTime();
      var time = new Date().getTime();
      var interval = setInterval(function() {
      var new_time = new Date().getTime();
      var delta = new_time — time;
      time = new_time;
      console.log(delta);
      if (time — start_time > 5000) {
      clearInterval(interval);
      }
      }, 1000);


  1. b1rdex
    17.06.2015 07:33

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

    Проверил, всё так и есть. Разница между планируемой задержкой и фактом примерно равна времени блокировки.

    Делаем вот так:

    setTimeout(function() {
        // Виртуальная нагрузка
    
        var startVirtCalcDate = new Date();
        var startVirtCalcTime = startVirtCalcDate.getTime();
    
        APELSERG.MAIN.VirtCalc();
    
        var stopVirtCalcDate = new Date();
        var stopVirtCalcTime = stopVirtCalcDate.getTime();
    
        APELSERG.TOOLBOX.prevVirtCalcMsec = APELSERG.TOOLBOX.currVirtCalcMsec;
        APELSERG.TOOLBOX.currVirtCalcMsec = stopVirtCalcTime - startVirtCalcTime;
    })
    

    И красное сливается с белым.


    1. apelserg Автор
      17.06.2015 13:56

      Конкуренция таймеров — это ещё одна интересная тема. В приложении могут конкурировать, например, setInterval и requestAnimationFrame. А ещё, как минимум, прерывания мыши, клавиатуры, изменения в DOM. Поэтому, такое комплексное поведение, лучше рассматривать отдельно.

      В данной реализации псевдонагрузка введена:

      • Для примерной оценки стабильности работы заданного таймера под нагрузкой.
      • Для сравнения браузеров (одним достаточно 10 миллионов циклов, другие переваривают миллиард).

      Для наглядного ответа на ваш комментарий я создал ещё один проект (самому стало интересно) и ввёл конкурентный таймер setInterval (функция APELSERG.MAIN.StartTimer2), который работает для любого выбранного режима. В конкурентный таймер я поместил псевдонагрузку, а из основного таймера — её убрал.

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

      Код с конкурентным таймером на GitHub.
      Демо с конкурентным таймером на GitHub.