Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
182 changes: 167 additions & 15 deletions Урок 6. Практическое задание/task_1.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,167 @@
"""
Задание 1.
Выполните профилирование памяти в скриптах
Проанализировать результат и определить программы с
наиболее эффективным использованием памяти.

Примечание: Для анализа возьмите любые 1-3 ваших программы или несколько
вариантов кода для одной и той же задачи. Можно взять задачи с курса Основ или с текущего курса Алгоритмов

Результаты анализа вставьте в виде комментариев к коду.
Также укажите в комментариях версию Python и разрядность вашей ОС.

ВНИМАНИЕ: ЗАДАНИЯ, В КОТОРЫХ БУДУТ ГОЛЫЕ ЦИФРЫ ЗАМЕРОВ (БЕЗ АНАЛИТИКИ)
БУДУТ ПРИНИМАТЬСЯ С ОЦЕНКОЙ УДОВЛЕТВОРИТЕЛЬНО
"""
"""
Задание 1.

Выполните профилирование памяти в скриптах
Проанализировать результат и определить программы с
наиболее эффективным использованием памяти.

Примечание: Для анализа возьмите любые 3-5 ваших РАЗНЫХ скриптов!
(хотя бы 3 разных для получения оценки отл).
На каждый скрипт вы должны сделать как минимум по две реализации.

Можно взять задачи с курса Основ
или с текущего курса Алгоритмов

Результаты профилирования добавьте в виде комментариев к коду.
Обязательно сделайте аналитику (что с памятью в ваших скриптах, в чем ваша оптимизация и т.д.)

ВНИМАНИЕ: ЗАДАНИЯ, В КОТОРЫХ БУДУТ ГОЛЫЕ ЦИФРЫ ЗАМЕРОВ (БЕЗ АНАЛИТИКИ)
БУДУТ ПРИНИМАТЬСЯ С ОЦЕНКОЙ УДОВЛЕТВОРИТЕЛЬНО

Попытайтесь дополнительно свой декоратор используя ф-цию memory_usage из memory_profiler
С одновременным замером времени (timeit.default_timer())!
"""
from random import randint, random
from timememit import timememit
from pympler.asizeof import asizeof
from recordclass import recordclass
from datetime import datetime, timedelta
import numpy as np
import json
import msgpack
import sys


args = iter(sys.argv)
next(args)
ARG1 = next(args, None)
NOW = datetime.now()
LETRANGE = (ord('A'), ord('Z'))

# Будем применять способы оптимизации памяти, рассмотренные на уроке

# 1. Ленивые вычисления
# пример в файле task_3a.py

# 2. Слоты в ООП
# 3. numpy (также пример в файле task_2.py)
# 4. recordclass


# Пусть у нас есть список чего-либо, допустим, биржевой торговли
def gen_transaction():
return (
# ticker
''.join(chr(randint(*LETRANGE)) for _ in range(4)),
# number
randint(1, 1000),
# seller_id
randint(1, 10000000),
# buyer_id
randint(1, 10000000),
# price
randint(1, 10000000),
# timestamp
NOW + timedelta(seconds=randint(0, 100000))
)


class Transaction:
def __init__(self, ticker, number, seller, buyer, price, timestamp):
self.ticker = ticker
self.number = number
self.seller = seller
self.byer = buyer
self.price = price
self.timestamp = timestamp


class TransSlots:
__slots__ = ['ticker', 'number', 'seller', 'buyer', 'price', 'timestamp']

def __init__(self, ticker, number, seller, buyer, price, timestamp):
self.ticker = ticker
self.number = number
self.seller = seller
self.buyer = buyer
self.price = price
self.timestamp = timestamp


TransRecord = recordclass(
'TransRecord',
'ticker number seller buyer price timestamp')

trans_dtype = [
('ticks', '<U4'),
('number', '<i4'),
('seller', '<i4'),
('buyer', '<i4'),
('price', '<i4'),
('timestamp', 'datetime64[m]')]

if ARG1 is None:
n = 1000
classes = [Transaction(*gen_transaction()) for i in range(n)]
slots = [TransSlots(*gen_transaction()) for i in range(n)]
records = [TransRecord(*gen_transaction()) for i in range(n)]
nprecords = np.array(
[gen_transaction() for i in range(n)],
dtype=trans_dtype)

print(f'Размер массива из {n} элементов')
print(f' class: {asizeof(classes)}')
print(f' __slots__: {asizeof(slots)}')
print(f'recordclass: {asizeof(records)}')
print(f' numpy: {asizeof(nprecords)}')
# ---
# Размер массива из 1000 элементов
# class: 429648
# __slots__: 318232
# recordclass: 82176
# numpy: 40120
# ---
# Практически невозможно "победить" по потреблению памяти numpy,
# но у этого есть своя цена, массивы numpy фиксированного размера,
# поэтому их применение подходит не для всех алгоритмов.
# recordclass по результатам измерений на втором месте, но это
# дополнительный модуль, компилируемый в бинарный код, что
# накладывает некоторые ограничения на развертывание приложения.
# __slots__ является частью языка и этих ограничений не имеет,
# однако в данном примере не видно большого уменьшения памяти.

