Производительность холста HTML5 — расчет циклов/кадров в секунду

Я знаю, что раньше задавали несколько подобных вопросов, например: Проверить FPS в JS? - что в какой-то степени сработало, я смог узнать, сколько времени потребовалось для завершения каждого цикла.

Однако я ищу что-то более читабельное и управляемое. Я хочу иметь возможность установить частоту обновления для счетчика FPS, чтобы сделать его медленным, чтобы он был удобочитаемым для человека, или настолько быстрым, насколько может работать приложение, чтобы я мог использовать его на каком-то спидометре.

Во всяком случае, вот код, который у меня есть прямо сейчас:

var lastLoop = new Date().getTime();

function updateStage()
{   
    clearCanvas();
    updateStageObjects();
    drawStageObjects();     

    var thisLoop = new Date().getTime(); 
    var fps = (thisLoop - lastLoop);

    $('#details').html(fps);

    lastLoop = thisLoop;
    iteration = setTimeout(updateStage, 1);
}
  1. Правильно ли я устанавливаю функцию setTimeout на скорость 1 миллисекунду? Я думал, что это просто сделает цикл настолько быстрым, насколько это возможно.

  2. Должен ли я считать каждые 100 кадров или около того, узнать, сколько миллисекунд потребовалось для запуска 100 кадров, а затем выполнить расчет, чтобы узнать, сколько кадров было бы сделано, если бы миллисекунды были 1000? Каким будет этот расчет?

  3. Чтобы сделать результат более точным, я предполагаю, что мне нужно отображать средние значения, поскольку один кадр может значительно отличаться, как мне это сделать?

Любые советы приветствуются.

Спасибо.


person Sabai    schedule 22.02.2011    source источник


Ответы (5)


  1. Обратите внимание, что чем быстрее вы обновите выходные данные, тем больше вы повлияете на свои измерения. Хотя это минимально, я стараюсь обновлять вывод fps раз в секунду или реже, если только нет необходимости работать быстрее.

  2. Мне нравится использовать фильтр нижних частот для моих результатов, чтобы временная икота не слишком сильно влияла на значения. Это легче вычислить и записать, чем скользящее среднее, и у него нет проблемы с общим средним значением, когда на ваши «текущие» показания влияет общая производительность за весь прогон (например, аномальные показания во время запуска).

В общем, вот как я обычно измеряю FPS:

var fps = 0, now, lastUpdate = (new Date)*1;

// The higher this value, the less the FPS will be affected by quick changes
// Setting this to 1 will show you the FPS of the last sampled frame only
var fpsFilter = 50;

function drawFrame(){
  // ... draw the frame ...

  var thisFrameFPS = 1000 / ((now=new Date) - lastUpdate);
  if (now!=lastUpdate){
    fps += (thisFrameFPS - fps) / fpsFilter;
    lastUpdate = now;
  }

  setTimeout( drawFrame, 1 );
}

var fpsOut = document.getElementById('fps');
setInterval(function(){
  fpsOut.innerHTML = fps.toFixed(1) + "fps";
}, 1000); 
person Phrogz    schedule 22.02.2011
comment
Эй, спасибо за ваш ответ, кажется, все имеет смысл. Однако по какой-то причине мой вывод — NaN, я вставил сюда свой адаптированный код, чтобы вы могли видеть: jsfiddle.net /ksgSg. - person Sabai; 22.02.2011
comment
@Henryz Как неловко это произойдет, если теперь == lastUpdate при первом проходе. Я отредактировал его с помощью охранника для этого. (Думаю, обычно я использую setTimeout при первом вызове drawFrame.) - person Phrogz; 22.02.2011
comment
Кажется, что это работает лучше, но все равно иногда приравнивается к NaN, если его оставить работать на 5-10 секунд. Я не уверен, почему, я не совсем смог понять это. - person Sabai; 23.02.2011
comment
Проблема NaN не возникает, если для fpsFilter установлено низкое число, например 10. Так что у меня это работает до такой степени. Пожалуйста, если у вас есть время, не могли бы вы дать мне быстрое объяснение, как это работает: (fps += (thisFrameFPS - fps) / fpsFilter;). Я полностью понимаю остальное, я просто не могу понять, почему это дает точные результаты. Спасибо! - person Sabai; 23.02.2011
comment
Я не могу придумать ни одной причины, по которой код должен когда-либо давать вам NaN, и даже меньше, чем ноль, причин, по которым выбранный вами фактор может повлиять на это. Можете ли вы вставить свой фактический код? В любом случае, это медленно корректирует fps по разнице между ним и текущей частотой кадров. Если ваш коэффициент равен 1, то формула будет fps = fps + thisFrameFPS - fps, которая просто установит его в thisFrameFPS. Если ваш коэффициент равен 2, а частота кадров прыгает, скажем, со стабильной скорости 10 до стабильной скорости 20, то сначала она добавит 5 к кадрам в секунду, затем 2,5, затем 1,25 и так далее. - person Phrogz; 24.02.2011
comment
@Henryz Если это поможет, вот пример из реальной жизни с использованием описанной выше техники. Хотя у него значение фильтра 10, у меня он работает так же хорошо с коэффициентом 1000. - person Phrogz; 25.02.2011
comment
Привет, спасибо, что ответили мне, да, теперь я понимаю, как работает фильтр, спасибо за это. У меня есть FTP-скрипт, так что вы можете увидеть, как он работает (нажмите «Обновить» несколько раз, если сначала вы видите пустую страницу). Когда вы просматриваете исходный код, материал находится в script.js. Как я уже сказал, это работает хорошо, просто у меня проблема с NaN, которая возникает чаще, когда fpsFilter выше. Спасибо еще раз! - henry.brown.name/experiments/fps - person Sabai; 25.02.2011
comment
@Henryz Спасибо, что поделились кодом. Я никогда не видел, чтобы он достигал NaN, даже при очень высокой настройке фильтра 1000. (См. ответ здесь для более подробной информации о влиянии силы фильтра; при 1000 потребуется 700 кадров, чтобы перейти от начального значения частоты кадров 0 к половине установившегося значения частоты кадров.) Что ОС/браузер/версия, в которой вы видите NaN? - person Phrogz; 25.02.2011
comment
Привет, извини, что я не вернулся к вам раньше. Я использую OS X, Firefox 3.6. Я продолжу тестирование в других браузерах и сообщу вам о своих результатах. - person Sabai; 02.03.2011
comment
Правильная ссылка - мой ответ здесь для моего комментария выше; были неверные данные в буфере обмена, кажется. - person Phrogz; 02.03.2011
comment
К вашему сведению - если @Henryz имеет: lastUpdate = (новая дата); (Обратите внимание на отсутствие *1) он увидит NaN. Я видел эту проблему в своем коде. - person raddevus; 14.11.2014

