效率与可读性:使用嵌套布尔索引数组时的混淆

2024-09-27 07:26:09 发布

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

我有一些很难看的索引正在进行。例如

valid[ data[ index[valid[:,0],0] ] == 0, 1] = False

其中validindex分别是{Nx2}数组或bools和ints,并且data是{N}长的。你知道吗

如果我真的很努力,我可以说服自己,这是我想做的。。。但它令人难以置信的混乱。我怎样才能有效地解开这样的谜团?

我可以打破它,例如:

valid_index = index[valid[:,0],0]
invalid_index = (data[ valid_index ] == 0)
valid[ invalid_index, 1 ] = False

但是我的数组将有多达1亿个条目,所以我不想复制内存;我需要尽可能保持速度效率。你知道吗


Tags: 内存falsedataindex条目数组速度效率
1条回答
网友
1楼 · 发布于 2024-09-27 07:26:09

这两个代码序列几乎相同,应该具有非常相似的性能。这是我的“直觉”,但后来我做了静态分析,运行了部分基准来确认。你知道吗

更清晰的选项需要另外四个字节码来实现,因此可能会稍微慢一点。但是额外的工作仅限于LOAD_FASTSTORE_FAST,它们只是从栈顶(TOS)到变量的移动。由于额外的工作是适度的,所以应该是性能影响。你知道吗

您可以在目标设备上对这两种方法进行基准测试,以获得更高的定量精度,但在我3岁的笔记本电脑上,在标准cpython2.7.5上,额外的1亿对LOAD_FAST/STORE_FAST只需3秒多。因此,我估计每100米参赛者的清晰度大约需要6秒。虽然PyPy即时Python编译器不使用相同的字节码,但我将清除版本的开销定为大约一半,即每100M 3秒。与处理项目的其他工作相比,清除版本可能不是一个重要的决战。你知道吗

TL;Backstory博士

我的第一印象是,虽然代码序列在可读性和清晰度方面有所不同,但在技术上非常相似,不应具有类似的性能特征。但是让我们使用Python反汇编程序进一步分析一下。我将每个代码片段放入一个函数中:

def one(valid, data):
    valid[ data[ index[valid[:,0],0] ] == 0, 1] = False

def two(valid, data):
    valid_index = index[valid[:,0],0]
    invalid_index = (data[ valid_index ] == 0)
    valid[ invalid_index, 1 ] = False

然后使用Python's bytecode dissassember

import dis
dis.dis(one)
print " -"
dis.dis(two)

提供:

15           0 LOAD_GLOBAL              0 (False)
             3 LOAD_FAST                0 (valid)
             6 LOAD_FAST                1 (data)
             9 LOAD_GLOBAL              1 (index)
            12 LOAD_FAST                0 (valid)
            15 LOAD_CONST               0 (None)
            18 LOAD_CONST               0 (None)
            21 BUILD_SLICE              2
            24 LOAD_CONST               1 (0)
            27 BUILD_TUPLE              2
            30 BINARY_SUBSCR       
            31 LOAD_CONST               1 (0)
            34 BUILD_TUPLE              2
            37 BINARY_SUBSCR       
            38 BINARY_SUBSCR       
            39 LOAD_CONST               1 (0)
            42 COMPARE_OP               2 (==)
            45 LOAD_CONST               2 (1)
            48 BUILD_TUPLE              2
            51 STORE_SUBSCR        
            52 LOAD_CONST               0 (None)
            55 RETURN_VALUE        

18           0 LOAD_GLOBAL              0 (index)
             3 LOAD_FAST                0 (valid)
             6 LOAD_CONST               0 (None)
             9 LOAD_CONST               0 (None)
            12 BUILD_SLICE              2
            15 LOAD_CONST               1 (0)
            18 BUILD_TUPLE              2
            21 BINARY_SUBSCR       
            22 LOAD_CONST               1 (0)
            25 BUILD_TUPLE              2
            28 BINARY_SUBSCR       
            29 STORE_FAST               2 (valid_index)

19          32 LOAD_FAST                1 (data)
            35 LOAD_FAST                2 (valid_index)
            38 BINARY_SUBSCR       
            39 LOAD_CONST               1 (0)
            42 COMPARE_OP               2 (==)
            45 STORE_FAST               3 (invalid_index)

20          48 LOAD_GLOBAL              1 (False)
            51 LOAD_FAST                0 (valid)
            54 LOAD_FAST                3 (invalid_index)
            57 LOAD_CONST               2 (1)
            60 BUILD_TUPLE              2
            63 STORE_SUBSCR        
            64 LOAD_CONST               0 (None)
            67 RETURN_VALUE        

相似但不完全相同,顺序也不相同。一个快速的diff of the two也显示了同样的情况,而且更清晰的函数可能需要更多的字节码。你知道吗

我从每个函数的反汇编程序列表中解析字节码操作码,将它们放入collections.Counter,并比较计数:

Bytecode       Count(s) 
========       ======== 
BINARY_SUBSCR  3        
BUILD_SLICE    1        
BUILD_TUPLE    3        
COMPARE_OP     1        
LOAD_CONST     7        
LOAD_FAST      3, 5     *** differs ***
LOAD_GLOBAL    2        
RETURN_VALUE   1        
STORE_FAST     0, 2     *** differs ***
STORE_SUBSCR   1   

很明显,第二种更清晰的方法只使用了四个字节码,而且是简单、快速的LOAD_FAST/STORE_FAST类型。因此,静态分析没有特别的理由担心额外的内存分配或其他破坏性能的副作用。你知道吗

然后我构造了两个非常相似的函数,反汇编程序显示的不同之处只是第二个函数有一个额外的LOAD_FAST/STORE_FAST对。我运行了100000000次,比较了它们的运行时间。它们在cpython2.7.5中相差3秒多,在pypy2.2.1(基于python2.7.3)中相差1.5秒左右。即使您将这些时间翻了一番(因为您有两对),很明显,这些额外的加载/存储对也不会让您慢很多。你知道吗

相关问题 更多 >

    热门问题