Python代码在每次迭代后变慢

1 投票
2 回答
4175 浏览
提问于 2025-04-18 14:36

我有一段代码,目的是对一组数据进行操作并存储结果。我的问题是,当我第一次运行这段代码时,每次循环(外层循环)大约需要12秒,但过了一段时间后,每次循环的时间变得越来越长,最后每次循环需要2分钟才能完成。我想知道我的代码哪里出了问题?这和我使用的内存大小以及数组的大小有关吗?如果是的话,我该怎么解决?如果不是,那问题出在哪里呢?

allclassifiers 是一个二维向量:allclassifiers.shape = tuple: (1020, 1629),这意味着我的循环应该执行1629次,每次对大小为1020的向量进行操作,每个向量是由0和1组成的数组。

最后,points_comb 的大小大约是400MB,points_comb.shape = tuple: (26538039, 2),我还得提一下,我是在一台有4GB内存的系统上运行这段代码。
valid_list 是一个大小为1020的0和1的向量,valid_list.shape tuple: (1020,)

startTime = datetime.now()

for i in range(allclassifiers.shape[1]):
    for j in range(allclassifiers.shape[1]):
        rs_t = combine_crisp(valid_list, allclassifiers[:,i], allclassifiers[:,j], boolean_function)
        fpr_tmp, tpr_tmp = resp2pts(valid_list, rs_t)
        points_comb = np.vstack((points_comb,np.hstack((fpr_tmp, tpr_tmp))))
    endTime = datetime.now()
    executionTime = endTime - startTime
    print "Combination for classifier: " + str(i)+ ' ' + str(executionTime.seconds / 60) + " minutes and " + str((executionTime.seconds) - ((executionTime.seconds / 60)*60)) +" seconds"


def combine_crisp(lab, r_1, r_2, fun):

    rs = np.empty([len(lab), len(fun)])
    k = 0
    for b in fun:
        if b == 1:            #----------------> 'A AND B'
            r12 = np.logical_and(r_1, r_2)
        elif b == 2:            #----------------> 'NOT A AND B'
            r12 = np.logical_not(np.logical_and(r_1, r_2))
        elif b == 3:            #----------------> 'A AND NOT B'
            r12 = np.logical_and(r_1, np.logical_not (r_2))
        elif b == 4:            #----------------> 'A NAND B'
            r12 = np.logical_not( (np.logical_and(r_1, r_2)))
        elif b == 5:            #----------------> 'A OR B'
            r12 = np.logical_or(r_1, r_2)
        elif b == 6:            #----------------> 'NOT A OR B'; 'A IMP B'
            r12 = np.logical_not (np.logical_or(r_1, r_2))
        elif b == 7:            #----------------> 'A OR NOT B' ;'B IMP A'
            r12 = np.logical_or(r_1, np.logical_not (r_2))
        elif b == 8:            #----------------> 'A NOR B'
            r12 = np.logical_not( (np.logical_or(r_1, r_2)))
        elif b == 9:            #----------------> 'A XOR B'
            r12 = np.logical_xor(r_1, r_2)
        elif b == 10:            #----------------> 'A EQV B'
            r12 = np.logical_not (np.logical_xor(r_1, r_2))
        else:
            print('Unknown Boolean function')

        rs[:, k] = r12
        k = k + 1


    return rs

def resp2pts(lab, resp):
    lab = lab > 0
    resp = resp > 0
    P = sum(lab)
    N = sum(~lab)
    if resp.ndim == 1:
        num_pts = 1
        tp = sum(lab[resp])
        fp = sum(~lab[resp])
    else:
        num_pts = resp.shape[1]
        tp = np.empty([num_pts,1])
        fp = np.empty([num_pts,1])
        for i in np.arange(num_pts):
            tp[i] = np.sum( lab[resp[:,i]]) 
            fp[i] = np.sum( ~lab[resp[:,i]])
    tp = np.true_divide(tp,P)
    fp = np.true_divide(fp,N)
    return fp, tp

2 个回答

2

我建议你在遇到这样的问题时,首先要做的事情。

使用性能分析工具。

通过使用性能分析工具,你可以找出(或者至少更接近)问题所在,哪些函数是导致问题的原因,以及应用程序是如何使用内存的。

所以,你可以先看看 Python的性能分析工具,这里还有一个很有趣的问题,关于 推荐哪个Python内存分析工具?

在我看来,一个非常好的起点是这个指南:分析Python性能的指南

你可以学到的一件事是,如何找出每行代码使用的内存量!!!

在这里输入图片描述

祝你好运!

2

我还没有实际测试过,但我很确定,速度变慢是因为反复使用了 np.vstack。你不能直接往一个 Numpy 数组里添加数据,所以如果你想增加数组的大小,就得先创建一个新的数组,然后把旧的数据和新数据都复制到这个新数组里。这个过程是需要时间的:

A = np.random.rand(1e7)
%timeit A.copy()
10 loops, best of 3: 119 ms per loop

你的 points_comb 数组在变大,所以复制它的时间也会越来越长。

解决办法是尽量少用数组的追加操作,或者使用 Python 列表(如果你事先不知道结果的大小)。

所以,不要这样做:

result = np.empty(shape=(0, N))

for i in some_iterable:
    for j in range(X):
        temp = somefunction(i,j)
        result = np.vstack(result, temp)

你可以试试这样做:

result = list()

for i in some_iterable:
    for j in range(X):
        temp = somefunction(i,j)
        result.append(temp)

result_np = np.vstack(result)

不过,列表可能会占用很多内存。如果你在循环之前就知道结果的大小,可以先分配好数组的空间,然后在数据可用时把内容复制进去:

result = np.empty(shape=(X**2, N))

for i in range(X):
    for j in range(X):
        temp = somefunction(i,j)
        result[i*j, :] = temp

这样你也是在复制,但每次只复制小块数据,所以速度还算快。不过,最好的办法是尽量把你的工作“向量化”(我并不是说这一定可行)。这样你就可以告别循环,做一些像这样的操作:

result = some_vectorized_function(X)

撰写回答