【问题标题】:Python script execution time increases when executed multiple time parallely并行执行多次时,Python 脚本执行时间会增加
【发布时间】:2016-08-29 11:17:07
【问题描述】:

我有一个 Python 脚本,它在独立执行时的执行时间为 1.2 秒。

但是当我并行执行 5-6 次(我使用邮递员多次 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

top 命令的屏幕截图(在评论中询问):

这是一个示例代码:

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) 如何确保脚本的执行时间每次都保持不变。

2) 我是否可以永久加载库,从而最大限度地减少脚本加载库所花费的时间和消耗的内存?

我做了一个环境并尝试使用

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

但它没有任何区别。

编辑:

我有 7.5GB RAM 的 AMAZON EC2 服务器。

我用来调用 python 脚本的 php 文件。

<?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) 我如何确保脚本的执行时间每次都保持不变。
  • 我有 amazon ec2 服务器,我有 7.5GB 的 RAM,使用命令 watch -n 1 free -m ,我可以看到每次执行需要将近 300MB。所以我想,如果我同时运行其中的 10 个,就有足够的 RAM 资源可供它们执行。所以至少对于其中的 10 个,执行时间应该是相同的。对吗?
  • 这是在什么 type of EC2 instance 上运行的?

标签: python time


【解决方案1】:

1) 你确实不能确保执行总是在同一时间,但至少你可以通过使用类似this answer 中描述的“锁定”策略来避免性能下降。

基本上你可以测试lockfile是否存在,如果存在,让你的程序休眠一段时间,然后再试一次。

如果程序没有找到锁文件,它会创建它,并在执行结束时删除锁文件。

请注意:在下面的代码中,当脚本未能获得一定数量的retries的锁时,它会退出(但这个选择真的取决于你)。

以下代码举例说明了使用文件作为“锁”来防止并行执行同一脚本。

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实例共享同一个内存池,我认为这是不可行的。

【讨论】:

  • 我试过你的代码,它正在做你解释的事情。但问题是,我将 python 脚本作为 API,即我调用 php 文件并传递一些参数,然后 python 执行它以提供一些输出。锁定确保当一个代码正在执行时,另一个代码继续尝试锁定,如果它是空闲的,则执行脚本。但这与我想要实现的目标完全不同。我希望该 python 脚本与已经在执行的脚本并行执行,因为我有可用的 RAM 资源。
  • 对。并且您希望它花费相同的时间;这听起来有点奇怪,因为执行不仅仅意味着 RAM,还包括 CPU、磁盘交换和任务切换(几乎所有这些都由操作系统处理,这给了你更少的控制权)。我建议的方式使执行时间线性化(即,如果 1 个脚本 = 1 秒,2 个脚本 = 2 秒等)。
  • 但是如果我使用它作为 API 然后为 100 个用户,那么对这 100 个用户的请求将线性完成,即一个一个。这正是我不想要的。我想同时处理这 100 个用户的请求。考虑到执行时间取决于多个参数的观点,它不应该是正常执行时间的 3 倍。对吗?
  • 不,一点也不。如果您同时处理 100 个请求,PHP 脚本将生成 100 个并行 shell 命令,并且按照您实现它的方式,每个 shell 命令将分配 300 Mb 的 RAM(即 30Gb,因此交换到磁盘并显着增加执行时间)。
  • 目前仅使用 5 个请求。所以 5*300=1.5GB 我的服务器上有 7.5GB。我有 8GB 的​​磁盘空间,其中几乎 80% 是免费的。这不是对系统有利的条件吗?附言我完全理解你的观点,我同意它,但我试图找出这种奇怪的行为。它不应该发生。
【解决方案2】:

1)

更多的服务器等于更高的可用性

传闻告诉我,确保请求时间一致的一种有效方法是对集群使用多个请求。正如我听到的那样,这个想法是这样的。

缓慢请求的可能性

(免责声明我不是数学家或统计学家。)

如果有 1% 的可能性一个请求需要异常多的时间才能完成,那么可以预期 100 分之一的请求会很慢。如果您作为客户/消费者向集群发出两个请求而不是一个,那么它们都变慢的可能性更像是 1/10000,而三个 1/1000000 等等。不利的一面是,您的传入请求增加了一倍,这意味着需要提供(并支付)两倍的服务器功率才能在一致的时间内满足您的请求,这种额外的成本会随着缓慢请求的可接受机会的多少而增加。

据我所知,此概念已针对一致的履行时间进行了优化。

客户

与这样的服务交互的客户端必须能够产生多个请求并优雅地处理它们,可能包括尽快关闭未完成的连接。

服务器

在后台应该有一个负载平衡器,可以将多个传入的客户端请求关联到多个唯一的集群工作人员。如果单个客户端向负载过重的节点发出多个请求,它只会增加自己的请求时间,就像您在简单示例中看到的那样。

除了让客户端有机会关闭连接之外,最好有一个共享作业完成状态/信息的系统,以便其他处理速度较慢的节点上的积压请求有机会中止已经完成的请求.


这是一个相当非正式的答案,我没有以这种方式优化服务应用程序的直接经验。如果有人这样做,我鼓励并欢迎更详细的编辑和专家实施意见。


2)

缓存导入

是的,这是一件事,而且很棒!

