Афонасьев Евгений
typedef struct _object {
Py_ssize_t ob_refcnt;
struct _typeobject *ob_type;
} PyObject;
typedef struct {
PyObject ob_base;
double ob_fval;
} PyFloatObject;
typedef struct {
PyObject ob_base;
PyObject *func_code; /* A code object, the __code__ attribute */
PyObject *func_globals; /* A dictionary (other mappings won't do) */
PyObject *func_defaults; /* NULL or a tuple */
PyObject *func_kwdefaults; /* NULL or a dict */
PyObject *func_closure; /* NULL or a tuple of cell objects */
PyObject *func_doc; /* The __doc__ attribute, can be anything */
PyObject *func_name; /* The __name__ attribute, a string object */
PyObject *func_dict; /* The __dict__ attribute, a dict or NULL */
PyObject *func_weakreflist; /* List of weak references */
PyObject *func_module; /* The __module__ attribute, can be anything */
PyObject *func_annotations; /* Annotations, a dict or NULL */
PyObject *func_qualname; /* The qualified name */
} PyFunctionObject;
Все объекты обернуты в PyObject структуру и могут хранить дополнительные метаданные
В python есть несколько схем выделения памяти, которые выбираются в зависимости от объема нужной памяти.
Запрос памяти у OS - относительно медленная операция, особенно если запрашивать много маленьких кусочков вместо меньшего кол-ва больших
блок - это область памяти с фиксированным размером кратным 8
Например: если объекту нужно 13Б, берем блок на 16Б
struct pool_header {
union { block *_padding;
uint count; } ref; /* number of allocated blocks */
block *freeblock; /* pool's free list head */
struct pool_header *nextpool; /* next pool of this size class */
struct pool_header *prevpool; /* previous pool "" */
uint arenaindex; /* index into arenas of base adr */
uint szidx; /* block size class index */
uint nextoffset; /* bytes to virgin block */
uint maxnextoffset; /* largest valid nextoffset */
};
struct arena_object {
uintptr_t address;
block* pool_address;
uint nfreepools;
uint ntotalpools;
struct pool_header* freepools;
struct arena_object* nextarena;
struct arena_object* prevarena;
};
class size num pools blocks in use avail blocks
----- ---- --------- ------------- ------------
0 8 2 801 211
1 16 2 394 112
2 24 4 509 163
3 32 48 6024 24
4 40 99 9944 55
5 48 69 5686 110
...
# arenas allocated total = 170
...
76 arenas * 262144 bytes/arena = 19,922,944
...
Total = 19,922,944
14 free PyCFunctionObjects * 48 bytes each = 672
72 free PyDictObjects * 48 bytes each = 3,456
3 free PyFloatObjects * 24 bytes each = 72
8 free PyFrameObjects * 368 bytes each = 2,944
...
In [1]: import sys
In [2]: sys.getsizeof([])
Out[2]: 64
In [3]: sys.getsizeof([1,2,3])
Out[3]: 88
In [4]: sys.getsizeof({})
Out[4]: 240
In [15]: class X:
...: pass
...:
...:
In [16]: X.__dict__
Out[16]:
mappingproxy({'__module__': '__main__',
'__dict__': <attribute '__dict__' of 'X' objects>,
'__weakref__': <attribute '__weakref__' of 'X' objects>,
'__doc__': None})
In [17]: X().__dict__
Out[17]: {}
В памяти питона словари повсюду!
In [1]: class X:
...: __slots__ = ('a', )
...: def __init__(self, a):
...: self.a = a
...:
...:
In [2]: X(1).__dict__
...
AttributeError: 'X' object has no attribute '__dict__'
Подсчёт ссылок также известен как один из алгоритмов сборки мусора, где каждый объект содержит счётчик количества ссылок на него, используемых другими объектами. Когда этот счётчик уменьшается до нуля, это означает, что объект стал недоступным, и он помещается в список объектов на уничтожение.
typedef struct _object {
Py_ssize_t ob_refcnt; # счетчик ссылок
struct _typeobject *ob_type;
} PyObject;
foo = []
# 2 references, 1 from the foo var and 1 from getrefcount
print(sys.getrefcount(foo))
def bar(a):
# 4 references
# from the foo var, function argument,
# getrefcount and Python's function stack
print(sys.getrefcount(a))
bar(foo)
# 2 references, the function scope is destroyed
print(sys.getrefcount(foo))
object_1 = {}
object_2 = {}
object_1['obj2'] = object_2
object_2['obj1'] = object_1
del object_1, object_2
Специальный процесс, называемый сборщиком мусора (англ. garbage collector), периодически освобождает память, удаляя объекты, которые уже не будут востребованы приложениями.
После создания любого контейнерного объекта проверяется, достиг ли счетчик некоторого порогового значения (700, 10, 10 по умолчанию), и запускается сборка для соответствующего поколения
но в очень редких случаях
gc.get_threshold
gc.set_threshold
Обходит граф объектов в памяти и помечает все объекты, которые могут быть достигнуты от некоторого узла, остальные считает недостижимыми и удаляет
In [1]: class X:
...: def __del__(self):
...: print('del X')
...:
In [2]: x = X()
In [3]: del x
del X
Может быть вызван при сборке мусора, но на него не стоит полагаться как на надежный механизм закрытия ресурсов. Лучше использовать контекстные менеджеры.
In [10]: x = X()
In [11]: ref = weakref.ref(x)
In [12]: print(ref())
<__main__.X object at 0x102bdd668>
In [13]: del x
del X
In [14]: print(ref())
None
import gc
gc.set_debug(gc.DEBUG_SAVEALL)
print(gc.get_count())
lst = []
lst.append(lst)
list_id = id(lst)
del lst
gc.collect()
for item in gc.garbage:
print(item)
assert list_id == id(item)
Line # Mem usage Increment Line Contents
==============================================
3 @profile
4 5.97 MB 0.00 MB def my_func():
5 13.61 MB 7.64 MB a = [1] * (10 ** 6)
6 166.20 MB 152.59 MB b = [2] * (2 * 10 ** 7)
7 13.61 MB -152.59 MB del b
8 13.61 MB 0.00 MB return a
Для асинхронных запросов тоже важно знать время их обработки
У каждого он свой. Если ваша система перестает показывать удовлетворительные показатели при достижении некоторого объема нагрузки, считайте, что у вас уже маленький highload =)
Бутылочное горлышко. Конкретная часть системы, которая не справляется с нагрузкой, и тормозит все остальное. Любые оптимизации должны начинаться именно с поиска bottleneck.
Способность системы продолжать корректную работу при отказе части компонентов. Обычно достигается множественным резервированием рабочих систем (failover).
https://ru.wikipedia.org/wiki/Соглашение_об_уровне_услуг
Формальный договор между заказчиком услуги и её поставщиком, содержащий описание услуги, права и обязанности сторон и, самое главное, согласованный уровень качества предоставления данной услуги.
Например мы можем сказать, что наш сервис отдаст ответ за 200мс в 99% случаев, и что он будет доступен 99.9% времени.
Способность системы продолжать работать при полном или частичном отказе части системы, пусть даже с уменьшением показателей.
Например, мы можем просто не показывать рекламу, если упал сервис рекламы, вместо полной недоступности системы.
Подвид тестирования производительности, сбор показателей и определение производительности и времени отклика программно-технической системы или устройства в ответ на внешний запрос с целью установления соответствия требованиям, предъявляемым к данной системе
ab -n 2000 -c 200 https://service/api/users
Шлем 2000 запросов в 200 параллельных соединений
Concurrency Level: 200
Time taken for tests: 35.902 seconds
Complete requests: 2000
Failed requests: 7
(Connect: 0, Receive: 0, Length: 7, Exceptions: 0)
Non-2xx responses: 1994
Total transferred: 642068 bytes
HTML transferred: 327016 bytes
Requests per second: 55.71 [#/sec] (mean)
Time per request: 3590.243 [ms] (mean)
Time per request: 17.951 [ms] (mean, across all concurrent requests)
Transfer rate: 17.46 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 1412 1519.1 1134 21320
Processing: 53 448 721.2 294 9093
Waiting: 0 389 615.5 226 9093
Total: 217 1861 1700.0 1498 21508
Percentage of the requests served within a certain time (ms)
50% 1498
66% 1928
75% 2328
80% 2659
90% 3743
95% 5223
98% 6842
99% 8987
100% 21508 (longest request)
Те самые перцентили для SLA
Выключаем те или иные компоненты системы (реплики бд, брокеры сообщений, контейнеры с сервисами, сеть между сервисами etc.) и следим, как изменятся наши показатели, останется ли система работоспособной.
Performance +
Performance +
High availability +
Закрепляем за нашим доменом несколько хостов (по их ip адресам).
Но DNS кэши медленно обновляются и нельзя оперативно добавлять или убирать хосты в случае обновлений/аварий.
Выставляем наружу промежуточный сервер, который уже проксирует запросы в наши приложения.
Оптимальный вариант.
Может быть несколько уровней промежуточных балансеров (haproxy).
В DNS прописываем адрес нашего балансера.
High availability +
Performance +
High availability + (можем читать из кэша, пока сервис недоступен)
Performance + (можем читать с реплик)
High availability + (failover)
Репликация — это процесс, под которым понимается копирование данных из одного источника на другой (или на множество других)
+ Увеличиваем возможность обрабатывать запросы на чтение
+ Отказоустойчивость (можно переключить запись на реплику, если мастер упал)
- (async replication) Eventual consistency - изменения доходят до реплик с задержкой (для критично важных запросов читаем из мастера)
- (sync replication) уменьшится скорость выполнения запросов на запись
Долго выполняются запросы или данные не помещаются на диск?
Принцип проектирования базы данных, при котором логически независимые строки таблицы базы данных хранятся раздельно
Performance + (данных меньше на конкретном шарде)
Например данные разных клиентов в разных базах, логи за разные периоды времени и тд - это разделение по условию. Под шардированием подразумевают такой способ.
Хранить сообщения чата в одной базе, аватарки пользователей в другой - это разделение по типу (типично для микросервисов)
High availability + (каждый шард продублирован)
Прекрасно живут вместе!
High availability + (размазываем нагрузку)
Складываем запросы в промежуточную очередь и обрабатываем отдельными процессами с предсказуемой нагрузкой на базу