我怎么能处理巨大的javascript数组(数组的20万个坐标)在浏览器端有效

How can i process huge javascript array (array of 20 mil coordinates) in browser side efficiently?

本文关键字:数组 坐标 有效 浏览器 处理 怎么能 巨大 javascript 20万      更新时间:2023-09-26

下面是我的代码:

<!DOCTYPE html>
<html>
<head>
    <title>d3 Practice</title>
</head>
<body>
<script src="./vislibs/d3.v3.min.js"></script>
<canvas id="test" width="1024" height="768" style="border: 1px solid black;"></canvas>
<script>
function generate_data(size){
    var randomX = d3.random.normal(width/2 , 80),
        randomY = d3.random.normal(height/2, 80);
    var data = d3.range(size).map(function() {
      return [
        randomX(),
        randomY()
      ];
    });
    return data
}
function main() {
    var canvasEl = d3.select('#test').node();
    // get 2d context to draw on
    var ctx = canvasEl.getContext('2d');
        width = canvasEl.width
    height = canvasEl.height
    data=generate_data(20000000)
    alert("data generated")
    // set fill color of context
    var x = 0
    ctx.fillStyle = 'red';
    batch_size = 10000
    debugger // Cannot step into requestAnimationFrame(draw_loop) at all , freezing eternity
    draw_loop = function () {
        if (x<=data.length-1) {
            for (i in d3.range(0,batch_size)){
                    //console.log(x)
                    ctx.fillRect(data[x][0], data[x][1], 2, 2);
                    x = x+1
            }
            setTimeout(draw_loop,100);
        }
    }
    requestAnimationFrame(draw_loop)
    //alert("done reqanim")
}
main()
//init()
</script>
</body>
</html>

这将在generate_data(20000000)生成后冻结浏览器,甚至不去requestAnimationFrame(draw_loop)。我在没有requestAnimationFramesetTimeout的情况下进行了测试,并立即渲染了所有内容,但效果很好,但它冻结了浏览器。卸载到服务器似乎是一个很好的解决方案,但我想知道为什么暂停(setTimeout和requestAnimiationFrame)导致浏览器冻结无限,而不是给予控制的浏览器。

在linux上测试,Chromium Version 26.0.1410.43(189671)。

浏览器的内存使用量约为1.4 GB,只是generate_data(20000000)完成后脚本打开的一个选项卡,但我的笔记本电脑上有6 GB的内存。那么有什么有效的方法来处理它吗?(不会造成无反应或无限冻结)

编辑:

这里是工作JSFiddle2 mil矩形

这会冻结你的浏览器。20mil矩形

如果没有setTimeout或requestAnimationFrame,这将导致1-2无响应,但可以很好地渲染到最后而不会冻结。不带暂停函数的20mil矩形

概念证明,没有渲染代码,数据生成后你会看到一个警告框,但是浏览器一旦点击requestAnimationFrame就会冻结。(20毫分)会冻结浏览器,

function generate_data(size){
    var randomX = d3.random.normal(width/2 , 80),
        randomY = d3.random.normal(height/2, 80);
    var data = d3.range(size).map(function() {
      return [
        randomX(),
        randomY()
      ];
    });
    return data
}
function main() {
    var canvasEl = d3.select('#test').node();
    // get 2d context to draw on (the "bitmap" mentioned earlier)
    var ctx = canvasEl.getContext('2d');
        width = canvasEl.width
    height = canvasEl.height
    data=generate_data(20000000)
    alert("data generated")
    // set fill color of context
    var x = 0
    ctx.fillStyle = 'red';
    batch_size = 10000
    //debugger // Cannot step into requestAnimationFrame(draw_loop) at all , freezing eternity
    draw_loop = function () {
        if (x<=data.length-1) {
            nada=""
            x=x+batch_size
            setTimeout(draw_loop,100);
        }
    }
    requestAnimationFrame(draw_loop)
    alert("done reqanim")
}
main()

2 mil点版本,没有渲染代码,可以工作

Javascript是一个单线程系统。像setTimeout这样创建回调的函数仍然会杀死单线程系统。

编辑:

根据评论,有一些创造性的方法可以让单线程Javascript产生异步执行大型逻辑函数的错觉,从而释放执行点击处理程序等事情的进程。

@V3ss0n根据代码中进程冻结发生的位置,您有以下几个选项:

1)进程在创建数组时冻结:将20万项加载到数组中可能需要一段时间,我建议不要这样做。有不止一种方法可以解决这个问题,但我会尽可能多地将沉重的循环逻辑移出javascript,并可能移到web服务中。这有它自己的挑战,比如试图发送2000万件物品给客户,这是一个坏主意。但是,如果您可以将其分成可管理的坐标组(最多20-100),然后根据需要进行多个web服务调用(使用类似JQuery Ajax方法http://api.jquery.com/jQuery.ajax/的东西),那么您的javascript所要担心的就是附加项目。这将在一段时间内向您传输数据,因此不要期望它是即时的,但如果它在20毫秒内循环遍历坐标创建函数时遇到困难,则不应冻结浏览器。

2)在渲染每个单独的点时进程冻结:解决这个问题的方法更具创造性,如果您不想使用web服务,也可以将其用于上述过程。首先看看下面的代码:

    var renderingCounter = 0;
