13

我必须处理大量图像。首先,我需要检查图像的大小是否大于 50x60,并适当增加坏图像的计数器。

n.width我遇到的问题是 Internet Explorer 8 上/的速度n.height极低。我检查了n.offsetWidthn.clientWidth但它们在速度方面都是一样的。但我不能使用n.style.width,因为这个值并不总是设置在<img />我感兴趣的标签上。

考虑以下代码:

Javascript

var Test = {
    processImages: function () {
        var fS = new Date().getTime();

        var minimagew = 50,
            minimageh = 60;
        var imgs = document.getElementsByTagName('img');
        var len = imgs.length,
            isBad = 0,
            i = len;

        while (i--) {
            var n = imgs[i];

            var imgW = n.width;
            var imgH = n.height;

            if (imgW < minimagew || imgH < minimageh) {
                isBad++;
            }
        }

        var fE = new Date().getTime();
        var fD = (fE - fS);

        console.info('Processed ' + imgs.length + ' images in ' 
                     + fD + 'ms.  ' + isBad + ' were marked as bad.');
    }
};

HTML

<img src="http://nsidc.org/images/logo_nasa_42x35.gif" />
   [snip 9998 images]
<img src="http://nsidc.org/images/logo_nasa_42x35.gif" />

代码生成以下解析 10k 图像的输出(3 个不同的 Ctrl+F5s)

  • FF:在 115 毫秒内处理了 10000 张图像。10000 被标记为坏。
  • FF:在 99 毫秒内处理了 10000 张图像。10000 被标记为坏。
  • FF:在 87 毫秒内处理了 10000 张图像。10000 被标记为坏。
  • IE8:在 206 毫秒内处理了 10000 张图像。10000 被标记为坏。
  • IE8:在 204 毫秒内处理了 10000 张图像。10000 被标记为坏。
  • IE8:在 208 毫秒内处理了 10000 张图像。10000 被标记为坏。

如您所见,FF 3.6 中的代码比 IE8 中执行的代码快两倍。

为了证明问题确实与浏览器维度属性的速度有关,如果我更改:n.widthn.height常量,那么我们将拥有:

 var imgW = 43;
 var imgH = 29;

我得到以下结果:

  • FF:在 38 毫秒内处理了 10000 张图像。10000 被标记为坏。
  • FF:在 34 毫秒内处理了 10000 张图像。10000 被标记为坏。
  • FF:在 33 毫秒内处理了 10000 张图像。10000 被标记为坏。
  • IE8:在 18 毫秒内处理了 10000 张图像。10000 被标记为坏。
  • IE8:在 22 毫秒内处理了 10000 张图像。10000 被标记为坏。
  • IE8:在 17 毫秒内处理了 10000 张图像。10000 被标记为坏。

这是正确的!当我们跳过<img />维度检查(调用node.width/ node.clientWidthetc)时,IE8 实际上比 Firefox 执行得更好。

你有什么想法为什么 IE 需要这么长时间来检查图像的大小以及最终如何提高这种检查的性能?

4

6 回答 6

8

好吧,您的代码非常基本。您唯一可以优化的是如何检查尺寸:

if (n.width < minimagew || n.height < minimageh) {
  isBad++;
}

这样,如果width图像的 错误,height将无法访问。对于带有 bad 的图像,它将使您的代码速度提高 1.5-2 倍width

但我的猜测是,您实际上并不需要 10 000 张图片作为您网站的一部分。在这种情况下,您可以检查Image对象而不是<img>元素。

loop {
  var img = new Image();
  img.src = "http://nsidc.org/images/logo_nasa_42x35.gif";
}

这将使您的代码在 IE 8 中快 2 倍,在 FF 中快 10 倍。

进行这些更改对我的计算机演示进行了以下改进:

FF: 200 ms ->  7 ms
IE:  80 ms -> 20 ms
于 2010-10-17T11:52:40.763 回答
5

换句话说,你问为什么浏览器 A 比浏览器 B 花费更长的时间来做完全相同的事情。

好吧,他们不做同样的事情。您在脚本中写下您想要发生的事情,您选择的浏览器会尽力做到这一点,但您几乎无法控制它是如何实现的。

浏览器是由不同的团队编写的,他们对如何做事有着不同的理念。您的 20-something-line-script 可能需要浏览器 A 中的 10000 个 CPU 周期和浏览器 B 中的 50000 个 CPU 周期,具体取决于浏览器代码的编写方式和浏览器的内部工作原理。

要详细回答为什么在这种情况下 IE8 比 FF 慢,我们必须深入了解到底发生了什么。由于 IE8 的源代码不是公开可用的,我们无法查看那里,我不知道是否有足够详细的文档来说明正在发生的事情。

相反,我给你一个例子,说明两种不同的做事哲学如何极大地影响产生相同最终结果的时间。注意:这是一个示例,与现实世界的任何相似之处纯属巧合。