# 5. map
# map можно отнести к ленивым вычислениям,
# в python3 map(f, seq) без потерь по производительности
# и памяти можно заменять на (f(x) for x in seq)

# 6. Сериализация
# Продположим у нас есть сервер, к которому подключаются
# пользоатели, для каждого из которых нужно хранить
# много данных.
# Можно разделить пользователей по частоте обращения,
# и данные "заснувших" сессий сериализовать.


# Для примера построим дерево cо строчками на крайних ветках
def gen_tree(deep=5):
if deep == 0:
return str(randint(1, 99))
newdeep = deep - 1
return [gen_tree(newdeep), gen_tree(newdeep)]


if ARG1 == "6":
tree = gen_tree(20)
print(
f' Размер объекта в памяти: {asizeof(tree)}')
print(
f' Размер сериализованных данных (json): {len(json.dumps(tree))}')
print(
f'Размер сериализованных данных (msgpack): '
+ f'{len(msgpack.packb(tree, use_bin_type=True))}')
# ---
# Размер объекта в памяти: 134217656
# Размер сериализованных данных (json): 8293148
# Размер сериализованных данных (msgpack): 4098847
# ---
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

но в целом хорошо

216 changes: 209 additions & 7 deletions Урок 6. Практическое задание/task_2.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,209 @@
"""
Задание 2.
Предложите фундаментальные варианты оптимизации памяти
и доказать (наглядно, кодом, если получится) их эффективность

Например, один из вариантов, использование генераторов
"""
"""
Задание 2.*
Предложить еще какие-либо варианты (механизмы, подходы, библиотеки, идеи)
для оптимизации памяти и
доказать!!! (наглядно, кодом) их эффективность (на примере профилировщика)
"""
from timememit import timememit
from numpy import arange, log, sin, cos, sqrt
from array import array
import math
import sys

args = iter(sys.argv)
next(args)
ARG1 = next(args, None)


# Сравним требования по памяти для трех реализаций
# "решета Эратосфена", через array, numpy и на
# "чистом" python
def upper_prime(n):
ln = log(n)
return int(n*(ln + log(ln)))


# через numpy
@timememit
def eratosthenes_numpy(n):
# разница только в инициализации массива
sieve = arange(upper_prime(n))
k = 2
for _ in range(n - 1):
# и заполнении решета
sieve[::k] = 0
while sieve[k] == 0:
k += 1
return k


# через array
@timememit
def eratosthenes_array(n):
size = upper_prime(n)
sieve = array('i', range(size))
k = 2
for _ in range(n - 1):
for i in range(k, size, k):
sieve[i] = 0
while sieve[k] == 0:
k += 1
return k


# на чистом python
@timememit
def eratosthenes(n):
size = upper_prime(n)
sieve = list(range(size))
k = 2
for _ in range(n - 1):
for i in range(k, size, k):
sieve[i] = 0
while sieve[k] == 0:
k += 1
return k


n = 100000



