garbage.collect()
Зачем мне все это знать?
Теория
Типичная ошибка: забытая подписка
Типичная ошибка
Типичная ошибка бесполезная работа
Мусор, который не собирается
Что значит мусор?
Что значит живой объект?
Что значит дойти по ссылкам?
Что значит дойти по ссылкам?
Что значит дойти по ссылкам?
Что значит дойти по ссылкам?
Что значит дойти по ссылкам?
Что значит дойти по ссылкам?
Что значит дойти по ссылкам?
Что значит дойти по ссылкам?
Что значит дойти по ссылкам?
Что значит дойти по ссылкам?
Что значит дойти по ссылкам?
Что значит дойти по ссылкам?
Что значит корневой объект?
Что значит корневой объект?
Что значит корневой объект?
Что значит корневой объект?
Суровая реальность
No-op collector
Mark and Sweep
Mark and Compact
Сборка мусора не бесплатна
Semispace aka Lisp 2
На заметку
Разные алгоритмы
Разные алгоритмы
Лучшее из всех миров
Гипотеза поколений?
Сборка поколениями
Ошибка: обман сборщика мусора
Когда собирать?
Stop the World сборщик
Parallel сборщик
Concurrent сборщик
Что почитать
Браузерная реальность
IoT-движки
IoT-движки
Вот эти ребята
В основном, конечно, вот этот
Сборка поколениями
Ошибка: боязнь создания мусора
Параллельный mark
Сколько стоит все это удовольствие?
Сколько стоит все это удовольствие?
Это много
Это много (для геймдева)
Это много (для геймдева)
Это много (для геймдева)
Статистика сборщика мусора: Chromium
Статистика сборщика мусора: Node
Будущее: WeakRef
Будущее: GC в WebAssembly
Что почитать
Повседневность
Если этого вдруг не хватило
Итого
Итого
Команда JavaScript API Яндекс.Карт ищет разработчиков
12.94M
Category: programmingprogramming

Браузерная реальность. Garbage. Collect

1.

2. garbage.collect()

Роенко Андрей
Разработчик API Яндекс.Карт

3.

│ Если бы в JavaScript действительно
работала сборка мусора,
большинство npm-модулей
удаляли бы сами себя сразу после
установки
(наверное, так бы сказал) Роберт Сьюэл

4.

5.

План доклада
1
Теория
2
Суровая реальность
3
Браузерная реальность
4
Повседневность

6. Зачем мне все это знать?

› Хорошо иметь представление о том как работает ваш инструмент
› Вы поймете где можно оптимизировать ваши приложения
› Не будете совершать ошибки
› Перестанете делать «оптимизации»
6

7.

│ Все нетривиальные
абстракции дырявы
Джоэл Спольски
7

8. Теория

9.

