Python script время выполнения увеличивается, когда выполняется несколько раз параллельно

У меня есть python script, время выполнения которого составляет 1,2 секунды, пока оно выполняется автономно.

Но когда я выполняю его 5-6 раз параллельно (я использую postman для ping url несколько раз), время выполнения увеличивается.

Добавление разбивки времени.

1 run -> ~1.2seconds
2 run -> ~1.8seconds
3 run -> ~2.3seconds
4 run -> ~2.9seconds
5 run -> ~4.0seconds
6 run -> ~4.5seconds
7 run -> ~5.2seconds
8 run -> ~5.2seconds
9 run -> ~6.4seconds
10 run -> ~7.1seconds

Снимок экрана с верхней командой (задано в комментарии): введите описание изображения здесь

Это пример кода:

import psutil
import os
import time
start_time = time.time()
import cgitb
cgitb.enable()
import numpy as np
import MySQLdb as mysql
import cv2
import sys
import rpy2.robjects as robj
import rpy2.robjects.numpy2ri
rpy2.robjects.numpy2ri.activate()
from rpy2.robjects.packages import importr
R = robj.r
DTW = importr('dtw')

process= psutil.Process(os.getpid())
print " Memory Consumed after libraries load: "
print process.memory_info()[0]/float(2**20)

st_pt=4
# Generate our data (numpy arrays)
template = np.array([range(84),range(84),range(84)]).transpose()
query = np.array([range(2500000),range(2500000),range(2500000)]).transpose()


#time taken
print(" --- %s seconds ---" % (time.time() - start_time))

Я также проверял потребление памяти с помощью watch -n 1 free -m, а потребление памяти также заметно увеличивается.

1) Как я могу убедиться, что время выполнения script остается постоянным каждый раз.

2) Могу ли я постоянно загружать библиотеки, чтобы время, затрачиваемое script на загрузку библиотек и потребляемой памяти, можно было свести к минимуму?

Я сделал обход и попытался использовать

#!/home/ec2-user/anaconda/envs/test_python/

но это не имеет никакого значения.

EDIT:

У меня есть сервер AMAZON EC2 с оперативной памятью 7,5 ГБ.

Мой php файл, с которым я вызываю python script.

<?php

    $response = array("error" => FALSE); 

    if($_SERVER['REQUEST_METHOD']=='GET'){

        $response["error"] = FALSE;
        $command =escapeshellcmd(shell_exec("sudo /home/ec2-user/anaconda/envs/anubhaw_python/bin/python2.7 /var/www/cgi-bin/dtw_test_code.py"));
        session_write_close();
        $order=array("\n","\\");
        $cleanData=str_replace($order,'',$command);
        $response["message"]=$cleanData;

    } else 
    {
        header('HTTP/1.0 400 Bad Request');
        $response["message"] = "Bad Request.";
    }
    echo json_encode($response);
?>

Спасибо

Ответ 1

1) Вы действительно не можете гарантировать, что выполнение будет выполняться всегда в одно и то же время, но по крайней мере вы можете избежать ухудшения производительности, используя стратегию "блокировки", такую ​​как те, что описаны в этот ответ.

В принципе, вы можете проверить, существует ли файл блокировки, и если да, поставите свою программу на некоторое время, а затем повторите попытку.

Если программа не находит файл блокировки, она создает его и удаляет файл блокировки в конце его выполнения.

Обратите внимание: в приведенном ниже коде, когда script не сможет получить блокировку для определенного количества retries, он выйдет (но этот выбор действительно зависит от вас).

Следующий код иллюстрирует использование файла как "блокировки" для параллельных исполнений того же script.

import time
import os
import sys

lockfilename = '.lock'
retries = 10
fail = True

for i in range(retries):
    try:
        lock = open(lockfilename, 'r')
        lock.close()
        time.sleep(1)
    except Exception:
        print('Got after {} retries'.format(i))
        fail = False
        lock = open(lockfilename, 'w')
        lock.write('Locked!')
        lock.close()
        break

if fail:
    print("Cannot get the lock, exiting.")
    sys.exit(2)

