Pull to refresh

Comments 48

UFO just landed and posted this here
Позже обязательно напишу им, и потом отпишусь если получу ответ от них…
Насколько я понимаю, первый вариант удаляет объект, а не элемент массива. И, если я не ошибаюсь, я даже где-то видел предостережение от такого использования delete.
Не совсем, если внимательно посмотреть, то там даже не массив, а объект, так же можно заменить
 testedObject[ i ] = ... 
на такое
 testedObject[ 'item_' + i ] 
но это ничего не даст.

Первый и второй вариант различаются лишь структурой хранения данных.

Вариант первый:

testedObject = {
    1 : {
        obj : {....}
    },
    ....
    .....
}

Вариант второй:

testedObject = {
    obj : {
        1 : {
             .....
         },
          ....
          .....
    }
}
Ну, я про это и пишу. В варианте с утечкой, delete применяется НЕ к элементу массива, а к объекту.
Точно, не правильно понял вас, но замечу что и в варианте без утечки delete так же применяется к объекту.
Теоритически, да. Но, вероятно, при таком синтаксисе уже работает «магия JavaScript» и delete делает именно то, что нужно.
Тоже возможно. Но как тогда объяснить, что если в качестве индетефикатора свойства, использовать не число, а строку 'item_' + i, и это ничего не меняет. Думаете тоже магия?
Ничего не меняет в том смысле что при таком варианте утечки продолжаются?
Вот это уже интересно. Т.к. удаление «элемента хэша» (или «свойства») это достаточно стандартная ситуация.
Вот и я о том же!!! Из-за этого потерял кучу времени пока понял в чем дело. У меня куча мест которые используют оператор delete, и везде все нормально, но в одной функции был именно такой вариант хранения данных, и уж никак я не мог грешить на delete.
Я смутно припоминаю, что я в таком случае сначала присваивал свойству пустое значение.
Пробовал, тоже не помогает. Я вообще кучу всего пробовал чтоб в варианте с утечкой не меняя структуры данных избавиться от утечки: переприсваивал свойства, делал удаление в отдельной функции и еще куча бредовых штук, но ничего не помогало…
Нда… Круто. Остается надеяться на разработчиков браузеров или виртальной машины JS.
А если заполнять вне функции?
Только что попробовал, все так же… течет ((
ха же хочется сюда `rm -fR .`
ха = как.
(голосую в соседнем посте за включение функции редактирования комментария =) )
Нет, в варианте без утечки вы удаляете элемент массива, а в варианте с утечкой — только одно его свойство.

Представьте, что у вас более сложная структура данных в варианте с утечкой:
 testedObject[ i ] = {
                obj : getObject(),

                fld1 : val1,
                fld2 : val2 
// и тд
            };


А удаляете вы только
delete testedObject[ i ].obj;


тогда как

testedObject[ i ].fld1;

и

testedObject[ i ].fld2;

и тд остаются на месте.

Все что надо — просто удалять

testedObject[ i ];

— то есть сам элемент массива, а не одно из его свойств.
При удалении свойства сам элемент никуда не девается.
Вот и вся разгадка.

Нет там утечки и не было.
Вы правы, именно так как вы описали все и задумывалось, но вот только если удалить testedObject[ i ].obj; а остальных свойсв нету, то мой массив занимает около 6-8 мб, а не 50. Я специально сделал свойство obj достаточно боьшим чтоб он заметно ощущался на фоне массива.
Интересно, а
testedObject[ i ] = null;
не поможет избавиться от утечки?
А я правильно понимаю, что сборщик мусора потом все же вычистил память?
Нет, сборщик вычищает только ту память которая была съедена во время удаления объектов. Причем в варианте без утечки во время удаления память не съедается. А память занимаемая объектами остается не вычещенной.
Учитывая, что delete для элементов массива не меняет его длинну, это может привести к всяческим ошибкам при дальнейших операциях с этим массивом. Предпочитаю использовать splice.
Кстати, вопрос не в тему))
Как сделать чтоб мой пост был во вкладке посты?
А все ненадо, разобрался))
Скажите, а для отлова утечек вы используете только стандартные средства хрома или что-то ещё?
И я правильно понимаю что вы скрупулёзно, в силу требований проекта, проверяете на утечки новые куски кода?
мне кажется это ощютилось тормозами броузера. вероятно в системе объекты больше
Да, сейчас это уже обычная практика.
Прикольно, что судя по всему, если сделать вместо
delete testedObject[ i ].obj;
testedObject[ i ].obj = null;
сборщик мусора собирает эти объекты
Как Вы пользуетесь гуглом?
У меня первым результатом ссылка на StackOverflow, смотрите второй ответ по популярности в качестве разъясняющего примера.
testedObject[ i ].obj = null;

будет в данном случае правильным решением, так как однозначно говорит удалить все ссылки на объект.
С чего это зануление однозначно говорит удалить _все_ ссылки на объект? Оно просто зануляет эту ссылку и все.
Когда вызывается delete в примере автора, других ссылок на него все-равно нет больше, по идее объект должен собираться со временем, поэтому разумным выглядит объяснение habrahabr.ru/post/150723/#comment_5107501
да, Вы правы, неправильно выразился, в данном случае зануляется ссылка на объект, которая была единственной, что и «видит» GC, поэтому и освобождает память из под объекта.
хм, нет, здесь все-таки прямое «уничтожение» объекта заменой на null, то есть mraleph ниже прав, у меня 848 байт на каждый элемент массива остается, что в сумме дает порядка 82 МБ.
Прежде всего совет: не пытайтесь понять, что удаляется, а что не удаляется по графику потребления памяти — это гадание на кофейной гуще. Просто посмотрите на снапшот кучи.