window.Foo = class Foo {
constructor() {
this.x = { y: 'y’ };
}
work(name) {
let z = 'z';
return function () {
console.log(name, this.x.y, z);
this.x = null;
}.bind(this);
}
};
9

10.

window.Foo = class Foo {
constructor() {
this.x = { y: 'y’ };
}
work(name) {
let z = 'z';
return function () {
console.log(name, this.x.y, z);
this.x = null;
}.bind(this);
}
};
10

11.

window.Foo = class Foo {
constructor() {
this.x = { y: 'y’ };
}
work(name) {
let z = 'z';
return function () {
console.log(name, this.x.y, z);
this.x = null;
}.bind(this);
}
};
11

12.

window.Foo = class Foo {
constructor() {
this.x = { y: 'y’ };
}
work(name) {
let z = 'z';
return function () {
console.log(name, this.x.y, z);
this.x = null;
}.bind(this);
}
};
12

13.

window.Foo = class Foo {
constructor() {
this.x = { y: 'y’ };
}
work(name) {
let z = 'z';
return function () {
console.log(name, this.x.y, z);
this.x = null;
}.bind(this);
}
};
13

14.

window.Foo = class Foo {
constructor() {
this.x = { y: 'y’ };
}
work(name) {
let z = 'z';
return function () {
console.log(name, this.x.y, z);
this.x = null;
}.bind(this);
}
};
14

15.

window.Foo = class Foo {
constructor() {
this.x = { y: 'y’ };
}
work(name) {
let z = 'z';
return function () {
console.log(name, this.x.y, z);
this.x = null;
}.bind(this);
}
var foo = new Foo();
};
window.worker = foo.work('Brendan Eich');
window.foo = null;
window.Foo = null;
window.worker();
window.worker = null;
15

16.

window.Foo = class Foo {
constructor() {
this.x = { y: 'y’ };
}
work(name) {
let z = 'z';
return function () {
console.log(name, this.x.y, z);
this.x = null;
}.bind(this);
}
var foo = new Foo();
};
window.worker = foo.work('Brendan Eich');
window.foo = null;
window.Foo = null;
window.worker();
window.worker = null;
16

17.

window.Foo = class Foo {
constructor() {
this.x = { y: 'y’ };
}
work(name) {
let z = 'z';
return function () {
console.log(name, this.x.y, z);
this.x = null;
}.bind(this);
}
var foo = new Foo();
};
window.worker = foo.work('Brendan Eich');
window.foo = null;
window.Foo = null;
window.worker();
window.worker = null;
17

18.

window.Foo = class Foo {
constructor() {
this.x = { y: 'y’ };
}
work(name) {
let z = 'z';
return function () {
console.log(name, this.x.y, z);
this.x = null;
}.bind(this);
}
var foo = new Foo();
};
window.worker = foo.work('Brendan Eich');
window.foo = null;
window.Foo = null;
window.worker();
window.worker = null;
18

19.

window.Foo = class Foo {
constructor() {
this.x = { y: 'y’ };
}
work(name) {
let z = 'z';
return function () {
console.log(name, this.x.y, z);
this.x = null;
}.bind(this);
}
var foo = new Foo();
};
window.worker = foo.work('Brendan Eich');
window.foo = null;
window.Foo = null;
window.worker();
window.worker = null;
19

20.

window.Foo = class Foo {
constructor() {
this.x = { y: 'y’ };
}
work(name) {
let z = 'z';
return function () {
console.log(name, this.x.y, z);
this.x = null;
}.bind(this);
}
var foo = new Foo();
};
window.worker = foo.work('Brendan Eich');
window.foo = null;
window.Foo = null;
window.worker();
window.worker = null;
20

21.

window.Foo = class Foo {
constructor() {
this.x = { y: 'y’ };
}
work(name) {
let z = 'z';
return function () {
console.log(name, this.x.y, z);
this.x = null;
}.bind(this);
}
var foo = new Foo();
};
window.worker = foo.work('Brendan Eich');
window.foo = null;
window.Foo = null;
window.worker();
window.worker = null;
21

22.

window.Foo = class Foo {
constructor() {
this.x = { y: 'y’ };
}
work(name) {
let z = 'z';
return function () {
console.log(name, this.x.y, z);
this.x = null;
}.bind(this);
}
var foo = new Foo();
};
window.worker = foo.work('Brendan Eich');
window.foo = null;
window.Foo = null;
window.worker();
window.worker = null;
22

23.

24.

class Foo

25.

class Foo
25

26.

class Foo {
constructor()
}
26

27.

class Foo {
constructor()
}
27

28.

class Foo {
constructor()
work()
}
28

29.

class Foo {
constructor()
work()
}
29

30.

30

31.

window.Foo = Foo
31

32.

foo = new Foo()
32

33.

foo = new Foo()
33

34.

worker = foo.work()
34

35.

fn(){}.bind()
35

36.

замыкание
36

37.

замыкание
37

38.

38

39.

window.foo = null
39

40.

window.foo = null
40

41.

41

42. Типичная ошибка: забытая подписка

externalElement.addEventListener('click', () => {
if (this.shouldDoSomethingOnClick) {
this.doSomething();
}
})
› Отписывайтесь
› Продумайте время жизни подписки и кто ей владеет
› Зануляйте ссылки (whatever = null)
› Используйте WeakMap
42

43.

43

44.

window.Foo = null
44

45.

window.Foo = null
45

46.

46

47. Типичная ошибка

destroy() {
this._x = null;
this._y = null;
// еще 10 this._foobar = null
}
› Если объект состоит из ссылок на другие объекты, то никакой
destroy() вам, скорее всего, не нужен
› Зануляйте когда надо, а когда не надо – не зануляйте
47

48. Типичная ошибка бесполезная работа

destroy() {
this._x = null;
this._y = null;
// еще 10 this._foobar = null
}
› Если объект состоит из ссылок на другие объекты, то никакой
destroy() вам, скорее всего, не нужен
› Зануляйте когда надо, а когда не надо – не зануляйте
48

49.

49

50.

window.worker()
// this.x = null
50

51.

window.worker()
// this.x = null
51

52.

window.worker = null
52

53. Мусор, который не собирается

▌ Объекты с парными функциями create/delete:
› createObjectURL(), revokeObjectURL()
› WebGL: create/delete Program/Shader/Buffer/Texture/etc
› ImageBitmap.close()
› indexDb.close()
› …
53

54.

window.worker = null
54

55.

55

56.

56

57.

57

58.

window.worker = null
58

59.

window.worker = null
59

60.

60

61.

61

62.

62

63.

63

64.

64

65. Что значит мусор?

› Мусор – всё, что не является живым объектом
65

66. Что значит живой объект?

› Живой объект – такой объект, до которого можно дойти по
ссылкам от корневого объекта
66

67. Что значит дойти по ссылкам?

67

68. Что значит дойти по ссылкам?

68

69. Что значит дойти по ссылкам?

69

70. Что значит дойти по ссылкам?

70

71. Что значит дойти по ссылкам?

71

72. Что значит дойти по ссылкам?

72

73. Что значит дойти по ссылкам?

73

74. Что значит дойти по ссылкам?

74

75. Что значит дойти по ссылкам?

75

76. Что значит дойти по ссылкам?

76

77. Что значит дойти по ссылкам?

77

78. Что значит дойти по ссылкам?

78

79. Что значит корневой объект?

79

80. Что значит корневой объект?

DOM
Microtask
Macrotask
RAF
Idle
80

81. Что значит корневой объект?

function foo (a, b, c) {
function bar (x, y, z) {
const x = {}; // nomem, run gc D:
// ...
DOM
Microtask
Macrotask
RAF
Idle
}
while (whatever()) bar();
}
81

82. Что значит корневой объект?

function foo (a, b, c) {
function bar (x, y, z) {
const x = {}; // nomem, run gc D:
// ...
DOM
Microtask
Macrotask
RAF
Idle
}
while (whatever()) bar();
}
82

83. Суровая реальность

84.

85.

86.

new Uint32Array(16 * 2 ** 30);

87.

87

88.

88

89.

89

90.

90

91.

91

92.

92

93.

93

94.

94

95.

95

96.

97. No-op collector

▌ Плюсы
› Очень простой
› У вас просто нет сборки мусора
▌ Минусы
› Ваше приложение падает
97

98.

98

99.

99

100.

100

101. Mark and Sweep

▌ Плюсы
› Простой
› Работает пропорционально количеству мусора
› Хорошо работает, когда у вас мало мусора
▌ Минусы
› Требует сложной логики поиска свободного места
› Фрагментирует память
101

102.

102

103.

103

104.

104

105. Mark and Compact

▌ Плюсы
› Дефрагментирует память
▌ Минусы
› Сложный
› Перемещает объекты
› Работает пропорционально количеству живых объектов
› Требует 2-3 прохода по всей памяти
› Медленный
105

106. Сборка мусора не бесплатна

▌ WebGL / WebAudio / WebGPU
› Инициализация объектов
› pull() вместо Promise
106

107.

107

108.

108

109.

109

110.

110

111.

111

112.

112

113.

113

114. Semispace aka Lisp 2

▌ Плюсы
› Дефрагментирует память
› Простой
› Можно совместить с фазой обхода
› Работает пропорционально количеству живых объектов
› Хорошо работает, когда у вас много мусора
▌ Минусы
› Двойной расход памяти
› Перемещает объекты
114

115. На заметку

› Сборщики мусора могут перемещать объекты
› Расширения используют двойные ссылки aka «хендлы»
› Например, v8::Local<v8::String>
115

116. Разные алгоритмы

Mark and
Compact
Сложность
Сборка
Создание
Фрагментация
Минус
Mark and
Sweep
Semispace
Eden
~ все
~ мусор
~ живые
~ живые
Мееедленно
Медленно
Быстро
Быстро
Быстро
Медленно
Быстро
Быстро
нет
есть
нет
нет
Сложная
реализация
Сложная
аллокация
Память x2
Сбрасывается
116

117. Разные алгоритмы

Mark and
Compact
Сложность
Сборка
Создание
Фрагментация
Минус
Mark and
Sweep
Semispace
Eden
~ все
~ мусор
~ живые
~ живые
Мееедленно
Быстро
Быстро
Быстро
Быстро
Медленно
Быстро
Быстро
нет
есть
нет
нет
Сложная
реализация
Сложная
аллокация
Память x2
Сбрасывается
117

118. Лучшее из всех миров

› Можно использовать несколько алгоритмов
› Но как?
118

119. Гипотеза поколений?

│ Большинство объектов
умирают молодыми
Слабая гипотеза о поколениях
119

120. Сборка поколениями

121.

121

122.

122

123.

123

124.

124

125.

125

126.

126

127.

127

128.

128

129.

129

130.

130

131. Ошибка: обман сборщика мусора

› Не создавайте долгоживущий мусор
› Сборщик начнет считать, что его не надо собирать
› Типичный пример обмана: LRU-cache
131

132. Когда собирать?

133. Stop the World сборщик

133

134. Parallel сборщик

134

135. Concurrent сборщик

135

136. Что почитать

wikipedia.org/Garbage_collection
memorymanagement.org
shipilev.net
136

137. Браузерная реальность

(ну, и не только браузерная)
Браузерная реальность

138. IoT-движки

› Mark’n’sweep
› Stop the world
› …
› На микроконтроллерах
138

139. IoT-движки

› Медленный язык
› Секундные фризы
› Фрагментация
› Теперь и в вашем чайнике
139

140. Вот эти ребята

140

141. В основном, конечно, вот этот

› Почти весь серверный JavaScript
› ~80% клиентского JavaScript’а
› Больше всего информации
› Проще всего читать исходники
141

142. Сборка поколениями

Eden
Semispace
Mark and sweep
142

143.

143

144.

Major GC
Major GC
144

145. Ошибка: боязнь создания мусора

› Мусор можно создавать, если это действительно мусор
› Переиспользование объектов хуже, чем мусор
145

146. Параллельный mark

146

147. Сколько стоит все это удовольствие?

│ 1-3%
147

148. Сколько стоит все это удовольствие?

│ 1/100 – 1/33
148

149. Это много

149

150. Это много (для геймдева)

150

151. Это много (для геймдева)

const pool = [new Bullet(), new Bullet(), /* ... */];
function getFromPool() {
const bullet = pool.find(x => !x.inUse);
bullet.isUse = true;
return bullet;
}
function returnToPool(bullet) { bullet.inUse = false; }
// Frame
const bullet = getFromPool();
// ...
returnToPool(bullet);
151

152. Это много (для геймдева)

const pool = [new Bullet(), new Bullet(), /* ... */];
function getFromPool() {
const bullet = pool.find(x => !x.inUse);
bullet.isUse = true;
return bullet;
}
function returnToPool(bullet) { bullet.inUse = false; }
// Frame
const bullet = getFromPool();
// ...
returnToPool(bullet);
152

153. Статистика сборщика мусора: Chromium

> performance.memory
MemoryInfo {
totalJSHeapSize: 10000000,
usedJSHeapSize: 10000000,
jsHeapSizeLimit: 2330000000
}
153

154. Статистика сборщика мусора: Node

> process.memoryUsage()
{ rss: 22839296,
heapTotal: 10207232,
heapUsed: 5967968,
external: 12829 }
154

155. Будущее: WeakRef

let cached = new WeakRef(myJson);
Ссылки, которые могут быть собраны
в случае нехватки памяти
github.com/tc39/proposal-weakrefs
npmjs.com/package/weak
// 2 часа спустя
let json = cached.deref();
if (!json) {
json = await fetchAgain();
}
155

156. Будущее: GC в WebAssembly

https://github.com/WebAssembly/gc
156

157. Что почитать

› v8.dev
▌ Исходники
› github.com/v8/v8/tree/7.0.237/src/heap
› github.com/servo/mozjs/blob/master/mozjs/js/src/gc/
› github.com/WebKit/webkit/…/JavaScriptCore/heap/MarkedSpace.cpp
› github.com/Microsoft/ChakraCore/…/HeapAllocator.cpp
› github.com/svaarala/duktape/…/duk_heap_markandsweep.c
› github.com/jerryscript-project/jerryscript/…/ecma-gc.c
157

158. Повседневность

159.

159

160.

160

161.

161

162.

162

163.

163

164.

164

165.

165

166.

166

167.

167

168.

React
168

169.

169

170.

170

171.

171

172.

172

173.

173

174.

React
174

175. Если этого вдруг не хватило

› Chromium: about:tracing
› Firefox: about:memory, about:performance
› Node: --trace-gc, --expose-gc, require('trace_events')
175

176. Итого

177. Итого

› Сборщик мусора умный
› Никто вам не мешает выстрелить себе в ногу
› Не бойтесь создавать мусор
› Следите за перформансом
› Если у вас не SPA, то вы можете забить и ничего не делать
› Большая часть ошибок и сомнительных мест растет из незнания
того, как работает ваш инструмент
› Вы теперь знаете
177

178. Команда JavaScript API Яндекс.Карт ищет разработчиков

› Возможность поучаствовать в работе над одним из крупнейших
API рунета
› Нестандартные задачи для фронтенда
› Отсутствие верстки
› yandex.ru/jobs/vacancies/dev/intdev_api_maps
› yandex.ru/jobs/vacancies/dev/dev_graphics_api_maps
178

179.

Слайды: flapenguin.me/talks/gc
Вопросы?
Роенко Андрей
flapenguin@yandex.ru
flapenguin
@flapenguin
flapenguin.me
@fla_penguin
179
English     Русский Rules