Привет! Сегодня хочу поделиться своим небольшим опытом выбора инструментов для организации расчетов на будущем сервере. Отмечу сразу, что в этой публикации речь пойдет не о самом сервере, а скорее об оптимизации символьных вычислений на нем.
Задача
Есть некий функционал, который позволяет пользователям формировать нередко громоздкие формулы следующего общего вида, по которым в дальнейшем необходимо рассчитывать запросы других пользователей.
Формула поступает в виде строки и подлежит сохранению на сервере и вызову по запросам пользователей. Предполагается, что в запросах пользователей передаются параметры x1_, x2_,… в виде простого списка значений. Требуется определить способ организации подобных вычислений с уклоном на минимизацию времени выполнения.
Особенность 1.
Время формирования самих формул достаточно велико (до пары минут для приведенной формулы), поэтому время обработки и хранения поступающих формул-строк в данной задаче не является критичным (в дальнейшем будет показано, что это величины разных порядков).
Особенность 2.
Предполагается, что основной объем запросов будет носить групповой характер, т.е. в одном запросе могут передаваться несколько наборов значений x1_, x2_,… для расчета по одной и той же формуле.
Инструменты
Язык программирования — Python 3x. В качестве СУБД — Redis (NoSQL).
Пару слов про Redis. На мой взгляд данная задача — прекрасный пример для его использования: пользователь формирует формулу; формула обрабатывается и отправляется в хранилище; далее она извлекается из хранилища и обрабатывается в случае, если кто-то захотел ей воспользоваться; переданные по запросу значения подставляются в формулу и выдается результат. Всё. Единственное, что необходимо знать пользователю, который хочет что-то рассчитать — количество уникальных переменных в формуле. В Redis есть встроенный механизм хэшей, так почему бы им и не воспользоваться?
import redis
r = redis.StrictRedis(host='localhost', port=6379, db=0) #подключение к серверу redis
r.hset('expr:1', 'expr', expr) #запись самой формулы в хэш 'expr:1'
r.hset('expr:1', 'params', num) #запись числа параметров в хэш 'expr:1'
r.hget('expr:1', 'expr') #извлечение формулы из хэша 'expr:1'
r.hget('expr:1', 'params') #извлечение числа параметров из хэша 'expr:1'
Для работы с самими формулами воспользуемся замечательной библиотекой Sympy, которая умеет переводить формулу-строку в символьное выражение и производить необходимые вычисления (а вообще библиотека открывает огромный математический функционал для работы с символьными выражениями).
Профилирование и оптимизация
Для измерения времени выполнения участков кода воспользуемся следующим классом (где-то позаимствованным в просторах интернета):
class Profiler(object): #профилировщик времени
def __init__(self,info=''):
self.info = info
def __enter__(self):
self._startTime = time()
def __exit__(self, type, value, traceback):
print(self.info, "Elapsed time: {:.3f} sec".format(time() - self._startTime))
Поехали… Для чистоты эксперимента введем num_iter = 1000 — число испытаний.
Протестируем профилировщик на чтении формулы-строки из файла:
with Profiler('read (' + str(num_iter) + '): cycle'):
for i in range(num_iter):
f = open('expr.txt')
expr_txt = f.read()
f.close()
>>read (1000): cycle Elapsed time: 0.014 sec
Формула-строка загружена. Теперь определим сколько же в ней переменных и какие они (должны же мы знать в какую переменную именно подставлять значения):
with Profiler('find unique sorted symbols (' + str(num_iter) + '): cycle'):
for i in range(num_iter):
symbols_set = set()
result = re.findall(r"xd_", expr_txt)
for match in result:
symbols_set.add(match)
symbols_set = sorted(symbols_set)
symbols_list = symbols(symbols_set)
>>find unique sorted symbols (1000): cycle Elapsed time: 0.156 sec
Полученное время вполне устраивает. Теперь переведем формулу-строку в символьное выражение:
with Profiler('sympify'):
expr = sympify(expr_txt)
>>sympify Elapsed time: 0.426 sec
В этом виде ее уже можно использовать для вычислений. Попробуем:
with Profiler('subs cycle (' + str(num_iter) + '): cycle'):
for i in range(num_iter):
expr_copy = copy.copy(expr)
for x in symbols_list:
expr_copy = expr_copy.subs(x,1)
>>subs cycle (1000): cycle Elapsed time: 0.245 sec
Здесь есть особенность: sympy не умеет (?) подставлять сразу все значения в переменные символьного выражения. Приходится пользоваться циклом. В результате выполнения в expr_copy получаем вещественное число.
В sympy есть возможность преобразовать символьное выражение в лямбда-функцию с использованием модуля numpy, что теоретически должно ускорить расчеты. Осуществим перевод:
with Profiler('lambdify'):
func = lambdify(tuple(symbols_list), expr, 'numpy') # returns a numpy-ready function
>>lambdify Elapsed time: 0.114 sec
Не слишком долго получилось, что радует. Теперь проверим как быстро будут осуществляться вычисления:
with Profiler('subs cycle (' + str(num_iter) + '): lambdify'):
for i in range(num_iter):
func(*[1 for i in range(len(symbols_set))])
>>subs cycle (1000): lambdify Elapsed time: 0.026 sec
Вот это уровень! Быстрее почти на порядок. Особенно вкусно, если учесть необходимость в групповых запросах (особенность 2). Проверим на всякий случай совпадение значений:
print('exp1 == exp2:', round(expr_copy,12) == round(func(*[1 for i in range(len(symbols_set))]),12))
>>exp1 == exp2: True
Вывод 1.
Хранить строку-формулу нецелесообразно — велико время ее преобразования для вычислений. Имеет смысл хранить либо символьное выражение, либо лямбда-функцию.
Попробуем разобраться с хранением. Символьное выражение — класс sympy, лямбда функция — также класс (в особенности не вникал). Будем пробовать сериализовать с помощью встроенного pickle, cloudpickle, dill:
with Profiler('pickle_dumps cycle (' + str(num_iter) + '): sympifyed expr'):
for i in range(num_iter):
pickle_dump = pickle.dumps(expr)
with Profiler('pickle_loads cycle (' + str(num_iter) + '): sympifyed expr'):
for i in range(num_iter):
pickle.loads(pickle_dump)
print()
with Profiler('cloudpickle_dumps cycle (' + str(num_iter) + '): sympifyed expr'):
for i in range(num_iter):
cloudpickle_dump = cloudpickle.dumps(expr)
with Profiler('cloudpickle_loads cycle (' + str(num_iter) + '): sympifyed expr'):
for i in range(num_iter):
cloudpickle.loads(cloudpickle_dump)
print()
with Profiler('dill_dumps cycle (' + str(num_iter) + '): sympifyed expr'):
for i in range(num_iter):
dill_dump = dill.dumps(expr)
with Profiler('dill_loads cycle (' + str(num_iter) + '): sympifyed expr'):
for i in range(num_iter):
dill.loads(dill_dump)
>>pickle_dumps cycle (1000): sympifyed expr Elapsed time: 0.430 sec
>>pickle_loads cycle (1000): sympifyed expr Elapsed time: 2.320 sec
>>
>>cloudpickle_dumps cycle (1000): sympifyed expr Elapsed time: 7.584 sec
>>cloudpickle_loads cycle (1000): sympifyed expr Elapsed time: 2.314 sec
>>
>>dill_dumps cycle (1000): sympifyed expr Elapsed time: 8.259 sec
>>dill_loads cycle (1000): sympifyed expr Elapsed time: 2.806 sec
Отметим, что pickle супер быстро сериализует символьные выражения, если сравнивать с «коллегами». Время десериализации отличается, но уже не так существенно. Теперь попробует протестировать сериализацию/десериализацию в связке с хранением/загрузкой Redis. Следует отметить тот факт, что pickle не сумел сериализовать/десериализовать лямбда-функцию.
with Profiler('redis_set cycle (' + str(num_iter) + '): sympifyed expr'):
for i in range(num_iter):
r.set('expr', pickle_dump)
with Profiler('redis_get cycle (' + str(num_iter) + '): sympifyed expr'):
for i in range(num_iter):
r.get('expr')
print()
with Profiler('pickle_dumps + redis_set cycle (' + str(num_iter) + '): sympifyed expr'):
for i in range(num_iter):
r.set('expr', pickle.dumps(expr))
with Profiler('redis_get + pickle_loads cycle (' + str(num_iter) + '): sympifyed expr'):
for i in range(num_iter):
pickle.loads(r.get('expr'))
print()
with Profiler('cloudpickle_dumps + redis_set cycle (' + str(num_iter) + '): sympifyed expr'):
for i in range(num_iter):
r.set('expr', cloudpickle.dumps(expr))
with Profiler('redis_get + cloudpickle_loads cycle (' + str(num_iter) + '): sympifyed expr'):
for i in range(num_iter):
cloudpickle.loads(r.get('expr'))
print()
with Profiler('dill_dumps + redis_set cycle (' + str(num_iter) + '): lambdifyed expr'):
for i in range(num_iter):
r.set('expr', dill.dumps(expr))
with Profiler('redis_get + dill_loads cycle (' + str(num_iter) + '): lambdifyed expr'):
for i in range(num_iter):
dill.loads(r.get('expr'))
>>redis_set cycle (1000): sympifyed expr Elapsed time: 0.066 sec
>>redis_get cycle (1000): sympifyed expr Elapsed time: 0.051 sec
>>
>>pickle_dumps + redis_set cycle (1000): sympifyed expr Elapsed time: 0.524 sec
>>redis_get + pickle_loads cycle (1000): sympifyed expr Elapsed time: 2.437 sec
>>
>>cloudpickle_dumps + redis_set cycle (1000): sympifyed expr Elapsed time: 7.659 sec
>>redis_get + cloudpickle_loads cycle (1000): sympifyed expr Elapsed time: 2.492 sec
>>
>>dill_dumps + redis_set cycle (1000): lambdifyed expr Elapsed time: 8.333 sec
>>redis_get + dill_loads cycle (1000): lambdifyed expr Elapsed time: 2.932 sec
cloudpickle и dill с сериализацией/десериализацией лямбда-функции справились (в примере выше, правда, cloudpickle работал с символьным выражением).
Вывод 2.
Redis показывает хороший результат чтение/запись 1000 значений в одном потоке. Чтобы сделать выбор в дальнейшем требуется профилировать полные цепочки действий от поступления формулы-строки до выдачи пользователю рассчитанного по ней значения:
print('nFINAL performance test:')
with Profiler('sympify + pickle_dumps_sympifyed_expr + redis_set cycle (' + str(num_iter) + '): '):
for i in range(num_iter):
expr = sympify(expr_txt)
r.set('expr', pickle.dumps(expr))
with Profiler('redis_get + pickle_loads_sympifyed_expr + subs cycle (' + str(num_iter) + '): '):
for i in range(num_iter):
loaded_expr = pickle.loads(r.get('expr'))
expr_copy = copy.copy(loaded_expr)
for x in symbols_list:
expr_copy = expr_copy.subs(x,1)
with Profiler('sympify + lambdify + dill_dumps_lambdifyed_expr + redis_set cycle (' + str(num_iter) + '): '):
for i in range(num_iter):
expr = sympify(expr_txt)
func = lambdify(tuple(symbols_list), expr, 'numpy')
r.set('expr', dill.dumps(expr))
with Profiler('redis_get + dill_loads_lambdifyed_expr + subs cycle (' + str(num_iter) + '): '):
for i in range(num_iter):
loaded_expr = dill.loads(r.get('expr'))
func(*[1 for i in range(len(symbols_set))])
with Profiler('sympify + cloudpickle_dumps_sympifyed_expr + redis_set cycle (' + str(num_iter) + '): '):
for i in range(num_iter):
expr = sympify(expr_txt)
r.set('expr', cloudpickle.dumps(expr))
with Profiler('redis_get + cloudpickle_loads_sympifyed_expr + subs cycle (' + str(num_iter) + '): '):
for i in range(num_iter):
loaded_expr = cloudpickle.loads(r.get('expr'))
expr_copy = copy.copy(loaded_expr)
for x in symbols_list:
expr_copy = expr_copy.subs(x,1)
with Profiler('sympify + lambdify + cloudpickle_dumps_lambdifyed_expr + redis_set cycle (' + str(num_iter) + '): '):
for i in range(num_iter):
expr = sympify(expr_txt)
func = lambdify(tuple(symbols_list), expr, 'numpy')
r.set('expr', cloudpickle.dumps(expr))
with Profiler('redis_get + cloudpickle_loads_lambdifyed_expr + subs cycle (' + str(num_iter) + '): '):
for i in range(num_iter):
loaded_expr = cloudpickle.loads(r.get('expr'))
func(*[1 for i in range(len(symbols_set))])
>>FINAL performance test:
>>sympify + pickle_dumps_sympifyed_expr + redis_set cycle (1000): Elapsed time: 15.075 sec
>>redis_get + pickle_loads_sympifyed_expr + subs cycle (1000): Elapsed time: 2.929 sec
>>sympify + lambdify + dill_dumps_lambdifyed_expr + redis_set cycle (1000): Elapsed time: 87.707 sec
>>redis_get + dill_loads_lambdifyed_expr + subs cycle (1000): Elapsed time: 2.356 sec
>>sympify + cloudpickle_dumps_sympifyed_expr + redis_set cycle (1000): Elapsed time: 23.633 sec
>>redis_get + cloudpickle_loads_sympifyed_expr + subs cycle (1000): Elapsed time: 3.059 sec
>>sympify + lambdify + cloudpickle_dumps_lambdifyed_expr + redis_set cycle (1000): Elapsed time: 86.739 sec
>>redis_get + cloudpickle_loads_lambdifyed_expr + subs cycle (1000): Elapsed time: 1.721 sec
Вывод 3.
Создание лямбда-функции и ее сериализация с помощью cloudpickle, конечно, оказались самыми долгими, НО, если вспомнить (особенность 1) некритичность времени обработки и хранения, то… Cloudpickle молодец! Удалось в рамках одного потока вытащить из базы, десериализовать и рассчитать 1000 раз за 1,7 сек. Что, в целом, хорошо, учитывая сложность исходной формулы-строки.
Попробуем оценить производительность для групповых запросов. Будем менять число групп параметров порядками с надеждой на улучшение результата:
print('nTEST performance for complex requests:')
for x in [1,10,100,1000]:
with Profiler('redis_get + cloudpickle_loads_lambdifyed_expr + ' + str(x) + '*subs cycle (' + str(round(num_iter/x)) + '): '):
for i in range(round(num_iter/x)):
loaded_expr = cloudpickle.loads(r.get('expr'))
for j in range(x):
func(*[1 for i in range(len(symbols_set))])
>>TEST performance for complex requests:
>>redis_get + cloudpickle_loads_lambdifyed_expr + 1*subs cycle (1000): Elapsed time: 1.768 sec
>>redis_get + cloudpickle_loads_lambdifyed_expr + 10*subs cycle (100): Elapsed time: 0.204 sec
>>redis_get + cloudpickle_loads_lambdifyed_expr + 100*subs cycle (10): Elapsed time: 0.046 sec
>>redis_get + cloudpickle_loads_lambdifyed_expr + 1000*subs cycle (1): Elapsed time: 0.028 sec
Результат выглядит вполне жизнеспособным. Расчеты проводились на виртуальной машине со следующими характеристиками: ОС Ubuntu 16.04.2 LTS, Процессор Intel® Core(TM) i7-4720HQ CPU @ 2.60GHz (выделено 1 ядро), DDR3-1600 (выделено 1Gb).
Заключение.
Спасибо за просмотр! Буду рад конструктивной критике и интересным замечаниям.
В вопросе профилирования и оптимизации требуемых вычислений были использованы идеи и подходы, изложенные здесь (слишком «слабая» формула в примере, но хороший набор тестов) и здесь (информация о сериализации лямбда-функций).
import redis
import pickle
import dill
import cloudpickle
import re
import copy
from time import time
from sympy.utilities.lambdify import lambdify
from sympy import sympify, symbols
class Profiler(object): #профилировщик времени
def __init__(self,info=''):
self.info = info
def __enter__(self):
self._startTime = time()
def __exit__(self, type, value, traceback):
print(self.info, "Elapsed time: {:.3f} sec".format(time() - self._startTime))
num_iter = 1000
dill.settings['recurse'] = True
r = redis.StrictRedis(host='localhost', port=6379, db=0)
with Profiler('read (' + str(num_iter) + '): cycle'):
for i in range(num_iter):
f = open('expr.txt')
expr_txt = f.read()
f.close()
with Profiler('find unique sorted symbols (' + str(num_iter) + '): cycle'):
for i in range(num_iter):
symbols_set = set()
result = re.findall(r"xd_", expr_txt)
for match in result:
symbols_set.add(match)
symbols_set = sorted(symbols_set)
symbols_list = symbols(symbols_set)
print()
with Profiler('sympify'):
expr = sympify(expr_txt)
with Profiler('lambdify'):
func = lambdify(tuple(symbols_list), expr, 'numpy') # returns a numpy-ready function
print()
with Profiler('subs cycle (' + str(num_iter) + '): cycle'):
for i in range(num_iter):
expr_copy = copy.copy(expr)
for x in symbols_list:
expr_copy = expr_copy.subs(x,1)
with Profiler('subs cycle (' + str(num_iter) + '): lambdify'):
for i in range(num_iter):
func(*[1 for i in range(len(symbols_set))])
print()
print('exp1 == exp2:', round(expr_copy,12) == round(func(*[1 for i in range(len(symbols_set))]),12))
print()
with Profiler('pickle_dumps cycle (' + str(num_iter) + '): sympifyed expr'):
for i in range(num_iter):
pickle_dump = pickle.dumps(expr)
with Profiler('pickle_loads cycle (' + str(num_iter) + '): sympifyed expr'):
for i in range(num_iter):
pickle.loads(pickle_dump)
print()
with Profiler('cloudpickle_dumps cycle (' + str(num_iter) + '): sympifyed expr'):
for i in range(num_iter):
cloudpickle_dump = cloudpickle.dumps(expr)
with Profiler('cloudpickle_loads cycle (' + str(num_iter) + '): sympifyed expr'):
for i in range(num_iter):
cloudpickle.loads(cloudpickle_dump)
print()
with Profiler('dill_dumps cycle (' + str(num_iter) + '): sympifyed expr'):
for i in range(num_iter):
dill_dump = dill.dumps(expr)
with Profiler('dill_loads cycle (' + str(num_iter) + '): sympifyed expr'):
for i in range(num_iter):
dill.loads(dill_dump)
print()
#убедились, что все правильно считает (до 12 знака), сравнили производительность, попробуем побаловаться с redis
with Profiler('redis_set cycle (' + str(num_iter) + '): sympifyed expr'):
for i in range(num_iter):
r.set('expr', pickle_dump)
with Profiler('redis_get cycle (' + str(num_iter) + '): sympifyed expr'):
for i in range(num_iter):
r.get('expr')
print()
with Profiler('pickle_dumps + redis_set cycle (' + str(num_iter) + '): sympifyed expr'):
for i in range(num_iter):
r.set('expr', pickle.dumps(expr))
with Profiler('redis_get + pickle_loads cycle (' + str(num_iter) + '): sympifyed expr'):
for i in range(num_iter):
pickle.loads(r.get('expr'))
print()
with Profiler('cloudpickle_dumps + redis_set cycle (' + str(num_iter) + '): sympifyed expr'):
for i in range(num_iter):
r.set('expr', cloudpickle.dumps(expr))
with Profiler('redis_get + cloudpickle_loads cycle (' + str(num_iter) + '): sympifyed expr'):
for i in range(num_iter):
cloudpickle.loads(r.get('expr'))
print()
with Profiler('dill_dumps + redis_set cycle (' + str(num_iter) + '): lambdifyed expr'):
for i in range(num_iter):
r.set('expr', dill.dumps(expr))
with Profiler('redis_get + dill_loads cycle (' + str(num_iter) + '): lambdifyed expr'):
for i in range(num_iter):
dill.loads(r.get('expr'))
print('nFINAL performance test:')
with Profiler('sympify + pickle_dumps_sympifyed_expr + redis_set cycle (' + str(num_iter) + '): '):
for i in range(num_iter):
expr = sympify(expr_txt)
r.set('expr', pickle.dumps(expr))
with Profiler('redis_get + pickle_loads_sympifyed_expr + subs cycle (' + str(num_iter) + '): '):
for i in range(num_iter):
loaded_expr = pickle.loads(r.get('expr'))
expr_copy = copy.copy(loaded_expr)
for x in symbols_list:
expr_copy = expr_copy.subs(x,1)
with Profiler('sympify + lambdify + dill_dumps_lambdifyed_expr + redis_set cycle (' + str(num_iter) + '): '):
for i in range(num_iter):
expr = sympify(expr_txt)
func = lambdify(tuple(symbols_list), expr, 'numpy')
r.set('expr', dill.dumps(expr))
with Profiler('redis_get + dill_loads_lambdifyed_expr + subs cycle (' + str(num_iter) + '): '):
for i in range(num_iter):
loaded_expr = dill.loads(r.get('expr'))
func(*[1 for i in range(len(symbols_set))])
with Profiler('sympify + cloudpickle_dumps_sympifyed_expr + redis_set cycle (' + str(num_iter) + '): '):
for i in range(num_iter):
expr = sympify(expr_txt)
r.set('expr', cloudpickle.dumps(expr))
with Profiler('redis_get + cloudpickle_loads_sympifyed_expr + subs cycle (' + str(num_iter) + '): '):
for i in range(num_iter):
loaded_expr = cloudpickle.loads(r.get('expr'))
expr_copy = copy.copy(loaded_expr)
for x in symbols_list:
expr_copy = expr_copy.subs(x,1)
with Profiler('sympify + lambdify + cloudpickle_dumps_lambdifyed_expr + redis_set cycle (' + str(num_iter) + '): '):
for i in range(num_iter):
expr = sympify(expr_txt)
func = lambdify(tuple(symbols_list), expr, 'numpy')
r.set('expr', cloudpickle.dumps(expr))
with Profiler('redis_get + cloudpickle_loads_lambdifyed_expr + subs cycle (' + str(num_iter) + '): '):
for i in range(num_iter):
loaded_expr = cloudpickle.loads(r.get('expr'))
func(*[1 for i in range(len(symbols_set))])
print('nTEST performance for complex requests:')
for x in [1,10,100,1000]:
with Profiler('redis_get + cloudpickle_loads_lambdifyed_expr + ' + str(x) + '*subs cycle (' + str(round(num_iter/x)) + '): '):
for i in range(round(num_iter/x)):
loaded_expr = cloudpickle.loads(r.get('expr'))
for j in range(x):
func(*[1 for i in range(len(symbols_set))])
#r.set('expr', func)
>>read (1000): cycle Elapsed time: 0.014 sec
>>find unique sorted symbols (1000): cycle Elapsed time: 0.156 sec
>>
>>sympify Elapsed time: 0.426 sec
>>lambdify Elapsed time: 0.114 sec
>>
>>subs cycle (1000): cycle Elapsed time: 0.245 sec
>>subs cycle (1000): lambdify Elapsed time: 0.026 sec
>>
>>exp1 == exp2: True
>>
>>pickle_dumps cycle (1000): sympifyed expr Elapsed time: 0.430 sec
>>pickle_loads cycle (1000): sympifyed expr Elapsed time: 2.320 sec
>>
>>cloudpickle_dumps cycle (1000): sympifyed expr Elapsed time: 7.584 sec
>>cloudpickle_loads cycle (1000): sympifyed expr Elapsed time: 2.314 sec
>>
>>dill_dumps cycle (1000): sympifyed expr Elapsed time: 8.259 sec
>>dill_loads cycle (1000): sympifyed expr Elapsed time: 2.806 sec
>>
>>redis_set cycle (1000): sympifyed expr Elapsed time: 0.066 sec
>>redis_get cycle (1000): sympifyed expr Elapsed time: 0.051 sec
>>
>>pickle_dumps + redis_set cycle (1000): sympifyed expr Elapsed time: 0.524 sec
>>redis_get + pickle_loads cycle (1000): sympifyed expr Elapsed time: 2.437 sec
>>
>>cloudpickle_dumps + redis_set cycle (1000): sympifyed expr Elapsed time: 7.659 sec
>>redis_get + cloudpickle_loads cycle (1000): sympifyed expr Elapsed time: 2.492 sec
>>
>>dill_dumps + redis_set cycle (1000): lambdifyed expr Elapsed time: 8.333 sec
>>redis_get + dill_loads cycle (1000): lambdifyed expr Elapsed time: 2.932 sec
>>
>>FINAL performance test:
>>sympify + pickle_dumps_sympifyed_expr + redis_set cycle (1000): Elapsed time: 15.075 sec
>>redis_get + pickle_loads_sympifyed_expr + subs cycle (1000): Elapsed time: 2.929 sec
>>sympify + lambdify + dill_dumps_lambdifyed_expr + redis_set cycle (1000): Elapsed time: 87.707 sec
>>redis_get + dill_loads_lambdifyed_expr + subs cycle (1000): Elapsed time: 2.356 sec
>>sympify + cloudpickle_dumps_sympifyed_expr + redis_set cycle (1000): Elapsed time: 23.633 sec
>>redis_get + cloudpickle_loads_sympifyed_expr + subs cycle (1000): Elapsed time: 3.059 sec
>>sympify + lambdify + cloudpickle_dumps_lambdifyed_expr + redis_set cycle (1000): Elapsed time: 86.739 sec
>>redis_get + cloudpickle_loads_lambdifyed_expr + subs cycle (1000): Elapsed time: 1.721 sec
>>
>>TEST performance for complex requests:
>>redis_get + cloudpickle_loads_lambdifyed_expr + 1*subs cycle (1000): Elapsed time: 1.768 sec
>>redis_get + cloudpickle_loads_lambdifyed_expr + 10*subs cycle (100): Elapsed time: 0.204 sec
>>redis_get + cloudpickle_loads_lambdifyed_expr + 100*subs cycle (10): Elapsed time: 0.046 sec
>>redis_get + cloudpickle_loads_lambdifyed_expr + 1000*subs cycle (1): Elapsed time: 0.028 sec
Чтобы воспользоваться кодом, необходимо:
- Создать файл expr.txt рядом с python-скриптом и поместить в него формулу-строку соответствующего вида
- Установить библиотеки redis, dill, cloudpickle, sympy, numpy
Автор: Дмитрий Павлов