if ARG1 is None:
print(f'prime({n}) = {eratosthenes(n)}')
print(f'prime({n}) = {eratosthenes(n)}')
print(f'prime({n}) = {eratosthenes(n)}')
print(f'prime({n}) = {eratosthenes(n)}')
print(f'prime({n}) = {eratosthenes_numpy(n)}')
print(f'prime({n}) = {eratosthenes_numpy(n)}')
print(f'prime({n}) = {eratosthenes_numpy(n)}')
print(f'prime({n}) = {eratosthenes_numpy(n)}')
print(f'prime({n}) = {eratosthenes_array(n)}')
print(f'prime({n}) = {eratosthenes_array(n)}')
print(f'prime({n}) = {eratosthenes_array(n)}')
print(f'prime({n}) = {eratosthenes_array(n)}')
# ---
# eratosthenes: 0.6396 s, 0.656250 MiB
# prime(100000) = 1299709
# eratosthenes: 0.5894 s, 10.886719 MiB
# prime(100000) = 1299709
# eratosthenes: 0.5845 s, 0.238281 MiB
# prime(100000) = 1299709
# eratosthenes: 0.5792 s, 0.238281 MiB
# prime(100000) = 1299709
# eratosthenes_array: 0.5666 s, 0.000000 MiB
# prime(100000) = 1299709
# eratosthenes_array: 0.5675 s, 0.000000 MiB
# prime(100000) = 1299709
# eratosthenes_array: 0.5665 s, 0.000000 MiB
# prime(100000) = 1299709
# eratosthenes_array: 0.5690 s, 0.000000 MiB
# prime(100000) = 1299709
# eratosthenes_numpy: 0.5143 s, 0.000000 MiB
# prime(100000) = 1299709
# eratosthenes_numpy: 0.5149 s, 0.000000 MiB
# prime(100000) = 1299709
# eratosthenes_numpy: 0.5138 s, 0.000000 MiB
# prime(100000) = 1299709
# eratosthenes_numpy: 0.5152 s, 0.000000 MiB
# prime(100000) = 1299709
# ---
#
# Теперь поменяем вызовы местами.
# Ключ коммандной строки показывает, что замеры
# нужно делать сразу после запуска, иначе
# будут нули
if ARG1 == 'array':
print(f'prime({n}) = {eratosthenes_array(n)}')
print(f'prime({n}) = {eratosthenes_array(n)}')
print(f'prime({n}) = {eratosthenes_array(n)}')
print(f'prime({n}) = {eratosthenes_array(n)}')
print(f'prime({n}) = {eratosthenes_numpy(n)}')
print(f'prime({n}) = {eratosthenes_numpy(n)}')
print(f'prime({n}) = {eratosthenes_numpy(n)}')
print(f'prime({n}) = {eratosthenes_numpy(n)}')
print(f'prime({n}) = {eratosthenes(n)}')
print(f'prime({n}) = {eratosthenes(n)}')
print(f'prime({n}) = {eratosthenes(n)}')
print(f'prime({n}) = {eratosthenes(n)}')
# ---
# eratosthenes_array: 0.5791 s, 0.781250 MiB
# prime(100000) = 1299709
# eratosthenes_array: 0.5782 s, 4.636719 MiB
# prime(100000) = 1299709
# eratosthenes_array: 0.5912 s, 0.000000 MiB
# prime(100000) = 1299709
# eratosthenes_array: 0.5735 s, 0.000000 MiB
# prime(100000) = 1299709
# eratosthenes_numpy: 0.5275 s, 0.066406 MiB
# prime(100000) = 1299709
# eratosthenes_numpy: 0.5225 s, 5.152344 MiB
# prime(100000) = 1299709
# eratosthenes_numpy: 0.5150 s, 0.000000 MiB
# prime(100000) = 1299709
# eratosthenes_numpy: 0.5153 s, 0.000000 MiB
# prime(100000) = 1299709
# eratosthenes: 0.5854 s, 0.675781 MiB
# prime(100000) = 1299709
# eratosthenes: 0.5867 s, 0.246094 MiB
# prime(100000) = 1299709
# eratosthenes: 0.5843 s, 0.246094 MiB
# prime(100000) = 1299709
# eratosthenes: 0.5867 s, 0.246094 MiB
# prime(100000) = 1299709
# ---
# Этот пример помогает нам понять результаты прошлого
# примера. Алгоритм "array" выделяет 1 блок 5M,
# "numpy" -- 1 блок 10M, "list" -- 1 блок по 10M
# и еще множество мелких блоков.
# Поэтому если расположить вызовы в порядке возрастания
# потребления памяти, мы видим ее выделение на каждой
# группе вызовов. Если же расположить в обратном порядке,
# то выделение происходит только на первой группе,
# на остальных -- нули.

# Что же касается array, то в этой задаче он показывает
# двукратную экономию памяти по сравнению с numpy и python,
# при этом не теряя в производительности.
# Однако заменить numpy он, конечно, не может
# Идеология numpy не сводится к экономии памяти,
# т.к. эта библиотека создавалась для ускорения
# вычисления сложных формул путем векторизации.
# Поэтому огромные массивы numpy являются платой
# за возможность выполнять быстрые С-функции.


@timememit
def formula_array():
xx = array('d', range(n))
yy = array('d', range(n))
for i, x in enumerate(xx):
yy[i] = math.sqrt(math.sin(x)**2 + math.cos(x)**2)
return yy


@timememit
def formula_numpy():
xx = arange(n)
yy = sqrt(sin(xx)**2 + cos(xx)**2)
return yy


if ARG1 == "math":
formula_array()
formula_numpy()

# ---
# formula_array: 0.1974 s, 1.714844 MiB
# formula_numpy: 0.1160 s, 1.769531 MiB
# ---

# То есть на сложных формулах array ожидаемо проигрывает numpy
# по времени из за накладных расходов на циклы и переменные python,
# при этом разница в потреблении памяти сокращается
# по мере усложнения вычислений.
#
# "Серебряной пулей" в данном вопросе мне кажутся библиотеки,
# компилирующие в бинарный код типа cython и numba, однако их
# использование накладывает существенные ограничения на этап
# развертывания приложения и не всегда допустимо.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

отлично, Сергей

Loading