# program execution...
time.sleep(5)
# end of program execution

os.remove(lockfilename)

2) Это означало бы, что разные экземпляры python используют один и тот же пул памяти, и я думаю, что это невозможно.

Ответ 2

Здесь мы имеем:

  • Тип экземпляра EC2 - это поле m3.large, которое имеет только 2 vCPUs https://aws.amazon.com/ec2/instance-types/?nc1=h_ls

  • Нам нужно запустить CPU-и память-голодный script, который занимает секунду, чтобы выполнить, когда CPU не занят

  • Вы создаете API, чем нужно обрабатывать параллельные запросы и запускать apache

  • Из скриншота я могу заключить, что:

    • ваши процессоры используются на 100% при запуске 5 процессов. Скорее всего, они будут использоваться на 100%, даже когда будет запущено меньшее количество процессов. Таким образом, это узкое место и не удивительно, что чем больше процессов выполняется, тем больше времени требуется - ресурсы процессора просто становятся доступными для одновременного запуска скриптов.

    • каждая копия script копирует около ~ 300 МБ ОЗУ, поэтому у вас много запасной ОЗУ, и это не узкое место. Количество свободной + буферной памяти на вашем снимке экрана подтверждает это.

  • Недопустимая часть:

    • - это запросы, которые отправляются непосредственно на ваш сервер apache или там есть балансир/прокси?
    • зачем вам PHP в вашем примере? Существует множество решений, доступных с использованием экосистемы python, только без обертки php перед ним.

Ответы на ваши вопросы:

  • Это неосуществимо в общем случае

Самое большее, что вы можете сделать, это отслеживать использование вашего ЦП и следить за тем, чтобы его время простоя не опускалось ниже некоторого эмпирического порога - в этом случае ваши скрипты будут работать в течение более или менее фиксированного времени.

Чтобы гарантировать одновременное ограничение количества обрабатываемых запросов. Но если к вашему API одновременно будет отправлено 100 запросов, вы не сможете обрабатывать их все параллельно! Только некоторые из них будут обрабатываться параллельно, а другие ждут своей очереди. Но ваш сервер не будет сбит, пытаясь обслужить их всех.

  1. Да и нет

Нет, поскольку вряд ли вы можете что-то сделать в своей нынешней архитектуре, когда новый script запускается по каждому запросу через флэш-обертку. Кстати, это очень дорогостоящая операция для запуска нового script с нуля каждый раз.

Да, если используется другое решение. Вот варианты:

  • используйте поддерживающий python pre-forking webserver, который будет обрабатывать ваши запросы напрямую. Вы избавитесь от ресурсов процессора при запуске python +, вы можете использовать некоторую технологию предварительной загрузки для обмена ОЗУ между рабочими, т.е. http://docs.gunicorn.org/en/stable/settings.html#preload-app. Вам также необходимо ограничить количество параллельных работников, которые будут выполняться http://docs.gunicorn.org/en/stable/settings.html#workers, чтобы отправить ваше первое требование.

  • если вам нужно PHP, по какой-то причине вы можете установить некоторый посредник между PHP script и рабочими питонами - то есть сервером с подобной очередью. Затем просто запустите несколько экземпляров ваших скриптов python, которые будут ждать, пока какой-либо запрос будет доступен в очереди. После того, как он будет доступен, он обработает его и вернет ответ в очередь, а php script будет удалять его и возвращать обратно клиенту. Но это сложнее построить для этого первое решение (если вы, конечно, можете удалить свой PHP скрипт), и будет задействовано больше компонентов.

  • отвергает идею одновременного обращения с такими тяжелыми запросами и вместо этого присваивает каждому запросу уникальный идентификатор, помещает запрос в очередь и немедленно возвращает этот идентификатор клиенту. Запрос будет поднят автономным обработчиком и вернется в очередь после его завершения. Клиенту будет поручено опросить ваш API за готовность этого конкретного запроса.

  • 1-й и 2-й комбинированные запросы обрабатываются в PHP и запрашивают другой HTTP-сервер (или любой другой TCP-сервер), обрабатывающий ваши предварительно загруженные .py-скрипты

