-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmemory_optimizer.py
More file actions
483 lines (399 loc) · 19.8 KB
/
Copy pathmemory_optimizer.py
File metadata and controls
483 lines (399 loc) · 19.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
"""
MemoryOptimizer - класс для управления памятью и оптимизации потребления ресурсов
SECURITY FIXES:
- Added pre-execution memory checking
- Implemented RealTimeMemoryMonitor for runtime monitoring
- Auto-trigger gc.collect() near memory limits
- Added detailed memory statistics
VULNERABILITY FIXES (v2) - ROBUST MEMORY API:
- RealTimeMemoryMonitor теперь имеет полный API: check_before_execution(),
check_during_execution(), check_after_execution().
- Все чтения gc.mem_free()/gc.mem_alloc() обёрнуты защитой: если память сильно
фрагментирована и вызов падает/возвращает некорректное значение, монитор НЕ
роняет выполнение, а возвращает безопасное значение (0 / None).
- gc.collect() вызывается перед каждым измерением для уплотнения кучи.
"""
import gc
import sys
import time
# Проверка доступности MicroPython-специфичных функций gc
_HAS_MEM_FREE = hasattr(gc, 'mem_free')
_HAS_MEM_ALLOC = hasattr(gc, 'mem_alloc')
def _get_free_memory():
"""
Безопасное получение свободной памяти.
Возвращает int (число свободных байт) или None, если gc.mem_free()
недоступен (CPython) или вызов упал из-за фрагментации/повреждения кучи.
Никогда не выбрасывает исключение.
"""
if not _HAS_MEM_FREE:
return None
try:
val = gc.mem_free()
# На сильно фрагментированной куче отдельные порты MicroPython могут
# вернуть отрицательное/None значение — считаем это «недоступно».
if val is None or val < 0:
return None
return int(val)
except Exception:
return None
def _get_allocated_memory():
"""
Безопасное получение выделенной памяти.
Возвращает int или None. Никогда не выбрасывает исключение.
"""
if not _HAS_MEM_ALLOC:
return None
try:
val = gc.mem_alloc()
if val is None or val < 0:
return None
return int(val)
except Exception:
return None
class RealTimeMemoryMonitor:
"""
Мониторинг памяти в реальном времени с проверкой до, во время и после выполнения.
API:
- check_before_execution(estimated_bytes): gc.collect() + проверка, что
свободной памяти достаточно для запуска (иначе MemoryError).
- check_during_execution(): быстрая проверка «ещё не критично?»;
возвращает свободные байты или 0 (никогда не падает).
- check_after_execution(): фиксирует дельту памяти и при сильной утечке
запускает gc.collect(); возвращает словарь со статистикой.
Все методы устойчивы к фрагментации: если gc.mem_free() вернул None,
монитор деградирует безопасно (считает, что «данных нет»), но не роняет
выполняемый код.
"""
def __init__(self, hard_limit_bytes, warning_threshold=0.8):
# hard_limit — минимальный резерв свободной памяти, который мы пытаемся
# удерживать (НЕ верхний предел выделенной памяти).
self.hard_limit = hard_limit_bytes
self.warning_threshold = warning_threshold
# Базовый уровень свободной памяти (для оценки утечек).
# Если gc.mem_free недоступен — используем безопасный fallback.
self._baseline_free = _get_free_memory()
if self._baseline_free is None:
self._baseline_free = hard_limit_bytes
self._memory_checks = []
def _record_check(self, kind, extra=None):
"""Внутренний: запись точки замера памяти в историю."""
entry = {
'type': kind,
'free': _get_free_memory(),
'allocated': _get_allocated_memory(),
'timestamp': time.ticks_ms() if hasattr(time, 'ticks_ms') else 0,
}
if extra:
entry.update(extra)
self._memory_checks.append(entry)
# Ограничиваем длину истории, чтобы не разрасталась в RAM.
if len(self._memory_checks) > 64:
del self._memory_checks[0]
def check_before_execution(self, estimated_bytes):
"""
Проверка памяти ДО выполнения кода.
Args:
estimated_bytes: Оценка требуемой памяти.
Raises:
MemoryError: Если свободной памяти меньше, чем estimated_bytes,
или она ниже критического порога hard_limit.
"""
gc.collect() # уплотняем кучу перед замером
free = _get_free_memory()
if free is None:
# gc.mem_free() недоступен (CPython) — пропускаем жёсткую проверку,
# но фиксируем факт вызова для истории.
self._record_check('before', {'estimated': estimated_bytes, 'unavailable': True})
return
# Жёсткая проверка: свободной памяти должно хватить под оценку + резерв.
if free < estimated_bytes:
self._record_check('before', {'estimated': estimated_bytes, 'denied': True})
raise MemoryError(
"Insufficient memory: need %d, have %d bytes free" % (estimated_bytes, free)
)
# Мягкая проверка: не приближаемся ли к опасному порогу.
if free < self.hard_limit * (1 - self.warning_threshold):
gc.collect() # последняя попытка освободить память
self._record_check('before', {'estimated': estimated_bytes})
def check_during_execution(self):
"""
Проверка памяти ВО ВРЕМЯ выполнения (вызывается из цикла движка).
Returns:
Текущее количество свободной памяти (int). Если gc.mem_free()
недоступен — возвращает 0, но НЕ выбрасывает исключение.
Raises:
MemoryError: Если свободная память упала ниже критического порога
(10% от hard_limit).
"""
free = _get_free_memory()
if free is None:
# Данных о памяти нет — не блокируем выполнение.
return 0
if free < self.hard_limit * 0.1: # критический уровень
self._record_check('during', {'critical': True})
raise MemoryError("Critical memory level reached")
# Не пишем в историю каждый вызов (цикл зовёт это часто) — только точку.
return free
def check_after_execution(self, baseline_free=None):
"""
Проверка памяти ПОСЛЕ выполнения кода.
Сравнивает текущий свободный объём с базовым уровнем (перед запуском
или с baseline_free, если передан). Если память заметно «утекла»,
запускает gc.collect() и повторно замеряет.
Args:
baseline_free: Опциональный базовый уровень свободной памяти
(например, замер до выполнения). Если None,
используется self._baseline_free.
Returns:
Словарь со статистикой: free, allocated, delta, gc_triggered.
"""
before = baseline_free if baseline_free is not None else self._baseline_free
gc.collect()
free = _get_free_memory()
allocated = _get_allocated_memory()
delta = None
gc_triggered = False
if free is not None and before is not None:
delta = free - before
# Если свободная память заметно просела (> 20% от лимита) —
# сборка мусора уже выполнена выше; помечаем это.
if delta < -(self.hard_limit * 0.2):
gc_triggered = True
# Повторный замер после gc.collect()
free = _get_free_memory()
allocated = _get_allocated_memory()
delta = free - before if (free is not None) else None
self._record_check('after', {'delta': delta, 'gc_triggered': gc_triggered})
return {
'free': free if free is not None else 0,
'allocated': allocated if allocated is not None else 0,
'delta': delta,
'gc_triggered': gc_triggered,
}
def get_memory_stats(self):
"""
Детальная статистика использования памяти.
Returns:
Словарь со статистикой памяти.
"""
gc.collect()
free = _get_free_memory()
allocated = _get_allocated_memory()
return {
'free': free if free is not None else 0,
'allocated': allocated if allocated is not None else 0,
'total': (free or 0) + (allocated or 0),
'baseline_free': self._baseline_free,
'check_count': len(self._memory_checks)
}
def get_memory_history(self):
"""
История проверок памяти.
Returns:
Список проверок памяти.
"""
return self._memory_checks
class MemoryOptimizer:
"""
Класс для управления памятью и оптимизации потребления ресурсов
"""
def __init__(self, max_memory_kb=50):
self.max_memory_bytes = max_memory_kb * 1024
self.buffers = {}
self.buffer_sizes = {}
self.monitoring_enabled = True
self.memory_log = []
self.gc_threshold = 1024 # Выполнять GC каждые N байт
self.memory_monitor = RealTimeMemoryMonitor(self.max_memory_bytes)
def preallocate_buffers(self, buffer_configs):
"""
Предварительное выделение буферов заданных размеров
buffer_configs: dict с парами {name: size_in_bytes}
"""
for name, size in buffer_configs.items():
# Создаем буфер нужного размера
buffer = bytearray(size)
self.buffers[name] = buffer
self.buffer_sizes[name] = size
return self.buffers
def monitor_memory_usage(self):
"""
Мониторинг использования памяти
"""
if not self.monitoring_enabled:
return {'enabled': False}
# В MicroPython нет точного способа измерения использования памяти
# поэтому используем косвенные методы
gc.collect() # Собираем мусор перед измерением
# Логируем текущее состояние
log_entry = {
'timestamp': self._get_timestamp(),
'buffers_count': len(self.buffers),
'total_buffer_size': sum(self.buffer_sizes.values()),
'active_buffers': list(self.buffers.keys())
}
self.memory_log.append(log_entry)
# Ограничиваем размер лога
if len(self.memory_log) > 100:
self.memory_log = self.memory_log[-50:] # Сохраняем последние 50 записей
return log_entry
def cleanup_cache(self, cache_objects=None):
"""
Очистка кэша
"""
cleaned_count = 0
if cache_objects is None:
# Очищаем внутренние кэши
caches_to_clean = [
'buffers', # Хотя буферы не всегда нужно очищать
]
for cache_name in caches_to_clean:
cache = getattr(self, cache_name, {})
if isinstance(cache, dict):
cache.clear()
cleaned_count += 1
else:
# Очищаем указанные кэши
for cache in cache_objects:
if hasattr(cache, 'clear'):
cache.clear()
cleaned_count += 1
# Выполняем сборку мусора
collected = gc.collect()
return {
'cleaned_caches': cleaned_count,
'garbage_collected': collected
}
def optimize_bytearray_usage(self, data_list):
"""
Оптимизация использования bytearray
"""
optimized_list = []
for item in data_list:
if isinstance(item, str):
# Преобразуем строки в bytearray если возможно
ba = bytearray(item, 'utf-8')
optimized_list.append(ba)
elif isinstance(item, list):
# Рекурсивно обрабатываем вложенные списки
optimized_list.append(self.optimize_bytearray_usage(item))
else:
optimized_list.append(item)
return optimized_list
def get_memory_stats(self):
"""
Получение статистики памяти
Returns:
Словарь со статистикой памяти
"""
gc.collect() # Собираем мусор для актуальных данных
stats = {
'max_allowed_bytes': self.max_memory_bytes,
'buffer_memory_used': sum(self.buffer_sizes.values()),
'buffers_count': len(self.buffers),
'log_entries_count': len(self.memory_log),
'monitoring_enabled': self.monitoring_enabled
}
# Добавляем детальную статистику из RealTimeMemoryMonitor
stats.update(self.memory_monitor.get_memory_stats())
return stats
def check_before_execution(self, estimated_bytes):
"""
Проверка памяти перед выполнением кода
Args:
estimated_bytes: Оценка требуемой памяти
Raises:
MemoryError: Если недостаточно памяти
"""
self.memory_monitor.check_before_execution(estimated_bytes)
def check_during_execution(self):
"""
Проверка памяти во время выполнения кода
Returns:
Текущее количество свободной памяти
Raises:
MemoryError: Если критический уровень памяти
"""
return self.memory_monitor.check_during_execution()
def check_after_execution(self, baseline_free=None):
"""
Проверка памяти ПОСЛЕ выполнения кода.
Делегирует в RealTimeMemoryMonitor. Возвращает словарь со статистикой
(free, allocated, delta, gc_triggered) и не выбрасывает исключений.
"""
return self.memory_monitor.check_after_execution(baseline_free)
def check_memory_pressure(self):
"""
Проверка давления на память
"""
stats = self.get_memory_stats()
buffer_memory = stats['buffer_memory_used']
# Оцениваем давление на память
pressure_level = buffer_memory / self.max_memory_bytes
return {
'pressure_ratio': pressure_level,
'is_critical': pressure_level > 0.9, # Критический уровень при 90% использовании
'available_bytes': self.max_memory_bytes - buffer_memory
}
def compact_memory(self):
"""
Компактификация памяти (в упрощенной форме)
"""
# В MicroPython нет встроенной компактификации памяти
# Поэтому просто выполняем сборку мусора
collected = gc.collect()
# Также очищаем неиспользуемые буферы
active_buffers = {}
for name, buffer in self.buffers.items():
# В реальной системе здесь была бы проверка на использование буфера
active_buffers[name] = buffer
self.buffers = active_buffers
return {
'garbage_collected': collected,
'buffers_compacted': len(self.buffers)
}
def _get_timestamp(self):
"""
Получение временной метки
"""
try:
import time
return time.ticks_ms()
except:
return 0 # Заглушка если модуль time недоступен
def set_monitoring(self, enabled=True):
"""
Включение/отключение мониторинга
"""
self.monitoring_enabled = enabled
def get_recent_logs(self, count=10):
"""
Получение последних записей лога
"""
return self.memory_log[-count:] if self.memory_log else []
def optimize_list_storage(self, lst):
"""
Оптимизация хранения списков
"""
# Попробуем использовать tuple вместо list где это возможно
# для экономии памяти
if isinstance(lst, list):
optimized = []
for item in lst:
if isinstance(item, list):
optimized.append(self.optimize_list_storage(item))
else:
optimized.append(item)
return optimized
return lst
def release_buffer(self, buffer_name):
"""
Освобождение конкретного буфера
"""
if buffer_name in self.buffers:
del self.buffers[buffer_name]
if buffer_name in self.buffer_sizes:
del self.buffer_sizes[buffer_name]
return True
return False