ak_47: (Default)
[personal profile] ak_47
(Здесь собраны воедино отрывки дискуссий отсюда и отсюда.)

Описание проблемы

При загрузке LJ страницы браузер IE7(IE6) зависает на несколько секунд (или минут). При этом загрузка CPU находится на отметке ~100%. Проблема проявляется особенно остро на страницах с большим количеством комментариев.

Причина

Причина кроется в файле "http://www.livejournal.com/js??dom.js" и конкретно в следующих трёх функциях:
  • DOM.filterElementsByClassName

  • DOM.filterElementsByAttribute

  • DOM.filterElementsByTagName
Во всех трёх к проблеме приводит одинаковый участок кода, поэтому разбор полётов будет проводится на примере `DOM.filterElementsByClassName'. Вот как выглядит эта функция:
    filterElementsByClassName: function( es, className ) {
        var filtered = [];
        for( var i = 0; i < es.length; i++ ) {
            var e = es[ i ];
            if( DOM.hasClassName( e, className ) )
                filtered[ filtered.length ] = e;
        }
        return filtered;
    }
где `es' это коллекция DOM элементов, возвращённая из вызова `document.getElementsByTagName("*")'. В частности, к зависанию приводит вот эта строка:
    var e = es[ i ];
Почему же в браузерах на движке IE такие грабли? Я считаю что корень проблемы заключается в различной имплементации коллекции элементов и следовательно, различном поведении оператора "[]" (subscript operator). Я буду говорить IE или FireFox, подразумевая на самом деле JavaScript движок, используемый этими браузерами (MS JScript и SpiderMonkey, соответственно).

Судя по всему, в FireFox коллекция элементов представляет собой массив последовательных элементов, либо содержит hash table для быстрого доступа к элементу по индексу. Поэтому оператор "[]" работает быстро, т.к. доступ к каждому элементу это всего лишь оффсет от первого элемента (либо готовая ссылка из hash table).

В IE, по-видимому, элементы коллекции хранятся в виде linked list или похожей структуре данных. Поэтому оператору "[]" для доступа к каждому i-му элементу надо сначала пройтись по всем i-1 элементам до него. Если это происходит несколько раз во время загрузки страницы (а так и происходит в скриптах LJ), то суммарное время итераций может быть вполне ощутимым.

Например, для страницы с 1000-й элементов (не такая уж и большая страница) и тремя проходами по всем элементам имеем:
    (1 + 2 + 3 + ... + 999 + 1000) * 3 = 1 498 500.
Для грубой оценки можно считать полтора миллиона итераций по элементам. Допустим что компьютер мощный и каждая итерация занимает треть миллисекунды. Выходит что для полного пробега нужно 500 секунд. А это ~8.3 минуты. Вот вам и зависание браузера на 5-10 минут на больших страницах.

Решение

Суть решения заключается в замене способа итерации по коллекции. В IE для этой цели существует специальный объект `Enumerator'. Вот как выглядит функция `DOM.filterElementsByClassName' с применением этого объекта:
    filterElementsByClassName: function( es, className ) {
        var filtered = [];
        for( var en = new Enumerator(es); !en.atEnd(); en.moveNext() ) {
            var e = en.item();
            if( DOM.hasClassName( e, className ) )
                filtered.push(e);
        }
        return filtered;
    }
Также, добавление найденых элементов в новый массив `filtered' было изменено на более быстрое `filtered.push(e);' вместо `filtered[filtered.length] = e;'.

Теперь осталось исправить вышеуказанные функции в скрипте LJ. Это можно сделать двумя способами (в скобках указаны примеры продуктов, с помощью которых это можно осуществить):
  1. Поставить некий фильтр на соединение с сервером вне браузера и исправить там страницу (Ad Muncher, Privoxy и т.д.).

  2. Поставить на браузер расширение, которое даёт доступ к содержимому страницы и написать костыль, который будет страницу чинить  (IE7Pro, Turnabout и т.д.).
На своё счастье я уже давно пользуюсь Ad Muncher'ом, поэтому не пришлось ничего специально ставить. Тем не менее, нижеприведённое решение можно применять и в других похожих программах, независимо от выбранного способа (естественно, после доработки напильником).

