为什么IE8上的JavaScript访问图像尺寸如此昂贵?

时间:2010-10-12 08:52:18

标签: javascript performance image dimensions

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

我遇到的问题是Internet Explorer 8上n.width / 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:在115ms内处理10000张图像。 10000被标记为坏。
  • FF:99ms处理10000张图像。 10000被标记为坏。
  • FF:在87ms内处理10000张图像。 10000被标记为坏。
  • IE8:在206ms内处理10000张图像。 10000被标记为坏。
  • IE8:在204ms内处理10000张图像。 10000被标记为坏。
  • IE8:在208ms内处理10000张图像。 10000被标记为坏。

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

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

 var imgW = 43;
 var imgH = 29;

我得到以下结果:

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

没错!当我们跳过<img />维度检查(调用node.width / node.clientWidth等)时,IE8实际上比Firefox更好。

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

6 个答案:

答案 0 :(得分:8)

你的代码非常基本。您可以优化的唯一方法是检查尺寸:

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

这样,如果图片的width错误,则无法访问height。对于错误width的图片,它会使您的代码速度提高1.5-2倍。

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

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

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

进行这些更改会对我的计算机进行以下改进 demo

FF: 200 ms ->  7 ms
IE:  80 ms -> 20 ms

答案 1 :(得分:5)

换句话说,你问为什么浏览器A需要比浏览器B更长的时间来完成相同的操作。

嗯,他们不做同样的事情。你在脚本中写下你想要发生的事情,你选择的浏览器会尝试最好地实现这一点,但是你很少或根本无法控制它是如何做到的。

浏览器由不同的团队编写,他们有不同的理解方法。您的20行内容脚本可能需要浏览器A中的10000个CPU周期和浏览器B中的50000个,这取决于浏览器代码的编写方式和浏览器的内部工作方式。

为了详细解答为什么IE8比FF慢,在这种情况下,人们必须深入了解最新情况。由于IE8的源代码不公开,我们无法查看,我不知道是否有任何详细的文档可以告诉我们发生了什么。

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

该怎么做:

  • 获取图片的尺寸。

A队:

  1. 从指定来源加载文件
  2. 将图像解码到内存中
  3. 返回图像的宽度和高度
  4. 没错,是吗?它确实返回图像的宽度和高度。团队可以做得更好吗?

    B队:

    1. 将前1024个字节的文件加载到内存中
    2. 检测图像格式
      • 是jpeg吗?获取标题FFC0,保存宽度和高度
      • 是png吗?找到标题,保存宽度和高度
      • 是gif吗?找到标题,保存宽度和高度
    3. 返回图像的宽度和高度
    4. 他们的代码也会返回图像的宽度和高度,但是他们以不同的方式执行它,比团队A写的代码快几倍:只有文件的开头加载到内存中,尺寸取自标题而不解码图像。它节省了带宽,内存和时间。

      那么哪一个最好? A队或B队的代码?想想这一点,两个团队都会对10万张图片运行代码....可能需要一个......哦,团队B已经完成了!他们说10%的图像小于50x60像素,并且他们无法打开其中3个像素。那么A队怎么样?看起来我们要等一会儿......也许是一杯咖啡?

      [10分钟后]

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

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

      您认为哪种代码最好?

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

答案 2 :(得分:4)

嗯,这很可能不是你想要的,但我会发布它,以防它帮助其他人。由于无法提高浏览器基本功能的速度,因此可以防止循环在浏览器执行时冻结浏览器。您可以通过以块为单位执行循环并使用setTimeout以0开始下一个块来执行此操作。这基本上允许浏览器重新绘制,并在调用下一个块之前执行其他操作。这是您脚本的修改版本:

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();
    }
};

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

答案 3 :(得分:1)

我对你的问题非常感兴趣,但遗憾的是我并没有真正优化你的代码。我在IE执行时能够减少大约30到40毫秒(这显然取决于你的物理机器的功率)。但我尝试了一切

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

  • [element].getBoundingClientRect() - 基本上这会在一个
  • 中返回高度和宽度
  • document.elementFromPoint(x, y) - 我使用offsetLeft + 50offsetTop + 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

答案 4 :(得分: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.

答案 5 :(得分: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时干扰造成的性能下降。