我有一个高度优化的JavaScript应用程序,一个高度交互的图形编辑器.我现在开始使用大量数据(图中数千种形状)对其进行分析(使用Chrome开发工具),并且我遇到了以前不寻常的性能瓶颈,即Hit Test.
| Self Time | Total Time | Activity | |-----------------|-----------------|---------------------| | 3579 ms (67.5%) | 3579 ms (67.5%) | Rendering | | 3455 ms (65.2%) | 3455 ms (65.2%) | Hit Test | <- this one | 78 ms (1.5%) | 78 ms (1.5%) | Update Layer Tree | | 40 ms (0.8%) | 40 ms (0.8%) | Recalculate Style | | 1343 ms (25.3%) | 1343 ms (25.3%) | Scripting | | 378 ms (7.1%) | 378 ms (7.1%) | Painting |
这占了所有内容的65%(!),仍然是我的代码库中的一个怪物瓶颈.我知道这是在指针下跟踪对象的过程,我对如何优化它有无用的想法(使用更少的元素,使用更少的鼠标事件等).
上下文:上面的性能配置文件在我的应用程序中显示"屏幕平移"功能,其中可以通过拖动空白区域来移动屏幕内容.这导致大量对象被移动,通过移动其容器而不是单独移动每个对象进行优化.我做了一个演示.
在进入这个之前,我想寻找优化命中测试的一般原则(那些好的'' 没有sh*t,Sherlock'博客文章),以及是否存在任何提高性能的技巧(例如使用translate3d
,以使GPU处理).
我尝试过像js optimize hit test这样的查询,但结果充满了图形编程文章和手动实现示例 - 就好像JS社区之前甚至没有听说过这个东西!即使是chrome devtools指南也缺乏此区域.
编辑:有这个问题,但它没有多大帮助:什么是Chrome开发工具"命中测试"时间表条目?
所以在这里,我自豪地完成了我的研究,询问:如何在JavaScript中优化本机命中测试?
我准备了一个演示性能瓶颈的演示,虽然它与我的实际应用程序并不完全相同,但数字显然会因设备而异.要看到瓶颈:
转到Chrome上的时间轴标签(或相当于您的浏览器)
开始录制,然后像疯子一样在演示中平移
停止录制并检查结果
回顾一下我在这方面已经做过的所有重要优化:
在屏幕上移动单个容器而不是单独移动数千个元素
使用transform: translate3d
移动容器
v-同步鼠标移动到屏幕刷新率
删除所有可能不必要的"包装器"和"修复器"元素
pointer-events: none
在形状上使用- 没有效果
补充说明:
瓶颈都存在有和没有 GPU加速
测试只在最新的Chrome中完成
DOM是使用ReactJS呈现的,但是没有它可以观察到相同的问题,如链接演示中所示
Manuel Otto.. 10
有趣,pointer-events: none
没有效果.但是如果你考虑它,它是有道理的,因为具有该标志集的元素仍然模糊了其他元素的指针事件,所以无论如何必须发生hittest.
您可以做的是对重要内容进行叠加并响应该叠加层上的鼠标事件,让您的代码决定如何处理它.
这是有效的,因为一旦最热门的算法找到命中,并且我假设它向下执行z-index,它就会停止.
// ================================================
// Increase or decrease this value for testing:
var NUMBER_OF_OBJECTS = 40000;
// Wether to use the overlay or the container directly
var USE_OVERLAY = true;
// ================================================
var overlay = document.getElementById("overlay");
var container = document.getElementById("container");
var contents = document.getElementById("contents");
for (var i = 0; i < NUMBER_OF_OBJECTS; i++) {
var node = document.createElement("div");
node.innerHtml = i;
node.className = "node";
node.style.top = Math.abs(Math.random() * 2000) + "px";
node.style.left = Math.abs(Math.random() * 2000) + "px";
contents.appendChild(node);
}
var posX = 100;
var posY = 100;
var previousX = null;
var previousY = null;
var mousedownHandler = function (e) {
window.onmousemove = globalMousemoveHandler;
window.onmouseup = globalMouseupHandler;
previousX = e.clientX;
previousY = e.clientY;
}
var globalMousemoveHandler = function (e) {
posX += e.clientX - previousX;
posY += e.clientY - previousY;
previousX = e.clientX;
previousY = e.clientY;
contents.style.transform = "translate3d(" + posX + "px, " + posY + "px, 0)";
}
var globalMouseupHandler = function (e) {
window.onmousemove = null;
window.onmouseup = null;
previousX = null;
previousY = null;
}
if(USE_OVERLAY){
overlay.onmousedown = mousedownHandler;
}else{
overlay.style.display = 'none';
container.onmousedown = mousedownHandler;
}
contents.style.transform = "translate3d(" + posX + "px, " + posY + "px, 0)";
#overlay{
position: absolute;
top: 0;
left: 0;
height: 400px;
width: 800px;
opacity: 0;
z-index: 100;
cursor: -webkit-grab;
cursor: -moz-grab;
cursor: grab;
-moz-user-select: none;
-ms-user-select: none;
-webkit-user-select: none;
user-select: none;
}
#container {
height: 400px;
width: 800px;
background-color: #ccc;
overflow: hidden;
}
#container:active {
cursor: move;
cursor: -webkit-grabbing;
cursor: -moz-grabbing;
cursor: grabbing;
}
.node {
position: absolute;
height: 20px;
width: 20px;
background-color: red;
border-radius: 10px;
pointer-events: none;
}
// ================================================
// Increase or decrease this value for testing:
var NUMBER_OF_OBJECTS = 40000;
// Wether to use the overlay or the container directly
var USE_OVERLAY = false;
// ================================================
var overlay = document.getElementById("overlay");
var container = document.getElementById("container");
var contents = document.getElementById("contents");
for (var i = 0; i < NUMBER_OF_OBJECTS; i++) {
var node = document.createElement("div");
node.innerHtml = i;
node.className = "node";
node.style.top = Math.abs(Math.random() * 2000) + "px";
node.style.left = Math.abs(Math.random() * 2000) + "px";
contents.appendChild(node);
}
var posX = 100;
var posY = 100;
var previousX = null;
var previousY = null;
var mousedownHandler = function (e) {
window.onmousemove = globalMousemoveHandler;
window.onmouseup = globalMouseupHandler;
previousX = e.clientX;
previousY = e.clientY;
}
var globalMousemoveHandler = function (e) {
posX += e.clientX - previousX;
posY += e.clientY - previousY;
previousX = e.clientX;
previousY = e.clientY;
contents.style.transform = "translate3d(" + posX + "px, " + posY + "px, 0)";
}
var globalMouseupHandler = function (e) {
window.onmousemove = null;
window.onmouseup = null;
previousX = null;
previousY = null;
}
if(USE_OVERLAY){
overlay.onmousedown = mousedownHandler;
}else{
overlay.style.display = 'none';
container.onmousedown = mousedownHandler;
}
contents.style.transform = "translate3d(" + posX + "px, " + posY + "px, 0)";
#overlay{
position: absolute;
top: 0;
left: 0;
height: 400px;
width: 800px;
opacity: 0;
z-index: 100;
cursor: -webkit-grab;
cursor: -moz-grab;
cursor: grab;
-moz-user-select: none;
-ms-user-select: none;
-webkit-user-select: none;
user-select: none;
}
#container {
height: 400px;
width: 800px;
background-color: #ccc;
overflow: hidden;
}
#container:active {
cursor: move;
cursor: -webkit-grabbing;
cursor: -moz-grabbing;
cursor: grabbing;
}
.node {
position: absolute;
height: 20px;
width: 20px;
background-color: red;
border-radius: 10px;
pointer-events: none;
}
有趣,pointer-events: none
没有效果.但是如果你考虑它,它是有道理的,因为具有该标志集的元素仍然模糊了其他元素的指针事件,所以无论如何必须发生hittest.
您可以做的是对重要内容进行叠加并响应该叠加层上的鼠标事件,让您的代码决定如何处理它.
这是有效的,因为一旦最热门的算法找到命中,并且我假设它向下执行z-index,它就会停止.
// ================================================
// Increase or decrease this value for testing:
var NUMBER_OF_OBJECTS = 40000;
// Wether to use the overlay or the container directly
var USE_OVERLAY = true;
// ================================================
var overlay = document.getElementById("overlay");
var container = document.getElementById("container");
var contents = document.getElementById("contents");
for (var i = 0; i < NUMBER_OF_OBJECTS; i++) {
var node = document.createElement("div");
node.innerHtml = i;
node.className = "node";
node.style.top = Math.abs(Math.random() * 2000) + "px";
node.style.left = Math.abs(Math.random() * 2000) + "px";
contents.appendChild(node);
}
var posX = 100;
var posY = 100;
var previousX = null;
var previousY = null;
var mousedownHandler = function (e) {
window.onmousemove = globalMousemoveHandler;
window.onmouseup = globalMouseupHandler;
previousX = e.clientX;
previousY = e.clientY;
}
var globalMousemoveHandler = function (e) {
posX += e.clientX - previousX;
posY += e.clientY - previousY;
previousX = e.clientX;
previousY = e.clientY;
contents.style.transform = "translate3d(" + posX + "px, " + posY + "px, 0)";
}
var globalMouseupHandler = function (e) {
window.onmousemove = null;
window.onmouseup = null;
previousX = null;
previousY = null;
}
if(USE_OVERLAY){
overlay.onmousedown = mousedownHandler;
}else{
overlay.style.display = 'none';
container.onmousedown = mousedownHandler;
}
contents.style.transform = "translate3d(" + posX + "px, " + posY + "px, 0)";
#overlay{
position: absolute;
top: 0;
left: 0;
height: 400px;
width: 800px;
opacity: 0;
z-index: 100;
cursor: -webkit-grab;
cursor: -moz-grab;
cursor: grab;
-moz-user-select: none;
-ms-user-select: none;
-webkit-user-select: none;
user-select: none;
}
#container {
height: 400px;
width: 800px;
background-color: #ccc;
overflow: hidden;
}
#container:active {
cursor: move;
cursor: -webkit-grabbing;
cursor: -moz-grabbing;
cursor: grabbing;
}
.node {
position: absolute;
height: 20px;
width: 20px;
background-color: red;
border-radius: 10px;
pointer-events: none;
}
其中一个问题是你要移动容器内的每一个元素,如果你有GPU加速没关系,瓶颈就是重新计算它们的新位置,即处理器字段.
我的建议是分割容器,因此你可以单独移动各种窗格,减少负荷,这称为宽相计算,即只移动需要移动的东西.如果屏幕上有东西,你为什么要移动它?
从制作而不是一个16个容器开始,你将不得不在这里做一些数学运算来找出哪些窗格正在显示.然后,当鼠标事件发生时,仅移动那些窗格并将未显示的窗格保留在原来的位置.这应该大大减少用于移动它们的时间.
+------+------+------+------+ | SS|SS | | | | SS|SS | | | +------+------+------+------+ | | | | | | | | | | +------+------+------+------+ | | | | | | | | | | +------+------+------+------+ | | | | | | | | | | +------+------+------+------+
在这个例子中,我们有16个窗格,其中2个正在显示(由S标记为屏幕).当用户平移时,检查"屏幕"的边界框,找出哪些窗格属于"屏幕",只移动那些窗格.这在理论上是可无限扩展的.
不幸的是,我没有时间编写显示思想的代码,但我希望这会对你有所帮助.
干杯!