Почему доступ к размерам изображения в JavaScript на IE8 стоит так дорого? - PullRequest
13 голосов
/ 12 октября 2010

Я должен обработать множество изображений.Во-первых, мне нужно проверить, больше ли размер изображения, чем 50x60, и соответственно увеличить счетчик плохих изображений.

Проблема, с которой я столкнулся, заключается в том, что в Internet Explorer скорость n.width / n.height8 крайне низок.Я проверил n.offsetWidth, n.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" />

Код выдает следующие выходные данные при разборе 10 тыс. Изображений (3 различных сочетания клавиш Ctrl + F5)

  • FF: обработано 10000 изображений за 115 мс.10000 помечены как плохие.
  • FF: обработано 10000 изображений за 99 мс.10000 помечены как плохие.
  • FF: обработано 10000 изображений за 87 мс.10000 помечены как плохие.
  • IE8: обработано 10000 изображений за 206 мс.10000 были отмечены как плохие.
  • IE8: обработано 10000 изображений за 204 мс.10000 помечены как плохие.
  • IE8: обработано 10000 изображений за 208 мс.10000 были помечены как плохие.

Как видите, код в FF 3.6 в два раза быстрее, чем код, выполняемый в IE8.

Чтобы доказать, что проблема действительно связана со свойством измерения скорости браузера, если я изменю: n.width и n.height на константы, у нас будет:

 var imgW = 43;
 var imgH = 29;

Я получаю следующие результаты:

  • FF: обработано 10000 изображений за 38 мс.10000 помечены как плохие.
  • FF: обработано 10000 изображений за 34 мс.10000 помечены как плохие.
  • FF: обработано 10000 изображений за 33 мс.10000 помечены как плохие.
  • IE8: обработано 10000 изображений за 18 мс.10000 помечены как плохие.
  • IE8: обработано 10000 изображений за 22 мс.10000 были помечены как плохие.
  • IE8: обработано 10000 изображений за 17 мс.10000 были отмечены как плохие.

Это верно!Когда мы пропускаем проверку размера <img /> (вызов node.width / node.clientWidth и т. Д.), IE8 на самом деле работает лучше, чем Firefox.

Есть ли у вас какие-либо идеи, почему IE так долго проверяетРазмер изображения и, в конечном итоге, как улучшить производительность этой проверки?

Ответы [ 6 ]

8 голосов
/ 17 октября 2010

Ну, твой код довольно простой. Единственное, что вы можете оптимизировать, это как вы проверяете размеры:

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

Таким образом, если width изображения неверно, к height не будет получен доступ. Это сделает ваш код в 1,5-2 раза быстрее для изображений с плохой width.

Но я думаю, что вам не нужно 10 000 изображений как часть вашего сайта. В этом случае вы можете проверить Image объектов вместо <img> элементов.

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

Это сделает ваш код в 2 раза быстрее в IE 8 и в 10 раз быстрее в FF.

Внесение этих изменений дало следующие улучшения на моем компьютере ( демо ) :

FF: 200 ms ->  7 ms
IE:  80 ms -> 20 ms
5 голосов
/ 23 октября 2010

Другими словами, вы спрашиваете, почему браузеру A требуется больше времени, чем браузеру B, чтобы сделать точно такой же сигнал.

Ну, они НЕ делают то же самое.Вы пишете в своем сценарии то, что хотите, и выбранный вами браузер делает все возможное, чтобы это произошло, но вы практически не контролируете, как это происходит.

Браузеры написаны разными командами с разной философией того, как делать вещи.Ваш 20-строчный сценарий может потребовать 10000 процессорных циклов в браузере A и 50000 в браузере B в зависимости от того, как написан код браузера и как он работает.

Чтобы дать подробный ответ, почему IE8 медленнее по сравнению с FF в этом случае, нужно посмотреть, что происходит.Поскольку исходный код для IE8 не является общедоступным, мы не можем искать там, и я не знаю, есть ли какая-либо документация, достаточно подробная, чтобы рассказать, что происходит.

Вместо этого я приведу вам примеро том, как две разные философии того, как делать вещи, сильно влияют на время, чтобы произвести тот же конечный результат.Примечание. Это пример, и любое сходство с реальным миром является чисто случайным.

