Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/335.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python 一次加法需要多少CPU周期?_Python_Assembly_Clock_Timeit - Fatal编程技术网

Python 一次加法需要多少CPU周期?

Python 一次加法需要多少CPU周期?,python,assembly,clock,timeit,Python,Assembly,Clock,Timeit,我想测量在Python3中执行加法操作所需的时钟周期数 我编写了一个程序来计算加法运算的平均值: 从timeit导入timeit def测试(n): 对于范围(n)中的i: 1 + 1 如果uuuu name uuuuuu='\uuuuuuu main\uuuuuuu': 时间={} 对于[2**n中的i,范围(10)]中的n: t=timeit.timeit(“测试(%d)”%i,setup=“从主导入测试”,编号=100000) 乘以[i]=t 打印(“%d个添加项占%f”%(i,t)) k

我想测量在Python3中执行加法操作所需的时钟周期数

我编写了一个程序来计算加法运算的平均值:

从timeit导入timeit
def测试(n):
对于范围(n)中的i:
1 + 1
如果uuuu name uuuuuu='\uuuuuuu main\uuuuuuu':
时间={}
对于[2**n中的i,范围(10)]中的n:
t=timeit.timeit(“测试(%d)”%i,setup=“从主导入测试”,编号=100000)
乘以[i]=t
打印(“%d个添加项占%f”%(i,t))
keys=已排序(列表(times.keys())
对于范围内的i(透镜(键)-2):
打印(“1次添加需要%f”%((次[键[i+1]]-次[键[i]])/(键[i+1]-键[i]))
输出:

16 additions takes 0.288647
32 additions takes 0.422229
64 additions takes 0.712617
128 additions takes 1.275438
256 additions takes 2.415222
512 additions takes 5.050155
1024 additions takes 10.381530
2048 additions takes 21.185604
4096 additions takes 43.122559
8192 additions takes 88.323853
16384 additions takes 194.353927
1  addition takes 0.008292
1 addition takes 0.010068
1 addition takes 0.008654
1 addition takes 0.010318
1 addition takes 0.008349
1 addition takes 0.009075
1 addition takes 0.008794
1 addition takes 0.008905
1 addition takes 0.010293
1 addition takes 0.010413
1 addition takes 0.010551
1 addition takes 0.010711
1 addition takes 0.011035

因此,根据这个输出,一次添加大约需要0.0095 usecs。按照说明,我计算出一次加法需要25个CPU周期。这是正常值吗?为什么?因为汇编指令ADD只需要1-2个CPU周期。

您正在计时函数调用(
test()
)、
循环以及对
范围()的调用。添加完全没有计时

def test(n):
    for i in range(n):
        1 + 1

import dis
dis.dis(test)
以下是测试函数的字节码(不包括对
test()
)的调用):

****注意,添加是在编译时完成的。很多其他语言及其编译器都会这样做,包括C语言。但是,标准很少定义何时实际完成
1+1
,因此通常依赖于实现

编辑

您的
timeit
函数调用可以是:

    t = timeit("x += 1", setup="x = 1", number=100000)
我们可以创建一个虚拟函数来检查操作:

def myfunc(x):
    x += 1

import dis
dis.dis(myfunc)
做出这样的改变会带来:

1 additions takes 0.008976
2 additions takes 0.007419
4 additions takes 0.007282
8 additions takes 0.007693
16 additions takes 0.007026
32 additions takes 0.007793
64 additions takes 0.010168
128 additions takes 0.008124
256 additions takes 0.009064
512 additions takes 0.007256
1 addition takes -0.001557
1 addition takes -0.000068
1 addition takes 0.000103
1 addition takes -0.000083
1 addition takes 0.000048
1 addition takes 0.000074
1 addition takes -0.000032
1 addition takes 0.000007

 26           0 LOAD_FAST                0 (x)
              3 LOAD_CONST               1 (1)
              6 INPLACE_ADD
              7 STORE_FAST               0 (x)
             10 LOAD_CONST               0 (None)
             13 RETURN_VALUE

请注意,
x+=1
是一个
in-place\u-ADD
,与
x=x+1
不同,后者是一个
BINARY\u-ADD
。因此,您需要决定要测量的操作码。

通过使用
dis
模块,您可以更深入地了解幕后的情况

具体地说,
dis.dis
函数获取编译后的Python代码片段,并返回该片段被解释为的字节码。在1+1的情况下:

In [1]: import dis

In [2]: def add1and1():
    return 1 + 1

In [3]: dis.dis(add1and1)
  2           0 LOAD_CONST               2 (2)
              3 RETURN_VALUE 
因此在这种情况下,当源代码被编译成字节码时,操作
1+1
只执行一次,然后结果存储为常量。我们可以通过返回传递给函数的参数之和来解决此问题:

In [1]: import dis

In [2]: def add(x, y):
    return x + y

In [3]: dis.dis(add)
      2           0 LOAD_FAST                0 (x)
              3 LOAD_FAST                1 (y)
              6 BINARY_ADD          
              7 RETURN_VALUE          
因此,您真正感兴趣的字节码指令是
BINARY\u ADD
。如果您想了解更多信息,可以在CPython解释器的
ceval.c
文件()中找到相关部分:

因此,这里发生的事情比你最初预期的要多。我们有:

  • 一个条件,用于确定是使用
    BINARY\u ADD
    进行字符串连接还是添加数字类型

  • PyNumber\u Add
    的实际调用,在这里人们可能会期待更多的东西,如
    left+right


  • 这两点都可以用Python的动态特性来解释;由于Python在实际调用
    add
    之前不知道
    x
    y
    的类型,因此类型检查是在运行时而不是编译时完成的。可以用动态语言进行巧妙的优化来解决这个问题(请参阅V8 for JavaScript或PyPy for Python),但一般来说,这是为解释的动态类型语言的灵活性所付出的代价。

    此外,这种添加甚至没有发生,因为它已经优化了。ok。但是,如果我运行的加法超过1000次,它不会影响我猜for循环inside
    test
    肯定会影响计时,而不管迭代次数如何。你计算循环一次迭代(“1次加法”)的时间的代码是错误的。它应该是
    乘以[keys[i]]/keys[i]
    。您计算的0.0095秒对应的循环数也是错误的。运行在2 GHz的CPU在0.0095秒内执行19000000个周期。Python很慢,非常慢。它的操作要比同等的汇编指令长几个数量级。@ross,噢,这只是一个打字错误。我指的是usec而不是sec。
    ((times[keys[I+1]]]-times[keys[I]])/(keys[I+1]-keys[I])
    这段代码没有错。通过划分三角洲,我可以调整计算的精度。谢谢,现在我明白了。但是有什么方法可以更准确地计算加法的时间吗?你想达到什么目的?在高级面向对象语言中调用
    +
    时,将两个对象添加到一起,
    +
    涉及函数调用。有些语言,如java和C++,使用了所谓的“原语”来表示整数,它们根本不是对象。在python中,您可以获得相当大的灵活性。高密度的数字运算Python模块,如
    numpy
    ,主要出于性能原因,使用C扩展而不是本地Python进行计算。测量的目的是什么?您还应该意识到Python进行了其他优化。如果你正在编写这种低级测试,你应该检查字节码是否真的在做你期望的事情。请参阅我的编辑以获得建议,并在我的评论中给出所有的注意事项。
    In [1]: import dis
    
    In [2]: def add(x, y):
        return x + y
    
    In [3]: dis.dis(add)
          2           0 LOAD_FAST                0 (x)
                  3 LOAD_FAST                1 (y)
                  6 BINARY_ADD          
                  7 RETURN_VALUE          
    
    TARGET(BINARY_ADD) {
        PyObject *right = POP();
        PyObject *left = TOP();
        PyObject *sum;
        if (PyUnicode_CheckExact(left) &&
                 PyUnicode_CheckExact(right)) {
            sum = unicode_concatenate(left, right, f, next_instr);
            /* unicode_concatenate consumed the ref to v */
        }
        else {
            sum = PyNumber_Add(left, right);
            Py_DECREF(left);
        }
        Py_DECREF(right);
        SET_TOP(sum);
        if (sum == NULL)
            goto error;
        DISPATCH();
    }