【问题标题】:Why are these two loops the exact same speed?为什么这两个循环的速度完全相同?
【发布时间】:2017-09-16 10:09:24
【问题描述】:
l = [1,0,0,1,1]

count = 0
start = time()
for _ in range(100000):
    for x in range(len(l)-1):
        for y in range(x+1, len(l)):
            if l[x] + l[y] == 1:
                count += 1
end = time()
count2 = 0
start2 = time()
for _ in range(100000):
    for x in range(len(l)-1):
        for y in range(x+1, len(l)):
            if l[x]^l[y]:
                count2 += 1
end2 = time()

print str(count) + ' : Add and compare took: ' + str((end - start)/100000)
print str(count2) + ' : Bitwise took: ' + str((end2 - start2)/100000)

根据我对按位运算的理解,它们应该比简单的比较更快。然而,这两个循环最终的速度完全相同(不过分挑剔)。

当整数比较看起来同样快时,使用可能更复杂的按位运算而不是整数比较有什么好处?

编辑:

看来操作码并非都是平等的。

Austin 的回答提到这两个操作之间的差异是 3 个操作码与 1 个操作码,但是,以下示例具有相同数量的操作码,但性能却显着不同:

i = j = 10

def test1():
    if i == j:
        print True

def test2():
    if not i-j:
        print True

print 'test 1'
start1 = time()
test1()
end1 = time()
dis(test1)
print 'test 2'
start2 = time()
test2()
end2 = time()
dis(test2)
print 'Test 1 took: ' + str(end1 - start1)
print 'Test 2 took: ' + str(end2 - start2)

这将输出:

test 1
True
 25           0 LOAD_GLOBAL              0 (i)
              3 LOAD_GLOBAL              1 (j)
              6 COMPARE_OP               2 (==)
              9 POP_JUMP_IF_FALSE       20

 26          12 LOAD_GLOBAL              2 (True)
             15 PRINT_ITEM          
             16 PRINT_NEWLINE       
             17 JUMP_FORWARD             0 (to 20)
        >>   20 LOAD_CONST               0 (None)
             23 RETURN_VALUE        
test 2
True
 29           0 LOAD_GLOBAL              0 (i)
              3 LOAD_GLOBAL              1 (j)
              6 BINARY_SUBTRACT     
              7 POP_JUMP_IF_TRUE        18

 30          10 LOAD_GLOBAL              2 (True)
             13 PRINT_ITEM          
             14 PRINT_NEWLINE       
             15 JUMP_FORWARD             0 (to 18)
        >>   18 LOAD_CONST               0 (None)
             21 RETURN_VALUE        
Test 1 took: 7.86781311035e-06
Test 2 took: 5.00679016113e-06

有没有更准确的衡量效率的方法?

编辑:

创建的操作码几乎相等。

修改代码以排除讨厌的 I/O 说明了 I/O 存在问题的原因。

i = j = 10
bool1 = False
bool2 = False

def test1():
    if i == j:
        bool1 = True

def test2():
    if not i-j:
        bool2 = True

print 'test 1'
start1 = time()
for _ in range(1000000):
    test1()
end1 = time()
dis(test1)
print 'test 2'
start2 = time()
for _ in range(1000000):
    test2()
end2 = time()
dis(test2)
print str(bool1) + ' : Test 1 took: ' + str(end1 - start1)
print str(bool2) + ' : Test 2 took: ' + str(end2 - start2)

将打印:

test 1
 27           0 LOAD_GLOBAL              0 (i)
              3 LOAD_GLOBAL              1 (j)
              6 COMPARE_OP               2 (==)
              9 POP_JUMP_IF_FALSE       21

 28          12 LOAD_GLOBAL              2 (True)
             15 STORE_FAST               0 (bool1)
             18 JUMP_FORWARD             0 (to 21)
        >>   21 LOAD_CONST               0 (None)
             24 RETURN_VALUE        
