1

这是我的代码:

<!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). 我在没有requestAnimationFrameor的情况下进行了测试,setTimeout并且一次渲染了所有内容,但工作正常,但它使浏览器冻结了一点。卸载到服务器似乎是一个很好的解决方案,但我想知道为什么暂停(setTimeout 和 requestAnimiationFrame)会导致浏览器无限期冻结而不是控制浏览器。

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

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

编辑:

这是工作JSFiddle 2 mil矩形

这将冻结您的浏览器。 2000 万个矩形

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

概念证明,无渲染代码,数据生成后,你会看到数据生成后的警告框,但浏览器一点击 requestAnimationFrame 就被冻结。(2000 万点)将冻结浏览器

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 百万点版本,没有渲染代码,可以工作

4

3 回答 3

3

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

编辑:

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

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

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

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 服务渲染或读取数据(你应该朝那个方向发展),同时仍然保持流畅的用户界面

于 2013-06-05T16:25:34.623 回答
2

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

  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 秒。所以还有优化的空间,但至少我没有经历过严重的冻结。

发生的情况是,您一个接一个地创建随机数,并且对于每个数字对,您计算图像数据数组中的正确像素,如果该像素尚未设置为红色,则将该像素设置为红色。

于 2013-06-05T18:10:39.053 回答
1

JavaScript 和 UI 更新(例如重绘和重排)都在单个“浏览器 UI 线程”中运行。浏览器 UI 线程在队列系统上工作,在该系统中任务一直保持到进程空闲。一旦空闲,就会检索并执行队列中的下一个任务。

像 setTimeout() 和 requestAnimationFrame 这样的技术会延迟你的任务的执行,并为 UI 更新留出一些喘息的空间,但是当这些任务最终运行时,它仍然是一次一个,而不是同时运行。因此,任何此类任务将始终阻止任何其他任务的执行。

HTML5 引入了web workers,它允许你将 Javascript 传递给一个单独的线程,在那里它与 UI 线程同时执行。您在 Web Worker 中执行的 Javascript 与 UI 线程隔离,您无法从 Web Worker 对 UI 进行任何更新。然而,网络工作者将是您的 generate_data() 函数之类的理想选择。

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

于 2013-06-05T17:27:53.347 回答