当多次并行执行时,Python脚本的执行时间会增加

2024-04-20 04:37:25 发布

您现在位置:Python中文网/ 问答频道 /正文

我有一个python脚本,当它独立执行时,它的执行时间是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

top命令截图(在评论中询问): enter image description here

以下是示例代码:

^{pr2}$

我还使用watch -n 1 free -m检查内存消耗,内存消耗也显著增加。在

1)如何确保每次脚本的执行时间保持不变。在

2)是否可以永久加载库,以便脚本加载库所用的时间和消耗的内存可以最小化?在

我创造了一个环境试着用

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

但这没有任何区别。在

编辑:

我有亚马逊的EC2服务器,有7.5GB的RAM。在

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

<?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);
?>

谢谢


Tags: 内存runtest脚本homeresponse时间anaconda
3条回答

以下是我们所拥有的:

  • EC2实例类型为m3.large box,只有2vCPUshttps://aws.amazon.com/ec2/instance-types/?nc1=h_ls

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

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

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

    • 当CPU使用率为100%时,进程的利用率为100%。最有可能的是,即使运行的进程较少,它们也能100%利用。因此,这是一个瓶颈,运行的进程越多,所需的时间也就越长——您的CPU资源只能在并发运行的脚本之间共享。

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

  • 缺少的部分是:

    1. 请求是直接发送到apache服务器还是前面有一个平衡器/代理?在
    2. 为什么你需要在你的例子中使用PHP?在python生态系统中,有很多解决方案都是在没有php包装器的情况下使用的

您问题的答案:

  1. That's infeasible in general case

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

以保证您需要限制同时处理的请求数。 但是如果同时向API发送100个请求,您将无法并行处理它们!只有一部分人将被并行处理,而其他人则等待轮到他们。但是你的服务器不会因为试图为所有人服务而崩溃。在

  1. Yes and no

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

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

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

  • 如果出于某种原因需要PHP,可以在脚本和python workers之间设置一些类似于队列的服务器。 而不是简单地运行几个python脚本的实例来等待队列中的某个请求可用。一旦它可用,它将处理它并将响应放回队列,php脚本将发出咯吱声并返回给客户机。但是,构建它要复杂得多,因为第一个解决方案(当然,如果您可以消除PHP脚本)和更多组件。

  • 拒绝并发处理这种重请求的想法,而是为每个请求分配一个惟一的id,将请求放入一个队列并立即将该id返回给客户机。一旦请求完成,它就会被处理程序接收到。客户将负责对您的API进行轮询,以确定是否已准备好此特定请求

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

(一)

服务器越多,可用性就越高

道听途说,确保请求时间一致的一个有效方法是使用对集群的多个请求。我听说这个主意是这样的。在

缓慢请求的可能性

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

如果一个请求需要1%的时间才能完成,那么百分之一的请求可能会很慢。如果您作为客户机/消费者向一个集群发出两个请求,而不是一个请求,那么两个请求的速度都很慢,可能是1/10000,3个是1/1000000,等等。缺点是您的传入请求翻倍意味着需要提供(并支付)两倍的服务器功率以在一致的时间内满足您的请求,此额外成本随缓慢请求的可接受性而变化。在

据我所知,这个概念是为一致的实现时间而优化的。在

客户

一旦一个客户机能够像这样关闭多个连接,它就可以很快地处理这些未完成的连接。在

服务器

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

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


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


(二)

缓存导入

是的,这是一件很了不起的事!在

我个人建议建立django+gunicorn+nginx。Nginx可以缓存静态内容并保留请求积压,gunicorn提供应用程序缓存和多线程&工作人员管理(更不用说出色的管理和统计工具),django嵌入了数据库迁移、身份验证、请求路由的最佳实践,以及提供语义rest端点的现成插件还有文件,各种各样的好处。在

如果您真的坚持从头开始构建它,那么您应该研究uWsgi,一个可以与gunicorn接口以提供应用程序缓存的好Wsgi implementation。Gunicorn也不是唯一的选择,nicholaspiël有一个Great write up比较各种python web服务应用程序的性能。在

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

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

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

请注意:在下面的代码中,当脚本无法获得某个数量的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实例共享同一个内存池,我认为这是不可行的。在

相关问题 更多 >