test 2
 31           0 LOAD_GLOBAL              0 (i)
              3 LOAD_GLOBAL              1 (j)
              6 BINARY_SUBTRACT     
              7 POP_JUMP_IF_TRUE        19

 32          10 LOAD_GLOBAL              2 (True)
             13 STORE_FAST               0 (bool2)
             16 JUMP_FORWARD             0 (to 19)
        >>   19 LOAD_CONST               0 (None)
             22 RETURN_VALUE        
False : Test 1 took: 0.156816959381
False : Test 2 took: 0.16281914711

所以没有那么激烈,但仍然略有不同。这运行了大约 12 次,而测试 1 只用了更长时间一次。

所以还是有一些谜团!只是没有那么激烈。

【问题讨论】:

  • if l[x] + l[y] == 1 不等同于if l[x]^l[y]
  • 功能上就是在这个例子中。最初的问题是为什么它们的速度相同,而不是它们是否相等。

标签: python loops optimization bitwise-operators


【解决方案1】:

您的循环中的所有代码都基本相同,因此我将其删除。相反,我将您的代码缩减为两个函数,并请我的好朋友 dis.dis 向我展示他们在做什么:

l = [1,0,0,1,1]

def f1():
    x = y = 0
    if l[x] + l[y] == 1:
        count += 1

def f2():
    x = y = 0
    if l[x]^l[y]:
        count2 += 1


import dis
print "F1"
dis.dis(f1)
print "F2"
dis.dis(f2)

这是输出:

$ python2.7 test.py
F1
  4           0 LOAD_CONST               1 (0)
              3 DUP_TOP
              4 STORE_FAST               0 (x)
              7 STORE_FAST               1 (y)

  5          10 LOAD_GLOBAL              0 (l)
             13 LOAD_FAST                0 (x)
             16 BINARY_SUBSCR
             17 LOAD_GLOBAL              0 (l)
             20 LOAD_FAST                1 (y)
             23 BINARY_SUBSCR
             24 BINARY_ADD                          ## Here.
             25 LOAD_CONST               2 (1)      ## Here.
             28 COMPARE_OP               2 (==)     ## Here.
             31 POP_JUMP_IF_FALSE       47

  6          34 LOAD_FAST                2 (count)
             37 LOAD_CONST               2 (1)
             40 INPLACE_ADD
             41 STORE_FAST               2 (count)
             44 JUMP_FORWARD             0 (to 47)
        >>   47 LOAD_CONST               0 (None)
             50 RETURN_VALUE
F2
  9           0 LOAD_CONST               1 (0)
              3 DUP_TOP
              4 STORE_FAST               0 (x)
              7 STORE_FAST               1 (y)

 10          10 LOAD_GLOBAL              0 (l)
             13 LOAD_FAST                0 (x)
             16 BINARY_SUBSCR
             17 LOAD_GLOBAL              0 (l)
             20 LOAD_FAST                1 (y)
             23 BINARY_SUBSCR
             24 BINARY_XOR                          ## Here.
             25 POP_JUMP_IF_FALSE       41

 11          28 LOAD_FAST                2 (count2)
             31 LOAD_CONST               2 (1)
             34 INPLACE_ADD
             35 STORE_FAST               2 (count2)
             38 JUMP_FORWARD             0 (to 41)
        >>   41 LOAD_CONST               0 (None)
             44 RETURN_VALUE

区别是 3 个操作码与 1 个操作码。这些操作的设置是 6 个操作码。差异消失在噪音中。

【讨论】:

  • 太棒了!这正是我所寻找的,而且我从未听说过“dis”。很酷!
  • @NickA 老实说,我对此表示怀疑。解释器不必在这些循环中做太多工作,因此所有内容都可能被缓存。
  • 如果没有任何不便,请看看我的编辑
  • 你违反了性能分析的第一条规则:不要做I/O!一旦您进行 IO,尤其是非缓冲 IO,您将在系统中引入大量可变性。如果你想用 I/O 进行测试,你需要担心缓冲、事件等。尝试写入一个文件,然后每个文件运行一百万次或更多次(以摆脱那个讨厌的 e-06 在计时中) .
  • 哦,你是对的,我将从时序分析中排除 I/O。
猜你喜欢
  • 2010-09-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-05-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多