如何在python中加速numpy数组填充?

时间:2011-04-15 22:59:25

标签: python optimization numpy

我正在尝试使用以下代码填充预分配的bytearray:

# preallocate a block array
dt = numpy.dtype('u8')
in_memory_blocks = numpy.zeros(_AVAIL_IN_MEMORY_BLOCKS, dt)

...

# write all the blocks out, flushing only as desired
blocks_per_flush_xrange = xrange(0, blocks_per_flush)
for _ in xrange(0, num_flushes):
    for block_index in blocks_per_flush_xrange:
        in_memory_blocks[block_index] = random.randint(0, _BLOCK_MAX)

    print('flushing bytes stored in memory...')

    # commented out for SO; exists in actual code
    # removing this doesn't make an order-of-magnitude difference in time
    # m.update(in_memory_blocks[:blocks_per_flush])

    in_memory_blocks[:blocks_per_flush].tofile(f)

有些观点:

  • num_flushes很低,约为4 - 10
  • blocks_per_flush是一个大数字,大约数百万
  • in_memory_blocks可以是一个相当大的缓冲区(我将其设置为低至1MB且高达100MB)但是时间非常合理......
  • _BLOCK_MAX是8字节无符号整数
  • 的最大值
  • mhashilib.md5()

使用上面的代码生成1MB需要〜1s; 500MB需要~376s。相比之下,我使用rand()的简单C程序可以在8s内创建一个500MB的文件。

如何在上述循环中提高性能?我很确定我忽略了一些显而易见的事情,这会导致运行时间的巨大差异。

4 个答案:

答案 0 :(得分:7)

