Python中的多处理:Numpy+向量求和>巨大的减速

2024-04-26 15:13:47 发布

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

请不要因为这封长信而气馁。我尽量提供尽可能多的数据,我真的需要帮助来解决这个问题:S。如果有新的提示或想法,我会每天更新

问题:

我试图在并行进程的帮助下,在双核机器上并行运行Python代码(以避免GIL),但是有一个问题,即代码速度明显减慢。例如,在一台单核机器上运行每台工作负载需要600秒,但是在双核计算机上运行需要1600秒(每个工作负载800秒)。在

我已经试过了:

  • 我测量了一下记忆,似乎没有记忆问题。 [在最高点使用20%]。

  • 我用“htop”来检查我是否真的在不同的内核上运行程序,或者我的核心亲和力是否被打乱了。但也不走运,我的程序在我所有的核心上运行。

  • 这个问题是一个CPU受限的问题,所以我检查并确认我的代码大部分时间都在所有内核上以100%的CPU运行。

  • 我检查了进程ID,实际上,我产生了两个不同的进程。

  • 我将提交给executor[e.submit(function,[…])]的函数改为calculate pie函数,并观察到了巨大的加速。所以问题很可能是在我的process_函数(…)中,我提交给执行器的,而不是之前的代码中。

  • 目前我正在使用“concurrent”中的“futures”来并行化任务。但我也尝试了“multiprocessing”中的“pool”类。不过,结果还是一样。

代码:

  • 生成进程:

    result = [None]*psutil.cpu_count()
    
    e = futures.ProcessPoolExecutor( max_workers=psutil.cpu_count() )
    
    for i in range(psutil.cpu_count()):
        result[i] = e.submit(process_function, ...)
    
  • 过程函数:

    from math import floor
    from math import ceil
    import numpy
    import MySQLdb
    import time
    
    db = MySQLdb.connect(...)
    cursor  = db.cursor()
    query = "SELECT ...."
    cursor.execute(query)
    
    [...]  #save db results into the variable db_matrix (30 columns, 5.000 rows)
    [...]  #save db results into the variable bp_vector (3 columns, 500 rows)
    [...]  #save db results into the variable option_vector( 3 columns, 4000 rows)
    
    cursor.close()
    db.close()
    
    counter = 0 
    
    for i in range(4000):
        for j in range(500):
             helper[:] = (1-bp_vector[j,0]-bp_vector[j,1]-bp_vector[j,2])*db_matrix[:,0] 
                         + db_matrix[:,option_vector[i,0]] * bp_vector[j,0]  
                         + db_matrix[:,option_vector[i,1]] * bp_vector[j,1]   
                         + db_matrix[:,option_vector[i,2]] * bp_vector[j,2]
    
             result[counter,0] = (helper < -7.55).sum()
    
             counter = counter + 1
    
    return result
    

我的猜测是:

  • 我的猜测是,由于某种原因,产生向量“helper”的weigthed向量乘法导致了一些问题。[我相信时间测量部分证实了这一猜测]

  • 会不会是纽比制造了这些问题?numpy兼容多处理吗?如果没有,我能做什么?[已在评论中回答]

  • 是不是因为缓存的缘故?我在论坛上读到过,但说实话,我并没有真正理解。但如果问题是根源,我会让自己熟悉这个话题。

时间测量:(编辑)

  • 单核:从数据库获取数据的时间:8秒。

  • 双核:从数据库获取数据的时间:12秒。

  • 单核:在进程函数中执行双循环的时间:~640秒。

  • 双核:在进程函数中执行双循环的时间:~1600秒

更新:(编辑)

当我用循环中每100个进程测量两个进程的时间时,我发现它大约是我在一个进程上运行时测量同一事物时所观察到的时间的220%。但更神秘的是,如果我在运行过程中退出进程,另一个进程会加速!另一个过程实际上会加速到与单独运行时相同的水平。所以,在我目前看不到的进程之间一定有一些依赖关系:S

更新2:(编辑)

所以,我又做了一些测试和测量。在测试运行中,我使用Google cloud compute engine的一台单核linux机器(n1-standard-1,1vcpu,3.75gb内存)或一台两核linux机器(n1-standard-2,2vcpu,7.5gb内存)。不过,我也在本地电脑上做了测试,结果大致相同。(>;因此,虚拟化环境应该很好)。结果如下:

注:这里的时间与上面的测量值不同,因为我对循环进行了一点限制,并在Google云上进行了测试,而不是在我的家用电脑上

1-core machine, started 1 process:

time: 225sec , CPU utilization: ~100%

1-core machine, started 2 process:

time: 557sec , CPU utilization: ~100%

1-core machine, started 1 process, limited max. CPU-utilization to 50%:

time: 488sec , CPU utilization: ~50%

一。在

2-core machine, started 2 process:

time: 665sec , CPU-1 utilization: ~100% , CPU-2 utilization: ~100%

the process did not jumped between the cores, each used 1 core

(at least htop displayed these results with the “Process” column)

2-core machine, started 1 process:

time: 222sec , CPU-1 utilization: ~100% (0%) , CPU-2 utilization: ~0% (100%)

however, the process jumped sometimes between the cores

2-core machine, started 1 process, limited max. CPU-utilization to 50%:

time: 493sec , CPU-1 utilization: ~50% (0%) , CPU-2 utilization: ~0% (100%)

however, the process jumped extremely often between the cores

我使用“htop”和python模块“time”来获得这些结果。在

更新-3:(编辑)

我使用cProfile分析我的代码:

python -m cProfile -s cumtime fun_name.py

这些文件太长了,不能在这里发表,但我相信如果它们包含有价值的信息,这些信息很可能是在结果文本之上的信息。因此,我将在这里发布结果的第一行:

1核机器,启动1个进程:

623158 function calls (622735 primitive calls) in 229.286 seconds

   Ordered by: cumulative time

   ncalls  tottime  percall  cumtime  percall   filename:lineno(function)
        1    0.371    0.371  229.287  229.287   20_with_multiprocessing.py:1(<module>)
        3    0.000    0.000  225.082   75.027   threading.py:309(wait)
        1    0.000    0.000  225.082  225.082   _base.py:378(result)
       25  225.082    9.003  225.082    9.003   {method 'acquire' of 'thread.lock' objects}
        1    0.598    0.598    3.081    3.081   get_BP_Verteilung_Vektoren.py:1(get_BP_Verteilung_Vektoren)
        3    0.000    0.000    2.877    0.959   cursors.py:164(execute)
        3    0.000    0.000    2.877    0.959   cursors.py:353(_query)
        3    0.000    0.000    1.958    0.653   cursors.py:315(_do_query)
        3    0.000    0.000    1.943    0.648   cursors.py:142(_do_get_result)
        3    0.000    0.000    1.943    0.648   cursors.py:351(_get_result)
        3    1.943    0.648    1.943    0.648   {method 'store_result' of '_mysql.connection' objects}
        3    0.001    0.000    0.919    0.306   cursors.py:358(_post_get_result)
        3    0.000    0.000    0.917    0.306   cursors.py:324(_fetch_row)
        3    0.917    0.306    0.917    0.306   {built-in method fetch_row}
   591314    0.161    0.000    0.161    0.000   {range}

1核机器,启动2个进程:

626052 function calls (625616 primitive calls) in 578.086 seconds

   Ordered by: cumulative time

   ncalls  tottime  percall  cumtime  percall   filename:lineno(function)
        1    0.310    0.310  578.087  578.087   20_with_multiprocessing.py:1(<module>)
       30  574.310   19.144  574.310   19.144   {method 'acquire' of 'thread.lock' objects}
        2    0.000    0.000  574.310  287.155   _base.py:378(result)
        3    0.000    0.000  574.310  191.437   threading.py:309(wait)
        1    0.544    0.544    2.854    2.854   get_BP_Verteilung_Vektoren.py:1(get_BP_Verteilung_Vektoren)
        3    0.000    0.000    2.563    0.854   cursors.py:164(execute)
        3    0.000    0.000    2.563    0.854   cursors.py:353(_query)
        3    0.000    0.000    1.715    0.572   cursors.py:315(_do_query)
        3    0.000    0.000    1.701    0.567   cursors.py:142(_do_get_result)
        3    0.000    0.000    1.701    0.567   cursors.py:351(_get_result)
        3    1.701    0.567    1.701    0.567   {method 'store_result' of '_mysql.connection' objects}
        3    0.001    0.000    0.848    0.283   cursors.py:358(_post_get_result)
        3    0.000    0.000    0.847    0.282   cursors.py:324(_fetch_row)
        3    0.847    0.282    0.847    0.282   {built-in method fetch_row}
   591343    0.152    0.000    0.152    0.000   {range}

