python - 效率与可读性 : obfuscation when using nested boolean index arrays

标签 python arrays numpy readability

我正在进行一些非常丑陋的索引。例如,诸如此类的事情

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

其中 validindex 分别是 {Nx2} 数组或 boolint,并且 data 长度为 {N}。

如果我非常努力地集中注意力,我可以说服自己这正在做我想做的事......但它非常困惑。 如何有效地消除此类内容的混淆?

我可以将其分解,例如:

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

但是我的数组将有多达数百百万个条目,所以我不想重复内存;我需要尽可能保持速度效率。

最佳答案

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

更清晰的选项需要另外四个字节码来实现,因此可能会稍微慢一些。但额外的工作仅限于 LOAD_FASTSTORE_FAST,它们只是从堆栈顶部 (TOS) 到变量/从变量移动。由于额外的工作量不大,因此对性能的影响也应该不大。

您可以在目标设备上对这两种方法进行基准测试,以获得更高的定量精度,但在我用了 3 年的笔记本电脑上,需要额外进行 1 亿个 LOAD_FAST/STORE_FAST 对在标准 CPython 2.7.5 上只需 3 秒多一点。因此,我估计这种清晰度每 100M 条目将花费大约 6 秒的时间。而PyPy即时 Python 编译器不使用相同的字节码,我将清晰版本的开销计时为大约一半,即每 100M 3 秒。与您处理这些元素所做的其他工作相比,更清晰的版本可能并不是一个重要的摊牌。

TL;DR 背景故事

我的第一印象是,代码序列虽然在可读性和清晰度方面有所不同,但在技术上非常相似,并且不应具有相似的性能特征。但让我们使用 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 对。我运行了它们 100,000,000 次,并比较了它们的运行时间。它们在 CPython 2.7.5 中仅相差 3 秒多一点,在 PyPy 2.2.1(基于 Python 2.7.3)下相差约 1.5 秒。即使您将这些时间加倍(因为您有两对),很明显这些额外的加载/存储对也不会减慢您的速度。

关于python - 效率与可读性 : obfuscation when using nested boolean index arrays,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26446302/

相关文章:

python - 属性错误: 'function' object has no attribute 'encode' error when writing to MySQL database

python - 用于限制对象中属性类型的函数(Python、mypy)

ruby - 等效于 Ruby 中 Set 的数组 join()?

python - Numpy:连接多维和一维数组

python - 带正则化的 Numpy 线性回归

python - Azure DataBricks导入错误: cannot import name dataclass_transform

javascript - AngularJS 数字数组的复选框

python - 如何将 theano.tensor 转换为 numpy.array?

python - 如何在没有 lambda 的情况下对带有 pandas 的列中的外观求和

python - "days += 1"在 Python 中是什么意思?