由于0.._BLOCK_MAX涵盖numpy.uint8的所有可能值(我假设numpy.dtype('u8')(即numpy.uint64是拼写错误),您可以使用:< / p>

import numpy as np

for _ in xrange(0, num_flushes):
    in_memory_blocks = np.frombuffer(np.random.bytes(blocks_per_flush),
                                     dtype=np.uint8)

    print('flushing bytes stored in memory...')
    # ...

此变体比@hgomersall's one快〜8倍:

$ python -mtimeit -s'import numpy as np' '
>     np.uint8(np.random.randint(0,256,20000000))'
10 loops, best of 3: 316 msec per loop

$ python -mtimeit -s'import numpy as np' '
>     np.frombuffer(np.random.bytes(20000000), dtype=np.uint8)'
10 loops, best of 3: 38.6 msec per loop

如果numpy.dtype('u8')不是拼写错误,而您确实需要numpy.uint64,那么:

a = np.int64(np.random.random_integers(0, _BLOCK_MAX, blocks_per_flush))
in_memory_blocks = a.view(np.uint64) # unsigned

注意:如果数组的dtype已经是np.int64(),则np.int64不会复制。 .view(numpy.uint64)强制将其解释为无符号(也不执行复制)。

答案 1 :(得分:4)

由于您正在分配连续的块,您应该能够执行以下操作(完全摆脱内部循环):

for _ in xrange(0, num_flushes):
    in_memory_blocks[:blocks_per_flush] = numpy.random.randint(
            0, _BLOCK_MAX+1, blocks_per_flush)

    print('flushing bytes stored in memory...')

    # commented out for SO; exists in actual code
    # removing this doesn't make an order-of-magnitude difference in time
    # m.update(in_memory_blocks[:blocks_per_flush])

    in_memory_blocks[:blocks_per_flush].tofile(f)

这使用numpy.random.randint函数,该函数分配整个内存块并用随机整数填充(注意J.F.Sebastian关于numpy.random.randintrandom.randint的评论。使用numpy随机例程填充预分配的数组是没有办法(据我所知)。另一个问题是numpy的randint返回int64数组。如果你需要一些其他大小的整数,那么你可以使用numpy类型方法,例如numpy.uint8。如果你想要randints覆盖整个类型的范围,那么下面使用numpy.random.bytes的@J. F. Sebastian方法将是最好的(几乎在任何情况下都是如此!)。

然而,简单测试显示合理的时间(与C代码的数量级相同)。以下代码使用numpy方法测试分配20,000个随机整数的uint8数组的时间:

from timeit import Timer
t = Timer(stmt='a=numpy.uint8(numpy.random.randint(0, 100, 20000000))',
        setup='import numpy')
test_runs = 50
time = t.timeit(test_runs)/test_runs
print time

我在我4岁的Core2笔记本电脑上每次分配大约需要0.7秒(它运行50次,因此运行整个测试需要更长的时间)。这是每次分配20,000,000个随机uint8整数的0.7秒,所以我希望整个500MB大约20秒左右。

更多内存意味着您可以同时分配更大的块,但是当您只需要8时,您仍然有效地浪费时间为每个int分配和写入64位(我没有量化此效果)。如果它仍然不够快,你可以使用numpy ctypes接口调用你的C实现。这真的很容易使用,你几乎没有比纯C慢。

一般的回家消息是,numpy总是尝试使用它们存在的numpy例程,记住用ctypes回退到C并不是太痛苦。一般来说,这种方法可以非常有效地使用python,而且数值处理的速度很慢。

编辑:我刚刚遇到的其他事情:正如上面所实现的那样,我认为你会制作一个额外的不必要副本。如果in_memory_blocks长度为blocks_per_flush,那么您最好只为其分配numpy.random.randint的返回值,而不是将其分配给某个子数组(在一般情况下为必须是副本)。所以:

in_memory_blocks = numpy.random.randint(0, _BLOCK_MAX+1, blocks_per_flush)

而不是:

in_memory_blocks[:blocks_per_flush] = numpy.random.randint(
        0, _BLOCK_MAX+1, blocks_per_flush)

然而,考虑到时间,第一种情况并没有导致速度显着提高(仅约2%),因此可能不值得担心太多。我想绝大部分时间花在实际生成随机数上(这是我所期望的)。

答案 2 :(得分:0)

如果您只是尝试填充文件,一次阻塞块数字,这可能比以前的答案更快。基于生成器并完全绕过阵列创建:

import numpy as np

def random_block_generator(block_size):
    while True:
        yield np.random.bytes(block_size)

rbg = random_block_generator(BLOCK_SIZE)

然后您的用法是:

f = open('testfile.bin','wb')

for _ in xrange(blocks_to_write):
    f.write( rbg.next())

f.close()

Numpy使用确定性随机数生成(序列中的下一个数字始终相同,它只是在初始化时在随机位置开始)。如果您需要真正的随机数据(加密等级),那么您可以使用import Crypto.Random as cryield cr.get_random_bytes(block_size)代替np。

另外,如果你的BLOCK_SIZE是一个定义的常量,你可以使用这样的生成器表达式(这次使用Crypto库):

import Crypto.Random as cr
from itertools import repeat

BLOCK_SIZE = 1000

rbg = (cr.get_random_bytes(BLOCK_SIZE) for _ in repeat(0))

f = open('testfile.bin','wb')

for _ in xrange(blocks_to_write):
    f.write( rbg.next())

f.close()

包括实施rbg=...和执行。这种生成器方法,即使使用稍慢的Crypto.Random,也会在最大化计算之前从磁盘i / o中最大化(尽管我确定其他答案也是如此)。

更新:

Athlon X2 245上的一些时序数据 -

  • 加密:生成500MB,不写 - 10.8s(46 MB / s)
  • 加密:生成500MB并写入 - 11.2s(44.5 MB / s)
  • Numpy:生成500MB,不写 - 1.4s(360 MB / s)
  • Numpy:生成500MB,写入 - 7.1s(70 MB / s)

因此numpy版本的速度提高了约8倍(很快就足以使我的旧式拼盘驱动器最大化)。我使用生成器表达式而不是生成器函数形式测试了它们。

答案 3 :(得分:-1)

我不擅长优化,但我没有看到你的代码可以更快地运行的方式。您使用的是纯粹的迭代器和O(1)访问结构。

我认为这个问题是你选择的语言。请记住,您正在虚拟机中运行,并在此处使用解释器。你的C程序总是会快一个数量级。