Слышал, что можно как-то хитрым образом написать программу так, что сборщик мусора уйдет в глубокий даун разгребая мусор, короче будет работать неэффективно. Правда это или миф? Если у кого есть конкретные примеры таких программ — поделитесь, пожалуйста.
В свою очередь, попробовал испытать пронырливость сборщика мусора. Рассуждал так: чем больше ссылок между объектами — тем хуже для сборщика мусора. Если у нас есть N штук объектов, то максимально возможное количество ссылок между ними равно N^2 — это когда все объекты друг на друга ссылаются (включая ссылки на себя). То есть случай N^2 ссылок является самым сложным для сборщика мусора, так или не так?
Создаю N объектов, устанавливаю между ними N^2 связей, потом натравливаю на эту структуру сборщик мусора и измеряю время. Вот что наизмерял:
N = 1000, new 50 ms, GC 70 ms
N = 2000, new 230 ms, GC 261 ms
N = 3000, new 511 ms, GC 570 ms
N = 4000, new 912 ms, GC 1011 ms
N = 5000, new 1452 ms, GC 1773 ms
N = 6000, new 2083 ms, GC 2333 ms
N = 7000, new 5208 ms, GC 3995 ms
N = 8000, new 5138 ms, GC 4907 ms
В этой таблице, время "new" включает в себя время на создание N объектов и время на установление между ними N^2 связей, время "GC" включает в себя полную очистку памяти сборщиком мусора и подготовки ее к повторному использованию.
Вывод. Сборщик мусора работает примерно столько же времени сколько времени требуется на само создание данной структуры в памяти, причем на малом количестве малых объектов он чуть отстает, а на большом количестве больших объектов он даже работает шустрее.
Я что-то не так померил или сборщик мусора действительно крут?
Исходный код программы:
MODULE TestGC;
IMPORT StdLog, Services;
PROCEDURE f(N: INTEGER);
TYPE Objects = POINTER TO ARRAY OF Object;
Object = POINTER TO RECORD ref: Objects END;
VAR obj: Objects; i,j,n: INTEGER; t: LONGINT;
BEGIN
StdLog.String("N = "); StdLog.Int(N);
(* Создание объектов *)
t := Services.Ticks();
NEW(obj, N);
FOR i := 0 TO N-1 DO NEW(obj[i]) END;
FOR i := 0 TO N-1 DO
NEW(obj[i].ref, N);
FOR j := 0 TO N-1 DO obj[i].ref[j] := obj[j] END
END;
t := Services.Ticks() - t;
StdLog.String(", new "); StdLog.Int(t); StdLog.String(" ms");
(* Удаление объектов *)
t := Services.Ticks();
obj := NIL;
Services.Collect();
t := Services.Ticks() - t;
StdLog.String(", GC "); StdLog.Int(t);
StdLog.String(" ms");StdLog.Ln();
END f;
PROCEDURE Test* ();
VAR n: INTEGER;
BEGIN
StdLog.Ln();
FOR n := 1 TO 8 DO f(n*1000) END
END Test;
END TestGC.
Размер одного объекта = 4*N + const, где const — это то что хранит в себе RTTI информацию.
Машина: процессор Celeron 1100 Coppermain 128Kb, память 448Mb PC100.
Здравствуйте, Сергей Губанов, Вы писали:
СГ>Я что-то не так померил или сборщик мусора действительно крут?
Перед удалением объектов вызови GC.Collect еще раз пока объекты не могут быть удалены. Для того чтобы они перешли в следующее поколение. И опубликуй плиз результаты. Они могут сильно отличаться
С уважением, Евгений
JetBrains, Inc. "Develop with pleasure!"
Здравствуйте, Сергей Губанов, Вы писали:
СГ>Создаю N объектов, устанавливаю между ними N^2 связей, потом натравливаю на эту структуру сборщик мусора и измеряю время. Вот что наизмерял: СГ>
СГ>N = 8000, new 5138 ms, GC 4907 ms
СГ>
5 секунд? Время сбора мусора 5 секунд? Что-же это за GC такой?
Здравствуйте, xvost, Вы писали:
X>Здравствуйте, alexeiz, Вы писали:
A>>5 секунд? Время сбора мусора 5 секунд? Что-же это за GC такой?
X>Все нормально. 8 тыс объектов, 16 млн связей — результат предсказуем.
X>Кстати, для .NET я вообще знаю способы заставить GC лечь намертво. Способы тривиальные.
Такой примерчик .NET GC соберет за >1ms. Он не будет подсчитывать ссылки в недоступных объектах. Кстати, "способы заставить .NET GC лечь намертво" — в студию.
Здравствуйте, alexeiz, Вы писали:
A>Такой примерчик .NET GC соберет за >1ms. A>Он не будет подсчитывать ссылки в недоступных объектах.
Это так. Но если ты создашь 2 такие независимые струкуры, и попробуешь одну из них удалить — GC будет несколько секунд обегать имеющиеся объекты
A>Кстати, "способы заставить .NET GC лечь намертво" — в студию.
Лови.
class C
{
public C() {}
~C() {System.Threading.Thread.Sleep(1000000);}
public static void Main()
{
C c = new C();
System.GC.Collect();
System.GC.WaitForPendingFinalizers();
}
}
Вызов System.GC.WaitForPendingFinalizers() вставлен только для того, чтобы в заставить отработать деструктор прямо сейчас. В реальной жизни этот вызов ненужен — рано или поздно деструктор запустится.
При этом основная неприятность всего этого — во время GC .NET полностью блокирует все потоки приложения. Т.е. имеем "мертвый вис".
С уважением, Евгений
JetBrains, Inc. "Develop with pleasure!"
Здравствуйте, xvost, Вы писали:
X>Здравствуйте, alexeiz, Вы писали:
A>>Такой примерчик .NET GC соберет за >1ms. A>>Он не будет подсчитывать ссылки в недоступных объектах.
X>Это так. Но если ты создашь 2 такие независимые струкуры, и попробуешь одну из них удалить — GC будет несколько секунд обегать имеющиеся объекты
...
X>Вызов System.GC.WaitForPendingFinalizers() вставлен только для того, чтобы в заставить отработать деструктор прямо сейчас. В реальной жизни этот вызов ненужен — рано или поздно деструктор запустится.
X>При этом основная неприятность всего этого — во время GC .NET полностью блокирует все потоки приложения. Т.е. имеем "мертвый вис".
Такой "способ" я не приму. Я думал, что ты знаешь честный способ.
Твой пример — блокируется только тред содержащий цикл. Т.е. например остальные потоки сервера спокойно продолжают обслуживать своих клиентов. В моем примере — нафиг блокируется ВЕСЬ сервер целиком.
С уважением, Евгений
JetBrains, Inc. "Develop with pleasure!"
Здравствуйте, xvost, Вы писали:
X>При этом основная неприятность всего этого — во время GC .NET полностью блокирует все потоки приложения. Т.е. имеем "мертвый вис".
А вот здесь ошибка — финалайзеры, в отличие от собственно сканирования графов и реструктуризации кучи, работают в отдельном потоке.
Здравствуйте, AndrewVK, Вы писали:
AVK>А вот здесь ошибка — финалайзеры, в отличие от собственно сканирования графов и реструктуризации кучи, работают в отдельном потоке.
Oops! Проверил — я был неправ.
Значит пока что я не знаю "честного" способа уронить GC
С уважением, Евгений
JetBrains, Inc. "Develop with pleasure!"
Здравствуйте, alexeiz, Вы писали:
СГ>>Создаю N объектов, устанавливаю между ними N^2 связей, потом натравливаю на эту структуру сборщик мусора и измеряю время. Вот что наизмерял: СГ>>
СГ>>N = 8000, new 5138 ms, GC 4907 ms
СГ>>
A>5 секунд? Время сбора мусора 5 секунд? Что-же это за GC такой?
Здравствуйте, xvost, Вы писали:
X>Перед удалением объектов вызови GC.Collect еще раз пока объекты не могут быть удалены. Для того чтобы они перешли в следующее поколение. И опубликуй плиз результаты. Они могут сильно отличаться
Знаете, сейчас продолжил издевательства над BlackBox-совским GC и пришел к заключению, что вызов Services.Collect() для него ровным счетом ничего не означает (Даже не смотря на то, что выполняется много времени). Чем реально занимается этот Services.Collect(), ума не приложу , хотя в хелпе черным по белому написано: "Forces a garbage collection". Реальная чистка памяти, как я понял, возникает только тогда когда сам BlackBox этого захочет, а раз так, то у меня нет способа измерить реальную скорость сборки мусора в BlackBox. Вот такие пироги............ Я могу измерить только суммарную скорость операций "создание+удаление".
Вот, например, создание/удаление (в цикле 10 раз) N = 5000 объектов с N^2 ссылками друг на друга, на машине Athlon 1.47Ghz 256Mb DDR266 отнимает от 811 до 820 тиков.
MODULE TestGC;
IMPORT StdLog, Services;
PROCEDURE f(N: INTEGER);
TYPE Objects = POINTER TO ARRAY OF Object;
Object = POINTER TO RECORD ref: Objects END;
VAR obj: Objects; i,j: INTEGER;
BEGIN
NEW(obj, N);
FOR i := 0 TO N-1 DO NEW(obj[i]) END;
FOR i := 0 TO N-1 DO
NEW(obj[i].ref, N);
FOR j := 0 TO N-1 DO obj[i].ref[j] := obj[j] END
END
END f;
PROCEDURE Test* ();
CONST N = 5000;
VAR n: INTEGER; t: LONGINT;
BEGIN
t := Services.Ticks();
FOR n := 1 TO 10 DO f(N) END;
t := Services.Ticks() - t;
StdLog.String("N ="); StdLog.Int(N);
StdLog.String(", t ="); StdLog.Int(t DIV 10); StdLog.String(" ms");
StdLog.Ln();
END Test;
END TestGC.
Log:
N = 5000, t = 811 ms
Но сколько именно из этой 0.8 секунды тратится на создание такой сложной структуры в памяти, а сколько тратится на ее уничтожение — я не знаю. В сумме 0.8 секунды и точка.
Здравствуйте, xvost, Вы писали:
X>Oops! Проверил — я был неправ.
X>Значит пока что я не знаю "честного" способа уронить GC
Что значит честный? Вот такой код собственно в свое время через продолжительное время убивал януса:
public sealed class SomeSingleton
{
private SomeSingleton() {}
public readonly SomeSingleton Instance = new SomeSingleton();
public event EventHandler SomeEvent;
}
...
private void RefreshForums
{
_forums.Clear();
foreach (ForumData fd in GetFromDb())
_forums.Add(new Forum(fd));
}
...
public class Forum
{
public Forum(ForumData fd)
{
//...
SomeSingleton.SomeEvent += new EventHandler(SomeEventFired);
}
//...
}
Здравствуйте, Трурль, Вы писали:
Т>Здравствуйте, Сергей Губанов, Вы писали:
СГ>> Рассуждал так: чем больше ссылок между объектами — тем хуже для сборщика мусора. Т>Это неверно.
X>class C
X>{
X> public C() {}
X> ~C() {System.Threading.Thread.Sleep(1000000);}
X> public static void Main()
X> {
X> C c = new C();
X> System.GC.Collect();
X> System.GC.WaitForPendingFinalizers();
X> }
X>}
X>
Ты его хоть запускал?
X>Вызов System.GC.WaitForPendingFinalizers() вставлен только для того, чтобы в заставить отработать деструктор прямо сейчас. В реальной жизни этот вызов ненужен — рано или поздно деструктор запустится.
Трепачь находка для шпионов. Если ЖЦ видет что финалайзер занят более чем на определенное время, то плюет на финализацию и продолжает работу.
X>При этом основная неприятность всего этого — во время GC .NET полностью блокирует все потоки приложения. Т.е. имеем "мертвый вис".
Кончай нести фигню и пойди почитай Рихтера.
... << RSDN@Home 1.1.4 beta 3 rev. 207>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, xvost, Вы писали:
X>Твой пример — блокируется только тред содержащий цикл. Т.е. например остальные потоки сервера спокойно продолжают обслуживать своих клиентов. В моем примере — нафиг блокируется ВЕСЬ сервер целиком.
1. Не ври.
2. Подними приоритет у потока и погляди как там остальные потоки спокойно...
... << RSDN@Home 1.1.4 beta 3 rev. 207>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, AndrewVK, Вы писали:
AVK>Что значит честный? Вот такой код собственно в свое время через продолжительное время убивал януса:
Это не проблемы не сборщика мусора, а проблема реализации событий. Тормоза там наступаеют не при ЖЦ, а именно при вызове событий или при привязке/отвязке.
... << RSDN@Home 1.1.4 beta 3 rev. 207>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.