3

我正在尝试在 Python 中创建一个二维列表。我发现了两种可能。

def cArray(size):
    c = [[0. for i in range(size)] for j in range(size)]
    return c

def npArray(size):
    np = numpy.zeros((size,size))
    return np

现在这两个函数都给出了正确的答案。这里的问题在于性能。我使用 timeit 运行了这两个,这是我的结果:

list size is 5000
number of times run is 5

cArray average time: 3.73241295815
npArray average time: 0.210782241821

所以很明显,我想避免第一个解决方案,特别是因为这将运行高达 100k 的大小。但是,我也不想使用太多的依赖项。我有没有办法在没有 numpy 的情况下有效地创建二维数组?它不需要完全跟上速度,只要不慢 17 倍即可。

4

4 回答 4

5

所以很明显,我想避免第一个解决方案,特别是因为这将运行高达 100k 的大小。但是,我也不想使用太多的依赖项。

您必须选择其中哪个对您更重要。Numpy 具有更好的性能正是因为它不使用内置的 Python 类型,而是使用自己的类型,这些类型针对数值工作进行了优化。如果您的数据将是数字数据并且您将拥有 100k 行/列,那么您将看到使用 numpy. 如果你想避免 numpy 依赖,你将不得不忍受性能下降。(显然,您总是可以编写自己的 Python 库或 C 扩展来针对您的特定用例进行优化,但是这些将像其他任何依赖项一样成为依赖项。)

我个人建议你只使用 numpy。它使用如此广泛,以至于任何正在考虑使用处理 100k 多维数组的库的人都可能已经安装了 numpy。

于 2012-08-22T18:57:40.820 回答
4

我尝试了几种选择;

编辑:原来的 eArray 有问题,它创建了对同一个列表的引用......

Edit2:按照塞巴斯蒂安的建议添加array.array

import time
import numpy as np
import array

t1 = 0

def starttimer():
    global t1
    t1 = time.clock()

def stoptimer(s):
    t2 = time.clock()
    print 'elapsed time for "{}": {:.3f} seconds'.format(s, t2-t1)

def cArray(size):
    c = [[0. for i in range(size)] for j in range(size)]
    return c

def dArray(size):
    d = [[0. for i in xrange(size)] for j in xrange(size)]
    return d

def eArray2(size):
    return [[0.]*size for j in xrange(size)]

def fArray(size):
    return np.zeros((size,size))

def gArray(size):
    return [array.array('d', [0])*size for j in xrange(size)]

sz = 5000

starttimer()
cArray(sz)
stoptimer('cArray')

starttimer()
dArray(sz)
stoptimer('dArray')

starttimer()
fArray(sz)
stoptimer('fArray')

starttimer()
gArray(sz)
stoptimer('gArray')

结果(FreeBSD amd64 上的 cpython 2.7.3,如果有人关心的话):

> python tmp/test.py
elapsed time for "cArray": 2.312 seconds
elapsed time for "dArray": 1.945 seconds
elapsed time for "eArray2": 0.680 seconds
elapsed time for "fArray": 0.180 seconds
elapsed time for "gArray": 0.695 seconds
> python tmp/test.py
elapsed time for "cArray": 2.312 seconds
elapsed time for "dArray": 1.914 seconds
elapsed time for "eArray2": 0.680 seconds
elapsed time for "fArray": 0.180 seconds
elapsed time for "gArray": 0.695 seconds
> python tmp/test.py
elapsed time for "cArray": 2.328 seconds
elapsed time for "dArray": 1.906 seconds
elapsed time for "eArray2": 0.680 seconds
elapsed time for "fArray": 0.180 seconds
elapsed time for "gArray": 0.703 seconds
于 2012-08-22T18:49:03.533 回答
2

如果你想达到非常低的水平,你可以使用ctypes

a = (ctypes.c_int * 5000 * 5000)()

但是 NumPy 在运行 Python 的大多数平台上都可用。

于 2012-08-22T18:51:19.833 回答
1

我的用例是在一些在线判断平台上,库依赖是有限的,但是在做动态规划时需要二维数组(也很难向量化)。我那里的python代码经常超过时间限制。

有几点要提醒:

  1. 尽管 python list 是一个指针数组,但天真的对象非常快。

  2. 在创建对象时使用像 numpy 这样的紧凑结构可能会很快,但访问元素会花费额外的开销,这与天真的 python 对象不同,

  3. 除非使用像Numba这样的 JIT 东西。

使用玩具 DP 示例测试代码:

import timeit

size = 1000

range_init_0 = "size={}".format(size)
range_init_1 = "a = [[0 for i in range(size)] for j in range(size)]"

multi_init_0 = "size={}".format(size)
multi_init_1 = "a = [[0]*size for _ in range(size)]"

numpy_init_0 = "from numpy import zeros; size={}".format(size)
numpy_init_1 = "a = zeros((size, size), dtype=int)"

array_init_0 = "from array import array; size={}".format(size)
array_init_1 = "a = [array('d', [0])*size for j in range(size)]"

ctyps_init_0 = "from ctypes import c_int; size={}".format(size)
ctypes_init_1 = "a = (c_int * size * size)()"

dp = '''
MOD = int(1e9+7)
for i in range(size):
    a[i][0] = 1
for j in range(size):
    a[0][i] = 1
for i in range(1, size):
    for j in range(1, size):
        a[i][j] = (a[i][j] + a[i-1][j] + a[i][j-1]) % MOD
'''

def test(name, init_0, init_1, dp, n=10):
    t = timeit.timeit(init_1, setup=init_0, number=n)
    print("{} initial time:\t{:.8f}".format(name, t))
    t = timeit.timeit(dp, setup=init_0 + '\n' + init_1, number=n)
    print("{} calculate time:\t{:.8f}".format(name, t))

test("range", range_init_0, range_init_1, dp)
test("multi", multi_init_0, multi_init_1, dp)
test("numpy", numpy_init_0, numpy_init_1, dp)
test("array", array_init_0, array_init_1, dp)
test("ctypes", ctyps_init_0, ctypes_init_1, dp)

print('------')
numba_init_0 = '''
import numpy as np
size = {}
a = np.zeros((size, size), dtype=np.int32)
'''.format(size)
numba_init_1 = '''
import numba
def dp1(a):
    size = len(a)
    MOD = int(1e9+7)
    for i in range(size):
        a[i][0] = 1
    for j in range(size):
        a[0][i] = 1
    for i in range(1, size):
        for j in range(1, size):
            a[i][j] = (a[i][j] + a[i-1][j] + a[i][j-1]) % MOD
dp_jit = numba.jit('void(i4[:,:])')(dp1)
'''
dp = "dp_jit(a)"

test("numba", numba_init_0, numba_init_1, dp)

结果:

range initial time:     0.56781153
range calculate time:   5.08359793
multi initial time:     0.03682878
multi calculate time:   5.14657282
numpy initial time:     0.00883761
numpy calculate time:   12.15619322
array initial time:     0.02656035
array calculate time:   5.27542352
ctypes initial time:    0.00523795
ctypes calculate time:  7.88469346
------
numba initial time:     2.98394509
numba calculate time:   0.05321887

(这里的numba初始化时间不包括numpy初始化)

正如我们所见,numpy 和 ctypes 在计算时都比原生列表慢。

Numba JIT 需要一些时间,但计算时间明显更短。

(并且不要在在线判断平台上使用 Python 进行 2D 动态编程!)

于 2018-11-11T10:37:21.157 回答