为啥我在 chrome 开发工具性能面板中得到 30 fps,但 JS 和 React 都说是 60?
Posted
技术标签:
【中文标题】为啥我在 chrome 开发工具性能面板中得到 30 fps,但 JS 和 React 都说是 60?【英文标题】:Why I get 30 fps in chrome dev tool performance panel, but JS and React both say it's 60?为什么我在 chrome 开发工具性能面板中得到 30 fps,但 JS 和 React 都说是 60? 【发布时间】:2020-06-13 11:36:44 【问题描述】:我正在尝试使用react开发经典游戏教程how-to-make-a-simple-html5-canvas-game。
一切都很顺利,直到我发现我的动作有点小故障,online test link 和 code。
虽然原来用JS写的game要流畅得多:
所以我稍微研究了一下,发现实际的 fps 是不同的:
反应:
纯JS:
奇怪的是,在我向 calc fps 添加了一些代码后,我在 react hook 和 useEffect 中都得到了“60 fps”:
// log interval in useEffect
useEffect(() =>
console.log('interval', Date.now() - renderTime.current);
renderTime.current = Date.now();
);
// calc fps in hook directly
fps: rangeShrink(Math.round(1000 / (Date.now() - time.current)), 0, 60),
// render
<Text
x=width - 120
y=borderWidth
text=`FPS: $fps`
fill="white"
fontSize=24
align="right"
fontFamily="Helvetica"
/>
定位问题
我添加了一个对比画布,每次更新 heroPos
时都会呈现。它让我在 chrome 开发工具中获得 60FPS。现在问题肯定是由我正在使用的画布库引起的:react-konva。
const canvasRef = useRef(null);
useEffect(() =>
const ctx = canvasRef.current.getContext('2d');
if (backgroundStatus === 'loaded')
ctx.drawImage(backgroundImage, 0, 0);
if (heroStatus === 'loaded')
ctx.drawImage(heroImage, heroPos.x, heroPos.y);
, [backgroundStatus, heroStatus, heroPos]);
发现问题
我找到了问题,是使用了batchDrawreact-konva
引起的:
更改这条线后,我现在可以得到 60fps 的移动。
- drawingNode && drawingNode.batchDraw();
+ drawingNode && drawingNode.draw();
根据他们的doc,batchDraw 将绘制the next animationFrame
。但是react
本身也使用RAF
来触发下一次props更新,所以这里的batchDraw
发生在我2 frames
之后setHeroPos()
。
解决方案:
我要向他们的项目提交pull-request。
【问题讨论】:
您应该使用您找到的内容发布自我答案,而不是将其添加为编辑。它可能对未来的读者也很有用,它比当前接受的答案更好地解释了为什么 30FPS 而不是 60FPS。 【参考方案1】:开发工具会给设备增加很多额外的负载。当您记录性能日志时更是如此。
React 是我用于实时应用程序的最后一件事,因为它为最简单的任务添加了幕后 JS。
通过测量帧之间的时间来计算性能并不能准确地指示性能。
性能
要测量函数的性能,请使用performance
API。最简单的方法是通过performance.now
使用它来获取函数完成所需的时间。
例如获取游戏中主循环函数的时间
function mainLoop(frameTime)
const now = performance.now(); // MUST BE FIRST LINE OF CODE TO TEST!!!!
requestAnimationFrame(mainLoop);
const executeTime = performance.now() - now; // MUST BE LAST LINE OF CODE TO TEST!!!
这将为您提供以毫秒为单位的执行时间。因为 JS 是阻塞的,所以只测量两行内的代码。
注意不会测量任何添加开销,例如 GC、合成、同步加载等...
注意毫秒(1/1,000,000)
注意此值的精度 performance.now
具有 deliberately been reduced 以保护用户,并且根据浏览器的不同在 100 毫秒 - 200 毫秒之间(可以在标志和系统后面访问 1 毫秒)配置))
有意义的表现
JS 执行是不确定的,这使得单个计时测量完全不可靠。 (使用performance.now
比使用peformance.mark
更好的原因)
要克服 JS 执行的不确定性和计时器的不准确性,请使用运行均值来为您的代码计时。下面的示例显示了如何执行此操作。
使用与应用程序需求相关的指标,而不是显示时间。例如,执行代码花费了多少帧。 (见例子)
示例
此示例使用requestAnimationFrame
为一些画布内容设置动画。
滑块可让您选择函数应花费渲染的大致时间。
顶部的信息文本将计时结果显示为运行平均值。
您会注意到,在帧速率下降之前,理想化帧负载 (IFL) 远低于 100%。
实验
-
开发工具和性能监控如何影响性能。
当帧率低于 60 时,将滑块移动到正下方。
打开开发工具,看看它是否以及如何影响明显的性能。记下任何变化。有没有影响,如果有,影响多少?
记录性能日志并查看 FPS 和/或 IFL 是否受记录影响
-
在影响帧速率之前,您的设备可以分配给渲染的最长时间是多少。
将滑块缓慢向右移动。
当帧速率低于 60 时,将幻灯片向后移动一步,直到它再次读取 60FPS。
IFL 值将给出完美帧(第 60 秒)执行代码的百分比。 时间以毫秒为单位的绝对执行时间。
Math.rand = (min, max) => Math.random() * (max - min) + min;
Math.randItem = arr => arr[Math.random() * arr.length | 0];
CPULoad.addEventListener("input",() => loadTimeMS = Number(CPULoad.value));
var loadTimeMS = Number(CPULoad.value);
const ctx = canvas.getContext("2d");
requestAnimationFrame(mainLoop);
function mainLoop(frameTime)
/* Timed section starts on next line */
const now = performance.now();
CPU_Load(loadTimeMS);
ctx.globalAlpha = 0.3;
ctx.fillStyle = "#000";
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
requestAnimationFrame(mainLoop);
const exeTime = performance.now() - now;
/* Timed section ends at above line*/
measure(info, frameTime, exeTime);
const measure = (() =>
const MEAN = (t, f) => t += f;
const fTimes = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], bTimes = [...fTimes];
var pos = 0, prevTime, busyFraction;
return (el, time, busy) =>
if (prevTime)
bTimes[pos % bTimes.length] = busy;
fTimes[(pos ++) % fTimes.length] = time - prevTime;
const meanBusy = bTimes.reduce(MEAN, 0) / bTimes.length;
const meanFPS = fTimes.reduce(MEAN, 0) / fTimes.length;
el.textContent = "Load: " + loadTimeMS.toFixed(1) + "ms " +
" FPS: " + Math.round(1000 / meanFPS) +
" IFL: " + (meanBusy / (1000 / 60) * 100).toFixed(1) + "%" +
" Time: " + meanBusy.toFixed(3) + "ms";
busyFraction = meanBusy / (1000/60);
prevTime = time;
;
)();
const colors = "#F00,#FF0,#0F0,#0FF,#00F,#F0F,#000,#FFF".split(",");
// This function shares the load between CPU and GPU reducing CPU
// heating and preventing clock speed throttling on slower systems.
function CPU_Load(ms) // ms = microsecond and is a min value only
const now = performance.now();
ctx.globalAlpha = 0.1;
do
ctx.fillStyle = Math.randItem(colors);
ctx.fillRect(Math.rand(-50,250), Math.rand(-50, 100), Math.rand(1, 200), Math.rand(1,100))
while(performance.now()-now <= ms);
ctx.globalAlpha = 1;
body
font-family: arial;
#info
position: absolute;
top: 10px;
left: 10px;
background: white;
font-size:small;
width:345px;
padding-left: 3px;
#canvas
background: #8AF;
border: 1px solid black;
#CPULoad
font-family: arial;
position: absolute;
top: 130px;
left: 10px;
color: black;
width: 340px !important;
<code id="info"></code>
<input id="CPULoad" min="0" max="36" step="0.5" value="2" type="range" list="marks"/>
<canvas id="canvas" ></canvas>
<datalist id="marks">
<option value="0"></option>
<option value="4"></option>
<option value="8"></option>
<option value="12"></option>
<option value="16"></option>
<option value="20"></option>
<option value="24"></option>
<option value="28"></option>
<option value="32"></option>
<option value="36"></option>
</datalist>
注意 时间的显示会影响结果。此代码在沙盒 sn-p 中运行的事实将影响结果。为了获得最准确的结果,请在独立页面上运行代码。将结果记录到 JS 数据结构中,并在测试运行后显示结果。
负载:请求的 CPU/GPU 执行负载在 1/1000 秒内。
FPS:平均每秒帧数。
IFL:理想化帧负载,第 60 秒执行代码的百分比。
时间:测量的平均执行时间(以 1/1000 秒为单位)。
【讨论】:
React 可以很好地进行反应。您只需要考虑很多性能,例如PureComponents、React.memo 等 很好的解释。我尝试了performance.now()
,但它最终向我显示了更准确的时间(从 16 毫秒到 16.213123 毫秒)。但这并不能解决我的问题...
@Stupidism 16ms 太长了。示例代码将为您估计一帧需要多长时间才能保持恒定的帧速率。对于普通设备,它约为 60 - 70%,低端设备下降,高端设备更高。原因是浏览器需要时间来完成它的工作(合成、可能的回流、事件、GC 等等)。我确实暗示了一个解决方案,React 不适合实时应用程序
@Blindman67 感谢您耐心的解释。感谢您不仅为我调试,还教导和指导我找到答案。
"注意:毫秒 (1/1,000,000th) 注意:此值性能的精度。现在 [...] 介于 100 毫秒 - 200 毫秒之间”。哼什么? FF 具有最大的舍入因子,只有一个毫秒。 Chrome 有一些抖动,但仍处于微秒精度。 200 毫秒的精度将使秒表更有用:-)以上是关于为啥我在 chrome 开发工具性能面板中得到 30 fps,但 JS 和 React 都说是 60?的主要内容,如果未能解决你的问题,请参考以下文章