Объяснение (по крайней мере для Хрома) очень простое: когда вы делаете delete testedObject[i].obj, V8 нормализует объект testedObject[i] — трансформирует его из быстрого компактного представления в медленное и раздутое представление на основе словаря, который еще и выделяется с запасом по размеру. При этом V8 не замечает, что после удаления в словаре будет пусто — и словарь (800 байтов) остается болтаться в воздухе. И так для каждого из ваших объектов.
а сами объекты с 9 свойствами, конечно же, помирают… просто они были меньше с словаря, который теперь свисает с каждого testedObject[i], что пораждает иллюзию их живости.
Дополню mraleph:

После создания объектов:


После удаления филдов obj


Как видно — obj прекрасно подчистился и никаких «first»-«second»… не осталось. Но остался массив в 10 000 элементов (сам по себе занимающиий 470КБ), в каждой ячейке из которого пустой объект весом 424 Байта (почему пустой объект весит больше заполненного объяснил mraleph — полагаю потому что сказав delete testedObject[i].obj вы фактически сказали что testedObject[i] — изменяемый и вы можете захотеть впихнуть в него новых данных например а не просто обратиться к его полям в «read-only» режиме)
подскажите, это и есть «снапшот кучи», о котором упомянул mraleph?
да, в хроме Heap Snapshot во вкладке Profiles
Все логично. В первом варианте у Вас:
testedObject = {
    1 : {
        obj : {....}
    },
    ....
    .....
}

что дает count объектов внутри testedObject, внутри каждого из которых по 1 объекту obj, то есть суммарное кол-во «значимых» объектов 2*count.

Во втором варианте у Вас:
testedObject = {
    obj : {
        1 : {
             .....
         },
          ....
          .....
    }
}

что дает внутри testedObject 1 объект obj, в котором count объектов с индексами, то есть суммарное кол-во «значимых» объектов count + 1.
То есть второй вариант изначально потребляет меньше памяти, что также можно видеть на графиках, достаточно выключить Record, а потом опять включить, чтоб увидеть конечный результат после нажатия «Заполнить массив».

С учетом комментария mraleph и комментария seriyPS можно принять, что при операции delete родительский объект «нормализуется» и в конечном счете становится не меньше N байт (как видно, зависит от системы).

Для первого варианта родительским объектом является testedObject[i] (который Вы не удаляете), для второго варианта родительским элементом является testedObject.obj, то есть в первом варианте (с «утечкой») Вы получаете в остатке:
(2*count - count)*N => count*N байт лишними
Во втором варианте Вы получаете:
(count + 1 - count)*N => N байт лишними
Как-бы логично, что count*N > N, «примерно» в count раз ;)
выше уже написали что удаляются разные вещи, но они удаляются — delete возвращает также результат операции, в обоих случаях true.

кроме того, странно видеть профайлер хрома, если баг открывают на мозилловцев.
если смотреть about:memory, то память возвращается спустя какое-то время или запуском сборщика мусора.
>> На все вопросы получены четкие ответы, это не магия JavaScript, не утечка и не браузерный баг, это особенность реализации.

Не совсем, все-таки, на мой взгляд утечка здесь есть, потому как был {}, потом {obj: {...}}, потом удалили obj через delete и получили, что размер полученного {} > начального {}, или теперь memory leak == «особенность реализации»? )
Просто у Вас некорректный пример, так как сравниваете изначально разные вещи, если уж отписывать баг, то скрипт должен быть другим, в котором будет видна утечка от «особенностей реализации».

В приведенном выше рассуждении была некая переменная N, вместо которой нужно (N + M), где:
— M — это размер пустого объекта {};
— N — размер «особенностей нормализации».
Тогда testedObject[i] должен после delete testedObject[i].obj стать размером M, а становится размером (N + M), то есть в первом варианте после «очистки» в идеале должны получить M*count, а получили (N + M)*count, а еще в куче видно, что M много больше N, что немного неправильно.
Простите первый раз неправильно прочитал ваш комент.
Вы все же считаете что утечка ест, я правильно понял? Я изначально думал тоже об утечке, но сильно сомневался изза того что не может быть такого о всех браузерах одновременно, и вариант описывающий реализацию delete, более реалистичен.
утечка — это когда память болтается непонятно где, не используется и не может быть переиспользована. а здесь не эффективное использование памяти, потому что это память никуда не пропала и занята объектом, на который есть валидные ссылки из другого объекта.
Не совсем, все-таки, на мой взгляд утечка здесь есть


Да, я согласен, это не утечка, как таковой казалась на первый взгляд, и я написал об этом. Под особенность реализации, о которых я не знал, думаю и многие другие, я имел в виду:
когда вы делаете delete testedObject[i].obj, V8 нормализует объект testedObject[i] — трансформирует его из быстрого компактного представления в медленное и раздутое представление на основе словаря, который еще и выделяется с запасом по размеру. При этом V8 не замечает, что после удаления в словаре будет пусто — и словарь (800 байтов) остается болтаться в воздухе.

зная о такой реализации оператора delete, я бы не наткнулся на такие грабли, и поста бы вообще небыло. Но пост есть, и думаю заслужиает существования, т.к. вполне вероятно многие незнаю как работает delete.
Sign up to leave a comment.

Articles