Что делать:

  • получить размеры изображения.

Команда A:

  1. Загрузка файла из указанного источника
  2. декодирование изображения в память
  3. возврат ширины ивысота изображения

Ничего плохого в этом нет?Он возвращает ширину и высоту изображения.Может ли команда сделать это лучше?

Команда B:

  1. Загрузить первые 1024 байта файла в память
  2. Определить формат изображения
    • это JPEG?получить заголовок FFC0, сохранить ширину и высоту
    • это png?найти заголовок, сохранить ширину и высоту
    • это GIF?найти заголовок, сохранить ширину и высоту
  3. вернуть ширину и высоту изображения

Их код также возвращает ширину и высоту изображения, но они делаютэто по-другому, что в несколько раз быстрее, чем код, который написала команда А: в память загружается только начало файла, а размеры берутся из заголовка без декодирования изображения.Это экономит полосу пропускания, память и время.

Итак, какой из них лучший?Код от команды А или команды Б?Подумайте об этом на мгновение, пока обе команды запускают свой код на 100 000 изображений .... Может потребоваться какое-то ... о, команда Б уже завершила!Они говорят, что 10% изображений были меньше, чем 50x60 пикселей, и что они не могли открыть 3 из них.Как насчет команды А тогда?Похоже, нам нужно немного подождать ... может быть, чашка кофе?

[10 минут спустя]

Полагаю, вы считаете, что команда B написала лучший код, я прав или нет?

Команда A говорит 8% отизображения были меньше, чем 50x60 пикселей.Странно, это не то, что сказала команда В.Команда А также говорит, что они не могли получить размер 20% изображений, потому что эти файлы были повреждены.Это то, о чем команда B ничего не сказала ...

Так какой код, по вашему мнению, был лучшим?

Я прошу прощения за языковые ошибки, английский не мойродной язык.

4 голосов
/ 20 октября 2010

Ну, это, скорее всего, не то, что вы ищете, но я хотел бы опубликовать это на случай, если это поможет кому-то еще. Поскольку нет способа повысить скорость базовой функциональности браузера, вы можете запретить циклу заморозить браузер во время его работы. Вы можете сделать это, выполнив ваш цикл в чанках и инициировать следующий чанк, используя 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();
    }
};

Конечно, это увеличивает общее время выполнения, но, возможно, вы можете использовать его, чтобы ваша страница работала во время работы.

1 голос
/ 19 октября 2010

Я был очень заинтересован в вашем вопросе, но, к сожалению, мне не удалось оптимизировать ваш код. Я смог сократить время выполнения IE примерно от 30 до 40 мс (это, очевидно, зависит от мощности вашей физической машины). Но я попробовал почти все

То, что я попробовал вместо [element] .width.

  • [element].getBoundingClientRect() - в основном это возвращает высоту и ширину в одном
  • document.elementFromPoint(x, y) - Я, хотя, используя offsetLeft + 50 и offsetTop + 60, мог определить, отличался ли элемент в этой точке от текущего элемента, что означает, что это было "плохое" изображение.

В конце концов, это то, что я придумал, чтобы немного сократить время (от 30 до 40 мс).

Примечание: лучшее время, которое я получил в IE 8, было 171 мс

Отредактировано - Я изменил код ниже, чтобы включить ваш путь, мой путь и использование 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>

Другие примечания:

Движок JavaScript в IE 8 не так быстр, как 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

0 голосов
/ 22 октября 2010

Доступ к атрибуту макета элемента на экране (или в файле document.body) может вызывать накладные расходы макета (блокировка, перекомпоновка и т. Д.), Даже если вы используете доступ только для чтения в IE.

Я не тестировал, но вы можете попробовать что-то вроде этого.

    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.

0 голосов
/ 22 октября 2010

Я не проверял это на IE, но, что вы могли бы сделать, это удалить объявления переменных из цикла, даже если они не такие дорогие (с точки зрения ЦП), они могут сэкономить несколько циклов ЦП, когда ониудаленный из цикла, вы также можете пропустить назначения imgW и imgH и получить прямой доступ к свойствам объектов, поскольку это может сохранить разыменование одного объекта, поскольку другая часть логической проверки в операторе 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.
...