Решение с помощью Ad Muncher

Должен сразу предупредить что это решение грубое и может портачить всплывающее оконце (Contextual Hover Menu) над иконкой юзера. Итак:
  1. Скопировать этот код в Notepad (или любой другой любимый текстовой редактор):
    if (document.domain.indexOf('livejournal.com') != -1)
    {
        DOM = {};
    
        DOM.filterElementsByClassName = function( es, className ) {
            var filtered = [];
            for( var en = new Enumerator(es); !en.atEnd(); en.moveNext() ) {
                var e = en.item();
                if( DOM.hasClassName( e, className ) )
                    filtered.push(e);
            }
            return filtered;
        };
        
        DOM.filterElementsByAttribute = function( es, attr ) {
            if( !es )
                return [];
            if( !defined( attr ) || attr == null || attr == "" )
                return es;
            var filtered = [];
            for( var en = new Enumerator(es); !en.atEnd(); en.moveNext() ) {
                var element = en.item();
                if( !element )
                    continue;
                if( element.getAttribute && ( element.getAttribute( attr ) ) )
                    filtered.push(element);
            }
            return filtered;
        };
        
        DOM.filterElementsByTagName = function( es, tagName ) {
            if( tagName == "*" )
                return es;
            var filtered = [];
            tagName = tagName.toLowerCase();
            for( var en = new Enumerator(es); !en.atEnd(); en.moveNext() ) {
                var e = en.item();
                if( e.tagName && e.tagName.toLowerCase() == tagName )
                    filtered.push(e);
            }
            return filtered;
        };
    }

  2. Отформатировать вышеприведённый код так, чтобы он был в одну (очень длинную) строку. Это ограничение Ad Muncher'а, ничего не поделаешь.

  3. Добавить получившуюся строку в фильтры Ad Muncher'а; категория фильтра: "Add javascript to all pages".

  4. Всё!

Обходной путь

Решение для бедных крайне простое и одновременно чрезвычайно эффективное. Примерно как топор в качестве средства от головной боли. Всё что нужно сделать это добавить строку *.livejournal.com в список Restricted Sites в настройках IE. Тогда вообще никаких скриптов на страницах LJ исполняться не будет. Нет скриптов - нет проблемы.

Дополнительная информация

После долгого перерыва Microsoft разродилась новой версией движков для скриптов: 5.7. В новой версии зачинены многие баги, произведены серьёзные улучшения в работе garbage collector'а и сделаны другие полезные изменения. Всем настоятельно рекомендуется обновить свои системы.После обновления значительно смягчились симптомы подвисания IE.

Date: 2007-10-05 06:27 pm (UTC)
From: [identity profile] birdwatcher.livejournal.com
Ах, вот оно что. На все есть причина.

Date: 2007-10-05 06:42 pm (UTC)
From: [identity profile] nessik.livejournal.com
кстати, всё забываю узнать что такое "оффсет"?
и почему бы тебе этот пост какому-нибудь авве не послать?

Date: 2007-10-05 11:18 pm (UTC)
From: [identity profile] nessik.livejournal.com
шаша, ты это сейчас с кем разговаривал? :)

Date: 2007-10-05 07:55 pm (UTC)
From: [identity profile] cmm.livejournal.com
кстати, всё забываю узнать что такое "оффсет"

"отступ".

Date: 2007-10-05 07:59 pm (UTC)
From: [identity profile] cmm.livejournal.com
был какой-то способ отключить конкретно этот contextual hover, кажется через консоль.
я этим способом воспользовался, но это было давно и я не помню название отключенного property, увы.
отключил, кстати, не из соображений производительности, а потому что раздражало. :)

Date: 2008-01-08 07:15 am (UTC)
From: [identity profile] eugene-ivanov.livejournal.com
проблема и выеденного яйца не стоит

суть не в том, что в осле что-то томрозит, а в неправильной архитектуре системы lj.

если не пользоваться getelems byclass или подобные идиотизмы, и выстроить систему так, чтобы это не использовать, то всё будет тип-топ.

Profile

ak_47: (Default)
АК-47

Most Popular Tags

Expand Cut Tags

No cut tags
Powered by Dreamwidth Studios