该怎么办:

  • 获取图像的尺寸。

A组:

  1. 从指定源加载文件
  2. 将图像解码到内存中
  3. 返回图像的宽度和高度

这没什么不好,不是吗?它确实返回图像的宽度和高度。团队可以做得更好吗?

B组:

  1. 将文件的前 1024 字节加载到内存中
  2. 检测图像格式
    • 是JPEG吗?获取标题FFC0,保存宽度和高度
    • 是png吗?定位页眉,保存宽度和高度
    • 是gif吗?定位页眉,保存宽度和高度
  3. 返回图像的宽度和高度

他们的代码还返回图像的宽度和高度,但他们以不同的方式执行此操作,比 A 团队编写的代码快几倍:仅将文件的开头加载到内存中,并取尺寸从标题中获取而不解码图像。它节省了带宽、内存和时间。

那么,哪一个是最好的呢?团队 A 或团队 B 的代码?当两个团队都针对 100 000 张图像运行他们的代码时,请想一想……这可能需要一点时间……哦,B 组已经完成了!他们说有 10% 的图像小于 50x60 像素,并且他们无法打开其中的 3 个。那么A队呢?看来我们得等一会儿了……也许是一杯咖啡?

[10分钟后]

我猜你认为 B 组写的代码最好,我是对的还是对的?

A 组表示 8% 的图像小于 50x60 像素。奇怪,B队不是这么说的。A 队还表示,他们无法获得 20% 的图像尺寸,因为这些文件已损坏。那是B队没有说的……

那么你认为哪个代码最好?

我为语言错误道歉,英语不是我的母语。

于 2010-10-23T01:26:47.057 回答
4

好吧,这很可能不是您要找的东西,但是我会发布它以防万一它对其他人有所帮助。由于无法提高浏览器基本功能的速度,因此您可以防止循环在执行时冻结浏览器。您可以通过在块中执行循环并使用时间为 0 的 setTimeout 启动下一个块来执行此操作。这基本上允许浏览器在调用下一个块之前重新绘制并执行其他操作。这是脚本的修改版本:

var Test = {
    processImages: function() {
        var fS = new Date().getTime();

        var minimagew = 50,
            minimageh = 60,
            stepSize = 1000;
        var imgs = document.getElementsByTagName('img');
        var len = imgs.length,
            isBad = 0,
            i = len,
            stopAt = len;

        var doStep = function() {
            stopAt -= stepSize;
            while (i >= stopAt && i--) {
                var n = imgs[i];

                var imgW = n.width;
                var imgH = n.height;

                if (imgW < minimagew || imgH < minimageh) {
                    isBad++;
                }
            }

            if (i > 0)
                setTimeout(doStep, 0);
            else {
                var fE = new Date().getTime();
                var fD = (fE - fS);

                console.info('Processed ' + imgs.length + ' images in '
                     + fD + 'ms.  ' + isBad + ' were marked as bad.');
            }
        }
        doStep();
    }
};

当然,这会使总执行时间更长,但也许您可以使用它,以便您的页面在工作时可用。

于 2010-10-19T21:10:22.763 回答
1

我对您的问题非常感兴趣,但不幸的是,我在优化您的代码方面并没有真正取得任何进展。我能够在 IE 执行上减少大约 30 到 40 毫秒(这显然取决于你的物理机器的能力)。但我几乎尝试了一切

我尝试过的东西而不是 [element].width。

  • [element].getBoundingClientRect()- 基本上这将高度和宽度合二为一
  • document.elementFromPoint(x, y)- 我通过使用offsetLeft + 50andoffsetTop + 60我可以确定该点的元素是否与当前元素不同,这意味着它是一个“坏”图像。

最后,这就是我想出的将时间缩短一点(30 到 40 毫秒)的方法。

注意:我在 IE 8 中获得的最佳时间是 171ms

已编辑- 我修改了下面的代码以包含您的方式、我的方式和使用 Jquery。测试一下。

<html>
<script src="http://code.jquery.com/jquery-1.4.2.js" type="text/javascript"></script>
<script type="text/javascript">
var TestYourWay = {
    processImages: function () {
        var fS = new Date().getTime();

        var minimagew = 50,
            minimageh = 60;
        var imgs = document.getElementsByTagName('img');
        var len = imgs.length,
            isBad = 0,
            i = len;

        while (i--) {
            var n = imgs[i];

            var imgW = n.width;
            var imgH = n.height;

            if (imgW < minimagew || imgH < minimageh) {
                isBad++;
            }
        }

        var fE = new Date().getTime();
        var fD = (fE - fS);

        alert('Processed ' + imgs.length + ' images in '
                     + fD + 'ms.  ' + isBad + ' were marked as bad.');
    }
};


