这个循环可以在纯Python中加速吗?

时间:2011-01-13 03:35:37

标签: python testing performance timing

我正在尝试使用Python进行实验,试图找出在一分钟的时间内将一个加到一个整数上的次数。假设除了CPU的速度之外,两台计算机是相同的,这应该估计一些CPU操作可能对所讨论的计算机的速度有多快。

以下代码是为满足上述要求而设计的测试示例。此版本比第一次尝试快20%,比第三次尝试快150%。任何人都可以提出任何建议,如何在一分钟的时间内获得最多的补充?更高的数字是可取的。

编辑1:此实验是用Python 3.1编写的,比第四次加速尝试快15%。

def start(seconds):
    import time, _thread
    def stop(seconds, signal):
        time.sleep(seconds)
        signal.pop()
    total, signal = 0, [None]
    _thread.start_new_thread(stop, (seconds, signal))
    while signal:
        total += 1
    return total

if __name__ == '__main__':
    print('Testing the CPU speed ...')
    print('Relative speed:', start(60))

编辑2:关于在while循环中使用True而不是1:应该没有速度差异。以下实验证明它们是相同的。首先,创建一个名为main.py的文件,并将以下代码复制到其中。

def test1():
    total = 0
    while 1:
        total += 1

def test2():
    total = 0
    while True:
        total += 1

if __name__ == '__main__':
    import dis, main
    dis.dis(main)

运行代码应该生成以下输出,该输出显示代码实际编译的方式以及生成的 Python虚拟机指令的结果。

Disassembly of test1:
  2           0 LOAD_CONST               1 (0) 
              3 STORE_FAST               0 (total) 

  3           6 SETUP_LOOP              13 (to 22) 

  4     >>    9 LOAD_FAST                0 (total) 
             12 LOAD_CONST               2 (1) 
             15 INPLACE_ADD          
             16 STORE_FAST               0 (total) 
             19 JUMP_ABSOLUTE            9 
        >>   22 LOAD_CONST               0 (None) 
             25 RETURN_VALUE         

Disassembly of test2:
  7           0 LOAD_CONST               1 (0) 
              3 STORE_FAST               0 (total) 

  8           6 SETUP_LOOP              13 (to 22) 

  9     >>    9 LOAD_FAST                0 (total) 
             12 LOAD_CONST               2 (1) 
             15 INPLACE_ADD          
             16 STORE_FAST               0 (total) 
             19 JUMP_ABSOLUTE            9 
        >>   22 LOAD_CONST               0 (None) 
             25 RETURN_VALUE         

发出的PVMI(字节代码)完全相同,因此两个循环应该在没有任何速度差异的情况下运行。

3 个答案:

答案 0 :(得分:3)

对于代码,我在Python 3.1.2上的the @Amber's one上看到的结果与my machine几乎相同,但总是更好(~2%):

import signal

class Alarm(Exception):
    pass

def alarm_handler(signum, frame):
    raise Alarm

def jfs_signal(seconds):
    # set signal handler
    signal.signal(signal.SIGALRM, alarm_handler)
    # raise Alarm in `seconds` seconds
    signal.alarm(seconds)

    total = 0
    try:
        while 1:
            total += 1
    finally:
        signal.alarm(0) # disable the alarm
        return total

这是使用subprocess模块运行可中断循环的变体:

#!/usr/bin/env python
# save it as `skytower.py` file
import atexit
import os
import signal
import subprocess
import sys
import tempfile
import time

def loop():
    @atexit.register
    def print_total():
        print(total)

    total = 0
    while 1:
        total += 1

def jfs_subprocess(seconds):
    # start process, redirect stdout/stderr
    f = tempfile.TemporaryFile() 
    p = subprocess.Popen([sys.executable, "-c",
                          "from skytower import loop; loop()"],
                         stdout=f, stderr=open(os.devnull, 'wb'))
    # wait 
    time.sleep(seconds)

    # raise KeyboardInterrupt
    #NOTE: if it doesn't kill the process then `p.wait()` blocks forever
    p.send_signal(signal.SIGINT) 
    p.wait() # wait for the process to terminate otherwise the output
             # might be garbled

    # return saved output
    f.seek(0) # rewind to the beginning of the file
    d = int(f.read())
    f.close()
    return d

if __name__ == '__main__':
    print('total:', jfs_subprocess(60))

比我机器上signal.alarm()的变体慢约20%。

答案 1 :(得分:2)

大约20-25%的改进,FWIW - 但和其他人一样,我建议Python递增整数可能不是最好的基准测试工具。

def start(seconds):
    import time, _thread
    def stop(seconds):
        time.sleep(seconds)
        _thread.interrupt_main()
    total = 0
    _thread.start_new_thread(stop, (seconds,))
    try:
        while True:
            total += 1
    except:
        return total

if __name__ == '__main__':
    print('Testing the CPU speed ...')
    print('Relative speed:', start(60))

答案 2 :(得分:0)

这项关于学习Python和计算机的更多练习令人满意。这是最终的计划:

def start(seconds, total=0):
    import _thread, time
    def stop():
        time.sleep(seconds)
        _thread.interrupt_main()
    _thread.start_new_thread(stop, ())
    try:
        while True:
            total += 1
    except KeyboardInterrupt:
        return total

if __name__ == '__main__':
    print('Testing the CPU speed ...')
    print('Relative speed:', start(60))

在具有2.16 GHz CPU的Windows 7 Professional上运行它在IDLE中产生了以下输出:

Python 3.1.3 (r313:86834, Nov 27 2010, 18:30:53) [MSC v.1500 32 bit (Intel)] 
on win32
Type "copyright", "credits" or "license()" for more information.
>>> ================================ RESTART ================================
>>> 
Testing the CPU speed ...
Relative speed: 673991388
>>> 

编辑:上面的代码只在一个核心上运行。编写以下程序来解决该问题。

#! /usr/bin/env python3

def main(seconds):
    from multiprocessing import cpu_count, Barrier, SimpleQueue, Process
    def get_all(queue):
        while not queue.empty():
            yield queue.get()
    args = seconds, Barrier(cpu_count()), SimpleQueue()
    processes = [Process(target=run, args=args) for _ in range(cpu_count())]
    for p in processes:
        p.start()
    for p in processes:
        p.join()
    print('Relative speed:', sorted(get_all(args[-1]), reverse=True))

def run(seconds, barrier, queue):
    from time import sleep
    from _thread import interrupt_main, start_new_thread
    def terminate():
        sleep(seconds)
        interrupt_main()
    total = 0
    barrier.wait()
    start_new_thread(terminate, ())
    try:
        while True:
            total += 1
    except KeyboardInterrupt:
        queue.put(total)

if __name__ == '__main__':
    main(60)