Ответ 3

1)

Больше серверов равно больше доступности

Hearsay говорит мне, что одним из эффективных способов обеспечения согласованного времени запросов является использование нескольких запросов в кластере. Как я слышал, идея идет примерно так.

Вероятность медленного запроса

(Отказ от ответственности Я не очень математик или статистик.)

Если есть вероятность 1%, запрос будет потратить ненормальное количество времени на завершение, можно ожидать, что один-на-сто запросов будет медленным. Если вы, как клиент/покупатель, делаете два запроса кластеру, а не только одному, вероятность того, что они оба окажутся медленными, будет больше похожа на 1/10000 и с тремя 1/1000000 и т.д. Недостаток удваивает ваши входящие запросы, требуя предоставить (и оплатить) до двух раз больше мощности сервера для выполнения ваших запросов с последовательным временем, это дополнительные шкалы затрат, с тем, насколько вероятность приемлема для медленного запроса.

Насколько мне известно, эта концепция оптимизирована для согласованного времени выполнения.

Клиент

Клиентский интерфейс с такой службой должен иметь возможность выдавать несколько запросов и обрабатывать их изящно, возможно, включая закрытие невыполненных подключений, как только это возможно.

Серверы

На обратной стороне должен быть балансировщик нагрузки, который может связывать несколько входящих клиентских запросов с несколькими уникальными сотрудниками кластера. Если один клиент делает несколько запросов к перегруженному node, он просто собирает свое собственное время запроса, как вы видите на своем простом примере.

В дополнение к наличию у клиента оппортунистически закрытых соединений было бы лучше, чтобы система совместного выполнения задания выполняла статус/информацию, так что запрошенный запрос на других других узлах с более медленным процессом имеет возможность прервать уже выполненный запрос,


Это довольно неофициальный ответ, я не имею прямого опыта в оптимизации приложения-службы таким образом. Если кто-то я поощряю и приветствую более подробные изменения и мнения экспертов по реализации.


2)

Импорт кэширования

да, это вещь, и ее удивительный!

Я лично рекомендовал бы установить django + gunicorn + nginx. Nginx может кэшировать статический контент и сохранять отставание запросов, gunicorn обеспечивает кэширование приложений и управление несколькими потоками и рабочим столом (не говоря уже об огромных инструментах администрирования и статистики), django внедряет передовые методы миграции баз данных, auth, маршрутизации запросов, готовые плагины для предоставления конечных точек семантического отдыха и документации, всевозможные добра.

Если вы действительно настаиваете на том, чтобы создать его с нуля, вы должны изучить uWsgi, отличный Внедрение Wsgi, которое может быть сопряжено с gunicorn для обеспечения кэширования приложений. Gunicorn - тоже не единственный вариант, у Николаса Пиэля есть Отличная запись, сравнивающая производительность различных приложений для веб-приложений python.

Ответ 4

Облако ec2 не гарантирует 7.5 ГБ свободной памяти на сервере. Это будет означать, что производительность виртуальной машины сильно затронута, как вы видите, где на сервере меньше 7,5 гб физического свободного бара. Попытайтесь уменьшить объем памяти, который сервер считает.

Эта форма параллельной работы очень дорогая. Как правило, с требованием 300 МБ, идеальным будет script, который работает долго, и повторно использует память для нескольких запросов. Функция fork Unix позволяет повторно использовать общее состояние. os.fork дает это в python, но может быть несовместим с вашими библиотеками.

Ответ 5

Возможно, это связано с тем, как работают компьютеры.

Каждая программа получает срез времени на компьютере (цитата Помогите своим детям с компьютерным программированием, скажем, может быть 1/1000 секунды)

Ответ 1. Попробуйте использовать несколько потоков вместо параллельных процессов.

Это будет меньше отнимает много времени, но программа время выполнения по-прежнему не будет полностью константой.

Примечание. Каждая программа имеет собственный слот памяти, поэтому используется потребление памяти.