var TestMyWay = {
    processImages: function () {
        var fS = new Date(),
        imgs = document.getElementsByTagName('img'),
        isBad = 0;
        for (var i = 0, img; img = imgs[i]; i++) {
            if (img.width  < 50 || img.height < 60) {
                isBad++;
            }
        }
        var fD = new Date() - fS;
        alert('Processed ' + i + ' images in ' + fD + 'ms.  ' + isBad + ' were marked as bad.');
    }
};

var TestJquery = {
    processImages: function () {
        var fS = new Date(),
        imgs = $('img'),
        isBad = 0;
        imgs.each(function () {

           if (this.width  < 50 || this.height < 60) {
                isBad++;
            }
        });
        var fD = new Date() - fS;
        alert('Processed ' + imgs.length + ' images in ' + fD + 'ms.  ' + isBad + ' were marked as bad.');
    }
};

</script>
<body>
     <button onclick="javascript:TestYourWay.processImages();" id="yourWay">Your Way</button>
     <button onclick="javascript:TestMyWay.processImages();" id="myWay">My Way</button>
     <button onclick="javascript:TestJquery.processImages();" id="myWay">jQuery Way</button>
     <img src="http://nsidc.org/images/logo_nasa_42x35.gif" />
     <!--Copy This image tag 10000 times -->
</body>
</html>

其他注意事项:

IE 8 中的 JavaScript 引擎不如 FireFox 3.6+、Safari 或 Chrome 快。Opera 对其脚本引擎进行了改进,但仍不如 FF、Safari 或 Chrome 快。然而,Opera 在某些方面确实优于 IE 8,但在其他方面表现迟缓。另外值得注意的是 IE 9 将于今年年底或明年初推出,他们对 JavaScript 引擎进行了改进。你可以在这里看到我所说的一些统计数据。

https://spreadsheets.google.com/pub?key=0AuWerG7Xqt-8dHBuU2pGMncwTENNNGlvNzFtaE5uX0E&hl=en&output=html

于 2010-10-18T20:44:44.837 回答
0

我没有在 IE 上测试过,但是你可以做的是从循环中删除变量声明,即使它们不是那么昂贵(CPU 方面),当它们从循环中删除时它们可以节省几个 CPU 周期循环,您也可以继续跳过imgWimgH分配并直接访问对象属性,因为这可以节省一个对象取消引用,因为不必为图像执行 if 语句中的逻辑检查的另一部分宽度错误;

var Test = {
    processImages: function () {
        var fS = new Date().getTime();

        var minimagew = 50,
            minimageh = 60;
        var imgs = document.getElementsByTagName('img');
        var len = imgs.length,
            isBad = 0,
            i = len;

        /* this is the only part updated */
        while (i--) {
            if (imgs[i].width < minimagew || imgs[i].height < minimageh) {
                isBad++;
            }
        }
        /* ... 'till here */

        var fE = new Date().getTime();
        var fD = (fE - fS);

        console.info('Updated:  Processed ' + imgs.length + ' images in ' 
                     + fD + 'ms.  ' + isBad + ' were marked as bad.');
    }
    ,processImagesOriginal: function () { // for comparisson
        var fS = new Date().getTime();

        var minimagew = 50,
            minimageh = 60;
        var imgs = document.getElementsByTagName('img');
        var len = imgs.length,
            isBad = 0,
            i = len;

        while (i--) {
            var n = imgs[i];

            var imgW = n.width;
            var imgH = n.height;

            if (imgW < minimagew || imgH < minimageh) {
                isBad++;
            }
        }

        var fE = new Date().getTime();
        var fD = (fE - fS);

        console.info('Original: Processed ' + imgs.length + ' images in ' 
                     + fD + 'ms.  ' + isBad + ' were marked as bad.');
    }
};

//Original: Processed 10000 images in ~38ms. 10000 were marked as bad.
//Updated:  Processed 10000 images in ~23ms. 10000 were marked as bad.
于 2010-10-21T23:59:36.200 回答
0

即使您在 IE 中进行只读访问,访问屏幕上(或 document.body 下)元素的布局属性也可能会调用布局开销(锁定、重排等)。

我没有测试,但你可以尝试这样的事情。

    var done=i;
    while (i--) { 
        var img=new Image();
        img.onload=function(){
           if (this.width < minimagew || this.height < minimageh) { 
              isBad++;
           } 
           if(done--==0){ onComplete(); }
        };
        img.onerror=function(){ done--; }
        img.src=imgs[i].src;
    }

不要使用长循环!这使 GUI 变慢

编辑: document.getElementsByTagName 比您预期的要慢。它不返回静态数组,而是返回反映更改(如果有)的动态对象。您可以尝试复制数组中的元素,这可能会减少其他 DOM API 时因干扰而导致的性能下降。

于 2010-10-22T02:23:33.890 回答