一。在

2核机器,启动1个进程:

623164 function calls (622741 primitive calls) in 235.954 seconds

   Ordered by: cumulative time

   ncalls  tottime  percall  cumtime  percall   filename:lineno(function)
        1    0.246    0.246  235.955  235.955   20_with_multiprocessing.py:1(<module>)
        3    0.000    0.000  232.003   77.334   threading.py:309(wait)
       25  232.003    9.280  232.003    9.280   {method 'acquire' of 'thread.lock' objects}
        1    0.000    0.000  232.003  232.003   _base.py:378(result)
        1    0.593    0.593    3.104    3.104   get_BP_Verteilung_Vektoren.py:1(get_BP_Verteilung_Vektoren)
        3    0.000    0.000    2.774    0.925   cursors.py:164(execute)
        3    0.000    0.000    2.774    0.925   cursors.py:353(_query)
        3    0.000    0.000    1.981    0.660   cursors.py:315(_do_query)
        3    0.000    0.000    1.970    0.657   cursors.py:142(_do_get_result)
        3    0.000    0.000    1.969    0.656   cursors.py:351(_get_result)
        3    1.969    0.656    1.969    0.656   {method 'store_result' of '_mysql.connection' objects}
        3    0.001    0.000    0.794    0.265 cursors.py:358(_post_get_result)  
        3    0.000    0.000    0.792    0.264   cursors.py:324(_fetch_row)
        3    0.792    0.264    0.792    0.264   {built-in method fetch_row}
   591314    0.144    0.000    0.144    0.000   {range}

2核机器,启动2个进程:

626072 function calls (625636 primitive calls) in 682.460 seconds

   Ordered by: cumulative time

   ncalls  tottime  percall  cumtime  percall   filename:lineno(function)
        1    0.334    0.334  682.461  682.461   20_with_multiprocessing.py:1(<module>)
        4    0.000    0.000  678.231  169.558   threading.py:309(wait)
       33  678.230   20.552  678.230   20.552   {method 'acquire' of 'thread.lock' objects}
        2    0.000    0.000  678.230  339.115   _base.py:378(result)
        1    0.527    0.527    2.974    2.974   get_BP_Verteilung_Vektoren.py:1(get_BP_Verteilung_Vektoren)
        3    0.000    0.000    2.723    0.908   cursors.py:164(execute)
        3    0.000    0.000    2.723    0.908   cursors.py:353(_query)
        3    0.000    0.000    1.749    0.583   cursors.py:315(_do_query)
        3    0.000    0.000    1.736    0.579   cursors.py:142(_do_get_result)
        3    0.000    0.000    1.736    0.579   cursors.py:351(_get_result)
        3    1.736    0.579    1.736    0.579   {method 'store_result' of '_mysql.connection' objects}
        3    0.001    0.000    0.975    0.325   cursors.py:358(_post_get_result)
        3    0.000    0.000    0.973    0.324   cursors.py:324(_fetch_row)
        3    0.973    0.324    0.973    0.324   {built-in method fetch_row}
        5    0.093    0.019    0.304    0.061   __init__.py:1(<module>)
        1    0.017    0.017    0.275    0.275   __init__.py:106(<module>)
        1    0.005    0.005    0.198    0.198   add_newdocs.py:10(<module>)
   591343    0.148    0.000    0.148    0.000   {range}

就我个人而言,我真的不知道如何处理这些结果。很乐意收到提示、提示或任何其他帮助-谢谢:)

回复答案1:(编辑)

Roland Smith查看了数据并提出,多处理可能会对性能造成更大的损害,而不是它的帮助。因此,我又做了一个没有多重处理的测量(就像他建议的代码):

我的结论是正确的吗?事实并非如此?因为测得的时间似乎和之前用多重处理法测得的时间相似?在

1核机器:

Database access took 2.53 seconds

Matrix manipulation took 236.71 seconds