var asyncTimer = null; // keep the render timer global to prevent duplicate renders from being run
function StartRender(){
    // test if existing async render is running
    if(asyncTimer != null){
        // existing async timer, clear the timer and clear the canvas
        clearInterval(asyncTimer);
        renderingCounter = 0;
        // code for clearing the canvas ..
    };
    // create interval object
    var asyncTimer = setInterval(function (){
        // at the begining of each start render, test if all points have been rendered
        if(renderingCounter >= pointArray.length){
            // kill the asynctimer
            clearInterval(asyncTimer);
            asyncTimer = null;
            renderingCounter = 0; // reset the counter
            return;
        };
        while(renderingCounter < 100){ // only draw 100 items
            if(renderingCounter >= pointArray.length){ // make sure you are not out of array bounds
                return;
            };
            // execute display code for that point
        }; // loop
    }, 40);
}

上面的代码将渲染过程分成100个点组。在一个组完成后,它暂停40毫秒,这应该在主线程上释放足够的时间,以便继续执行其他项目(例如UI)。全局变量renderingCounter将保持数组的当前进度。如果函数在渲染过程中被重新调用,代码还会检查当前正在运行的渲染计时器(在这个例子中,它会终止当前的渲染并重置它)。如果呈现计时器仍然导致挂起,您可以增加暂停间隔。

这两种接近异步执行的方法应该提供足够的灵活性,允许从服务器端web服务呈现或读取数据(如果你曾经这样做过),同时仍然保持流畅的ui

您可以通过预先计算图像数据数组来加速代码,而不是尝试创建线程和人为中断。到目前为止,您的代码有两个缺陷:

  1. 您单独绘制每个像素
  2. 重新绘制已经存在的像素

由于数据点的绝对数量,这两个缺陷可以很快加起来。当你运行你的原始小提琴时,你会看到,仅仅一秒钟后,图片不再改变,但你仍然在同一区域上绘制像素,可能再过20秒。下面是putImageData()函数的一个示例:

function createPictureArray() {
    var res, i, max_i;
    var canvasEl = d3.select('#test').node();
    var ctx = canvasEl.getContext('2d');
    var data;
    res = ctx.getImageData(0, 0, width, height);
    data = res.data;
    for (i = 0, max_i = data.length; i < max_i; i++) {
        data[i] = 255;
    }
    return res;
}
function generateData(size) {
    var i, x, y, s, res, data, randomX, randomY;
    randomX = d3.random.normal(width/2 , 80);
    randomY = d3.random.normal(height/2, 80);    
    res = createPictureArray();
    data = res.data;
    for (i = 0; i < size; i++) {
        x = parseInt(randomX());
        y = parseInt(randomY());
        s = 4 * y * width + 4 * x;
        if (data.length > s && data[s + 1] !== 0) {
            data[s + 1] = 0;
            data[s + 2] = 0;
        }
    }
    return res;
}
function main() {
    var canvasEl = d3.select('#test').node();
    var ctx = canvasEl.getContext('2d');
    width = canvasEl.width;
    height = canvasEl.height;
    data = generateData(2000000);
    ctx.putImageData(data, 0, 0);
}
main()

小提琴

这个例子创建了200万个数据点,但可以随意增加到2000万。在我的机器上,大约花了7秒。所以还有优化的空间,但至少我没有遇到严重的冻结。

所发生的是,你创建你的随机数一个接一个,并为每个数字对你计算正确的像素内的图像数据数组,你设置像素为红色,如果它还没有被设置为红色。

JavaScript和UI更新(如重绘和回流)都在单个"浏览器UI线程"中运行。浏览器UI线程在排队系统上工作,其中任务一直保持到进程空闲为止。一旦空闲,将检索队列中的下一个任务并执行。

setTimeout()和requestAnimationFrame等技术可以延迟任务的执行,并允许UI更新一些喘息空间,但是当这些任务最终运行时,它仍然是一次一个,而不是并发的。因此,任何这样的任务总是会阻塞任何其他任务的执行。

HTML5引入了web worker,它允许你将Javascript传递给一个单独的线程,在那里它与UI线程并发执行。你在web worker中执行的Javascript与UI线程是隔离的,你不能从web worker中对UI进行任何更新。然而,对于generate_data()函数这样的东西,web worker将是理想的。

这是一个很棒的web worker教程,这是一个浏览器兼容性矩阵。