Я пробовал кое-что,

Если вы измените

lastUpdate = now

to

lastUpdate = now * 1 - 1;

Ваша проблема с NaN решена! Это также используется там, где определено lastUpdate. Вероятно, потому что он не может преобразовать дату в отметку времени unix.

Новый результат будет:

var fps = 0, now, lastUpdate = (new Date)*1 - 1;

// The higher this value, the less the FPS will be affected by quick changes
// Setting this to 1 will show you the FPS of the last sampled frame only
var fpsFilter = 50;

function drawFrame(){
  // ... draw the frame ...

  var thisFrameFPS = 1000 / ((now=new Date) - lastUpdate);
  fps += (thisFrameFPS - fps) / fpsFilter;
  lastUpdate = now * 1 - 1;

  setTimeout( drawFrame, 1 );
}

var fpsOut = document.getElementById('fps');
setInterval(function(){
  fpsOut.innerHTML = fps.toFixed(1) + "fps";
}, 1000); 
person Niels    schedule 11.11.2011

Я взял опубликованные решения и немного улучшил их. Посмотрите здесь - http://jsfiddle.net/ync3S/

  1. Я исправил эту ошибку NaN, используя Date.now() вместо того, чтобы каждый раз создавать новый объект даты и пытаться ссылаться на него. Это также предотвращает необходимость сбора мусора.
  2. Я немного подправил имена переменных и функций и добавил дополнительные комментарии — не обязательно, но приятно.
  3. Я включил код рисования для тестирования.
  4. Я добавил fpsDesired в качестве тестовой переменной для цикла движка.
  5. Я запустил fpsAverage с fpsDesired, поэтому с fpsFilter он не работает с 0 до реального FPS, а начинает с желаемого FPS и настраивается оттуда.
  6. Рисование сейчас блокируется, если оно уже рисовалось, и это можно использовать для приостановки и других функций управления.

Основной блок выглядит следующим образом:

var fpsFilter = 1; // the low pass filter to apply to the FPS average
var fpsDesired = 25; // your desired FPS, also works as a max
var fpsAverage = fpsDesired;
var timeCurrent, timeLast = Date.now();
var drawing = false;

function fpsUpdate() {
    fpsOutput.innerHTML = fpsAverage.toFixed(2);
}

function frameDraw() {
    if(drawing) { return; } else { drawing = true; }

    timeCurrent = Date.now();
    var fpsThisFrame = 1000 / (timeCurrent - timeLast);
    if(timeCurrent > timeLast) {
        fpsAverage += (fpsThisFrame - fpsAverage) / fpsFilter;
        timeLast = timeCurrent;
    }

    drawing = false;
}

setInterval(fpsUpdate, 1000);
fpsUpdate();

setInterval(frameDraw, 1000 / fpsDesired);
frameDraw();

Собираюсь повозиться и посмотреть, смогу ли я придумать что-то более гладкое, так как эта тема находится в верхней части результатов Google.

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

-Платима

person user2552832    schedule 24.03.2014

Просто установите интервал, который сбрасывает счетчик кадров каждую секунду.

var fpsOut, fpsCount;

var draw = function () {

    fpsCount++;

    ..Draw To Canvas..


    ..Get the fps value: fpsOut

    requestAnimationFrame(draw);

};
setInterval(function () {

    fpsOut = fpsCount;
    fpsCount = 0;

}, 1000);

draw();
person Gustav G    schedule 04.05.2016

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

Обратите внимание, что один кадр длится 16,66 миллисекунды.

setInterval(function(){var latencybase1 = parseFloat(new Date().getTime());
var latencybase2 = parseFloat(new Date().getTime());
var latency = latencybase2-latencybase1;
var fps = Math.round(1000/latency);

if (latency<16.66) 
{document.getElementById("FPS").innerHTML = fps+" 
FPS";}
else {document.getElementById("FPS").innerHTML = ""+fps+" FPS";}
document.getElementById("Latency").innerHTML = latency+" ms";}, 0);
person Dev60    schedule 11.10.2020
comment
Я могу ошибаться, но, похоже, этот код измеряет только время, необходимое для вызова второго new Date().getTime()), и ничего более. Также будьте осторожны, 60 Гц — не единственная частота обновления, setInterval имеет минимальную задержку 4 мс после 5-й итерации, а установка innerHTML более одного раза за кадр рендеринга просто убивает деревья напрасно. - person Kaiido; 11.10.2020