我个人建议设置 django+gunicorn+nginx。 Nginx 可以缓存静态内容并保留请求积压,gunicorn 提供应用程序缓存和多线程和工作人员管理(更不用说很棒的管理和统计工具),django 嵌入了数据库迁移、身份验证、请求路由以及离线的最佳实践用于提供语义休息端点和文档的架子插件,各种优点。

如果你真的坚持自己从头开始构建它,你应该学习uWsgi,一个很棒的Wsgi implementation,它可以与gunicorn 接口以提供应用程序缓存。 Gunicorn 也不是唯一的选择,Nicholas Piël 有一个 Great write up 比较各种 Python Web 服务应用程序的性能。

【讨论】:

    【解决方案3】:

    这是我们所拥有的:

    • EC2 实例类型是 m3.large box,它只有 2 个 vCPU https://aws.amazon.com/ec2/instance-types/?nc1=h_ls

    • 我们需要运行一个占用大量 CPU 和内存的脚本,当 CPU 不忙时,该脚本需要一秒钟才能执行

    • 您正在构建的 API 需要处理并发请求和运行 apache

    • 从截图我可以得出结论:

      • 当运行 5 个进程时,您的 CPU 利用率为 100%。即使运行的进程较少,它们也很可能会 100% 被利用。所以这是瓶颈,运行的进程越多,需要的时间就越多也就不足为奇了——你的 CPU 资源只是在并发运行的脚本之间共享。

      • 每个脚本副本占用大约 300MB 的 RAM,因此您有大量的备用 RAM,这不是瓶颈。屏幕截图上的可用 + 缓冲区内存量证实了这一点。

    • 缺少的部分是:

      1. 请求是直接发送到您的 apache 服务器还是前面有平衡器/代理?
      2. 为什么在您的示例中需要 PHP?只有在没有 php 包装器的情况下,使用 python 生态系统才能获得大量解决方案

    回答您的问题:

    1. 一般情况下这是不可行的

    您最多只能跟踪 CPU 使用率并确保其空闲时间不会低于某个经验阈值——在这种情况下,您的脚本将在或多或少固定的时间内运行。

    为了保证您需要限制并发处理的请求数。 但是,如果同时向您的 API 发送 100 个请求,您将无法并行处理它们!只有其中一些将被并行处理,而其他则等待轮到他们。但是你的服务器不会因为试图为它们提供服务而被击倒。

    1. 是与否

    ,因为当通过 php 包装器针对每个请求启动新脚本时,您不太可能在当前架构中执行某些操作。顺便说一句,每次从头开始运行一个新脚本是一项非常昂贵的操作。

    ,如果使用不同的解决方案。以下是选项:

    • 使用 Python 感知的 pre-forking 网络服务器,它将直接处理您的请求。您将在 python 启动时节省 CPU 资源 + 您可能会利用一些预加载技术在工作人员之间共享 RAM,即 http://docs.gunicorn.org/en/stable/settings.html#preload-app。您还需要限制要运行的并行工作程序的数量http://docs.gunicorn.org/en/stable/settings.html#workers 以满足您的第一个要求。

    • 如果您出于某种原因需要 PHP,您可能会在 PHP 脚本和 python 工作者之间设置一些 中介 - 即 queue 类服务器。 而不是简单地运行您的 python 脚本的几个实例,这些脚本将等待队列中的某些请求可用。一旦它可用,它就会处理它并将响应放回 queue 并且 php 脚本会吞下它并返回给客户端。但是构建这个更复杂,因为第一个解决方案(如果你当然可以消除你的 PHP 脚本)和更多的组件会涉及到。

    • 拒绝并发处理如此繁重的请求的想法,而是为每个请求分配一个唯一 id,将请求放入 队列 并将此 id 返回给客户立即。请求将被离线处理程序拾取,并在完成后放回 queue。客户有责任轮询您的 API 以了解此特定请求是否准备就绪

    • 第 1 和第 2 组合 - 在 PHP 中处理请求并请求另一个 HTTP 服务器(或任何其他 TCP 服务器)来处理您预加载的 .py 脚本

    【讨论】:

      【解决方案4】:

      ec2 云不保证服务器上有 7.5gb 的可用内存。这意味着 VM 性能会受到严重影响,就像您看到服务器的物理空闲内存少于 7.5gb 一样。尝试减少服务器认为它拥有的内存量。

      这种形式的并行性能非常昂贵。通常有 300mb 的要求,理想的脚本是长时间运行,并将内存重新用于多个请求。 Unix fork 函数允许重用共享状态。 os.fork 在 python 中提供了这个,但可能与您的库不兼容。

      【讨论】:

        【解决方案5】:

        这可能与计算机的运行方式有关。

        每个程序在计算机上获得时间片(引用Help Your Kids With Computer Programming,说可能1/1000

        答案 1:尝试使用多个 threads 而不是并行进程 >。

        它会减少耗时,但程序的执行时间仍不会完全恒定

        注意:每个程序都有自己的内存槽,所以内存消耗在拍摄起来。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-07-05
          • 2012-04-25
          • 2023-04-01
          • 1970-01-01
          • 2013-06-07
          • 1970-01-01
          • 2019-02-07
          • 1970-01-01
          相关资源
          最近更新 更多