1842384 function calls (1841974 primitive calls) in 241.114 seconds

   Ordered by: cumulative time

   ncalls  tottime  percall  cumtime  percall   filename:lineno(function)
        1  219.036  219.036  241.115  241.115   20_with_multiprocessing.py:1(<module>)
   406000    0.873    0.000   18.097    0.000   {method 'sum' of 'numpy.ndarray' objects}
   406000    0.502    0.000   17.224    0.000   _methods.py:31(_sum)
   406001   16.722    0.000   16.722    0.000   {method 'reduce' of 'numpy.ufunc' objects}
        1    0.587    0.587    3.222    3.222   get_BP_Verteilung_Vektoren.py:1(get_BP_Verteilung_Vektoren)
        3    0.000    0.000    2.964    0.988   cursors.py:164(execute)
        3    0.000    0.000    2.964    0.988   cursors.py:353(_query)
        3    0.000    0.000    1.958    0.653   cursors.py:315(_do_query)
        3    0.000    0.000    1.944    0.648   cursors.py:142(_do_get_result)
        3    0.000    0.000    1.944    0.648   cursors.py:351(_get_result)
        3    1.944    0.648    1.944    0.648   {method 'store_result' of '_mysql.connection' objects}
        3    0.001    0.000    1.006    0.335   cursors.py:358(_post_get_result)
        3    0.000    0.000    1.005    0.335   cursors.py:324(_fetch_row)
        3    1.005    0.335    1.005    0.335   {built-in method fetch_row}
   591285    0.158    0.000    0.158    0.000   {range}

2核机器:

Database access took 2.32 seconds

Matrix manipulation took 242.45 seconds

^{9}$

Tags: inpydbgettime进程时间function
1条回答
网友
1楼 · 发布于 2024-04-26 15:13:47

你的程序似乎花费了大部分时间来获取锁。这似乎表明,在您的情况下,多处理带来的伤害大于它的帮助。在

去掉所有的多处理的东西,开始测量没有它的东西需要多长时间。E、 像这样。在

from math import floor
from math import ceil
import numpy
import MySQLdb
import time

start = time.clock()
db = MySQLdb.connect(...)
cursor  = db.cursor()
query = "SELECT ...."
cursor.execute(query)
stop = time.clock()
print "Database access took {:.2f} seconds".format(stop - start)

start = time.clock()
[...]  #save db results into the variable db_matrix (30 columns, 5.000 rows)
[...]  #save db results into the variable bp_vector (3 columns, 500 rows)
[...]  #save db results into the variable option_vector( 3 columns, 4000 rows)
stop = time.clock()
print "Creating matrices took {:.2f} seconds".format(stop - start)
cursor.close()
db.close()

counter = 0 

start = time.clock()
for i in range(4000):
    for j in range(500):
         helper[:] = (1-bp_vector[j,0]-bp_vector[j,1]-bp_vector[j,2])*db_matrix[:,0] 
                     + db_matrix[:,option_vector[i,0]] * bp_vector[j,0]  
                     + db_matrix[:,option_vector[i,1]] * bp_vector[j,1]   
                     + db_matrix[:,option_vector[i,2]] * bp_vector[j,2]

         result[counter,0] = (helper < -7.55).sum()

         counter = counter + 1
stop = time.clock()
print "Matrix manipulation took {:.2f} seconds".format(stop - start)

编辑-1

根据您的测量结果,我支持我的结论(稍微重新措辞),即在多核机器上,像您现在所做的那样使用multiprocessing会极大地损害您的性能。在双核机器上,有多处理的程序比没有多处理器的程序要长得多!在

我认为,在使用单核机器时,使用多处理与否没有什么区别,这并不是很重要。不管怎样,单核机器不会从多处理中得到多少好处。在

新的测量结果表明,大部分时间都花在矩阵运算上。这是合乎逻辑的,因为您使用的是显式嵌套for循环,这不是很快。在

基本上有四种可能的解决办法

第一种方法是将嵌套循环重新写入numpy操作中。Numpy操作有隐式循环(用C编写),而不是Python中的显式循环,因此速度更快。(一种罕见的情况是显式比隐式差。缺点是这可能会占用大量内存。在

第二种选择是将helper的计算分成4部分。在一个单独的过程中执行每个部分,并在最后将结果相加。这确实会产生一些开销;每个进程都必须从数据库中检索所有数据,并且必须将部分结果传输回主进程(也可能通过数据库?)。在

第三种选择可能是使用pypy,而不是Cpython。它可以明显更快。在

第四种选择是用Cython或C重新编写关键矩阵操作

相关问题 更多 >