reactjs - 为什么我在 chrome 开发工具性能面板中得到 30 fps,但 JS 和 React 都说是 60?
问题描述
我正在尝试使用 react 开发经典游戏教程how-to-make-a-simple-html5-canvas-game。
一切都很顺利,直到我发现我的动作有点小故障,在线测试链接和代码。
虽然用 JS 编写的原始游戏要流畅得多:
所以我深入研究了一下,发现实际的 fps 是不同的:
反应:
奇怪的是,在我向 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]);
定位问题
我找到了问题,它是由使用的batchDraw react-konva
引起的:
改变这条线后,我现在可以得到 60fps 的移动。
- drawingNode && drawingNode.batchDraw();
+ drawingNode && drawingNode.draw();
根据他们的文档,batchDraw 会绘制the next animationFrame
。但react
它本身也用于RAF
触发下一次道具更新,所以batchDraw
这里发生2 frames
在 i 之后setHeroPos()
。
解决方案:
我要向他们的项目提交一个拉取请求。
解决方案
开发工具可以在设备上增加很多额外的负载。当您记录性能日志时更是如此。
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,000th)
注意此值的精度
performance.now
已被故意降低以保护用户,并且根据浏览器的不同在 100 毫秒 - 200 毫秒之间(可以在标志和系统配置后面访问 1 毫秒))
有意义的表现
JS 执行是不确定的,这使得单个计时测量完全不可靠。(为什么它比使用更好的performance.now
原因peformance.mark
)
为了克服 JS 执行的不确定性和计时器的不准确性,请使用运行平均值来计时您的代码。下面的示例显示了如何执行此操作。
与其显示时间,不如使用与应用程序需求相关的指标。例如,执行代码花费了多少帧。(见示例)
例子
此示例使用requestAnimationFrame
.
滑块允许您选择函数应该花费渲染的大约时间。
顶部的信息文本将计时结果显示为运行平均值。
您会注意到理想化帧负载 (IFL) 在帧速率下降之前远低于 100%。
实验
- 开发工具和性能监控如何影响性能。
当帧速率低于 60 时,将滑块移动到正下方。
打开开发工具,看看它是否以及如何影响明显的性能。记下任何变化。是否有影响,如果有,影响多少?
记录性能日志并查看 FPS 和/或 IFL 是否受记录影响
- 在影响帧速率之前,您的设备可以分配给渲染的最长时间是多少。
将滑块缓慢向右移动。
当帧速率低于 60 时,将幻灯片向后移动一步,直到它再次读取 60FPS。
值IFL将给出完美帧(第 60 秒)执行代码的百分比。Time绝对执行时间,以毫秒为单位。
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" width="350"></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>
注意时间的显示会影响结果。此代码在沙盒代码段中运行的事实将影响结果。为了获得最准确的结果,请在独立页面上运行代码。将结果记录到 JS 数据结构中,并在测试运行后显示结果。
负载:请求的 CPU/GPU 执行负载在 1/1000 秒内。
FPS:运行平均每秒帧数。
IFL:理想化帧负载,第 60 秒执行代码的百分比。
时间:平均测量的执行时间,以 1/1000 秒为单位。
推荐阅读
- javascript - Angular IE11 文件上传取消事件
- css - 如何在没有/deep/的情况下覆盖`ng-template`构建字段的css样式?
- javascript - JavaScript中的地理位置不返回位置
- ftp - 对 ALLO 的否定响应的 lftp 处理
- java - 如何使用 Java SQL API 重命名 Azure Cosmos DB 中的容器?
- batch-file - 如何批量输入密码?
- android - Dart FFI - 从 C++ 调用 Dart 函数
- database - 如何为来自 ORM 系统(用于 PHP)的字段构建带有翻译的表单?
- r - 展开表格中的变量标签
- c# - 如何在 WebBrowser 中搜索页面上的文本?(WPF)