Почему доступ к размерам изображения настолько дорог в JavaScript на IE8?

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

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

Код создает следующие выходные синтаксические разборки 10k изображений (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 так долго проверяет размер изображения и, в конечном итоге, как повысить производительность этой проверки?

Ответы

Ответ 1

Хорошо, скорее всего, это не то, что вы ищете, но я, хотя я бы опубликовал его, если он поможет кому-то другому. Поскольку нет возможности улучшить скорость работы основных функций браузера, вы можете предотвратить зависание цикла в браузере во время его выполнения. Вы можете сделать это, выполнив цикл в кусках и запустив следующий фрагмент с помощью setTimeout со временем 0. Это в основном позволяет перерисовать браузер и выполнять другие действия, прежде чем он называет следующий фрагмент. Здесь изменена версия вашего script:

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

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

Ответ 2

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

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";
}

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

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

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

Ответ 3

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

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

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

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

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

Что делать:

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

Команда A:

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

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

Команда B:

  • Загрузите первые 1024 байта файла в память
  • Определить формат изображения
    • Это jpeg? получить заголовок FFC0, сохранить ширину и высоту
    • Это png? найти заголовок, сохранить ширину и высоту
    • Это gif? найти заголовок, сохранить ширину и высоту
  • ширина возврата и высота изображения

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

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

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

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

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

Итак, какой код вы считали лучшим?

Извиняюсь за лингвистические ошибки, английский - не мой родной язык.

Ответ 4

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

Вещи, которые я пытался вместо [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 Engine. Вы можете увидеть некоторые статистические данные о том, что я говорю здесь.

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

Ответ 5

Я не тестировал это в IE, но, что вы могли бы сделать, это удалить объявления переменных из цикла, хотя они не так дороги (с точки зрения процессора), они могут сэкономить несколько циклов процессора, когда они удаленный из цикла, вы также можете перейти и пропустить назначения imgW и imgH и получить доступ к свойствам объектов напрямую, так как это может сохранить один объект для удаления ссылок, поскольку другая часть логической проверки в операторе if 't должен быть выполнен для изображений с дефектной шириной;

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.

Ответ 6

Доступ к атрибуту макета элемента на экране (или под документом .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 медленным

EDIT: document.getElementsByTagName медленнее, чем вы ожидали. Он не возвращает статический массив, а динамический объект, который отражает изменения (если они есть). Вы можете попытаться скопировать элементы в массив, что может снизить падение производительности за счет помех при использовании другого DOM API.