use*_*884 7 python multithreading cpython gil
我试图了解CPython的GIL如何工作以及CPython 2.7.x和CPython 3.4.x中GIL之间的区别是什么.我正在使用此代码进行基准测试:
from __future__ import print_function
import argparse
import resource
import sys
import threading
import time
def countdown(n):
while n > 0:
n -= 1
def get_time():
stats = resource.getrusage(resource.RUSAGE_SELF)
total_cpu_time = stats.ru_utime + stats.ru_stime
return time.time(), total_cpu_time, stats.ru_utime, stats.ru_stime
def get_time_diff(start_time, end_time):
return tuple((end-start) for start, end in zip(start_time, end_time))
def main(total_cycles, max_threads, no_headers=False):
header = ("%4s %8s %8s %8s %8s %8s %8s %8s %8s" %
("#t", "seq_r", "seq_c", "seq_u", "seq_s",
"par_r", "par_c", "par_u", "par_s"))
row_format = ("%(threads)4d "
"%(seq_r)8.2f %(seq_c)8.2f %(seq_u)8.2f %(seq_s)8.2f "
"%(par_r)8.2f %(par_c)8.2f %(par_u)8.2f %(par_s)8.2f")
if not no_headers:
print(header)
for thread_count in range(1, max_threads+1):
# We don't care about a few lost cycles
cycles = total_cycles // thread_count
threads = [threading.Thread(target=countdown, args=(cycles,))
for i in range(thread_count)]
start_time = get_time()
for thread in threads:
thread.start()
thread.join()
end_time = get_time()
sequential = get_time_diff(start_time, end_time)
threads = [threading.Thread(target=countdown, args=(cycles,))
for i in range(thread_count)]
start_time = get_time()
for thread in threads:
thread.start()
for thread in threads:
thread.join()
end_time = get_time()
parallel = get_time_diff(start_time, end_time)
print(row_format % {"threads": thread_count,
"seq_r": sequential[0],
"seq_c": sequential[1],
"seq_u": sequential[2],
"seq_s": sequential[3],
"par_r": parallel[0],
"par_c": parallel[1],
"par_u": parallel[2],
"par_s": parallel[3]})
if __name__ == "__main__":
arg_parser = argparse.ArgumentParser()
arg_parser.add_argument("max_threads", nargs="?",
type=int, default=5)
arg_parser.add_argument("total_cycles", nargs="?",
type=int, default=50000000)
arg_parser.add_argument("--no-headers",
action="store_true")
args = arg_parser.parse_args()
sys.exit(main(args.total_cycles, args.max_threads, args.no_headers))
Run Code Online (Sandbox Code Playgroud)
在使用Python 2.7.6在Ubuntu 14.04下的四核i5-2500机器上运行此脚本时,我得到以下结果(_r代表实时,_c代表CPU时间,_u代表用户模式,_s代表内核模式):
#t seq_r seq_c seq_u seq_s par_r par_c par_u par_s
1 1.47 1.47 1.47 0.00 1.46 1.46 1.46 0.00
2 1.74 1.74 1.74 0.00 3.33 5.45 3.52 1.93
3 1.87 1.90 1.90 0.00 3.08 6.42 3.77 2.65
4 1.78 1.83 1.83 0.00 3.73 6.18 3.88 2.30
5 1.73 1.79 1.79 0.00 3.74 6.26 3.87 2.39
Run Code Online (Sandbox Code Playgroud)
现在,如果我将所有线程绑定到一个核心,结果会有很大不同:
taskset -c 0 python countdown.py
#t seq_r seq_c seq_u seq_s par_r par_c par_u par_s
1 1.46 1.46 1.46 0.00 1.46 1.46 1.46 0.00
2 1.74 1.74 1.73 0.00 1.69 1.68 1.68 0.00
3 1.47 1.47 1.47 0.00 1.58 1.58 1.54 0.04
4 1.74 1.74 1.74 0.00 2.02 2.02 1.87 0.15
5 1.46 1.46 1.46 0.00 1.91 1.90 1.75 0.15
Run Code Online (Sandbox Code Playgroud)
所以问题是:为什么在多核上运行这个Python代码比挂钟慢1.5x-2x,CPU时钟慢4x-5x比在单核上运行?
询问周围和谷歌搜索产生了两个假设:
还有其他原因吗?我想了解发生了什么,并能够用数字支持我的理解(意味着如果减速是由于缓存未命中,我想看到并比较两种情况下的数字).
这是由于多个本机线程竞争 GIL 时 GIL 抖动造成的。David Beazley 关于这个主题的材料将告诉您您想知道的一切。
请参阅此处的信息,了解正在发生的情况的精美图形表示。
Python3.2 对 GIL 进行了更改,有助于解决此问题,因此您应该会看到 3.2 及更高版本的性能有所提高。
还应该注意的是,GIL 是该语言的 cpython 参考实现的实现细节。其他实现(例如 Jython)没有 GIL,因此不会遇到此特定问题。
D. Beazley 关于 GIL 的其余信息也会对您有所帮助。
要具体回答有关为什么涉及多个内核时性能会如此糟糕的问题,请参阅“ GIL 内部演示”的幻灯片 29-41。它详细讨论了多核 GIL 争用,而不是单核上的多线程。幻灯片 32 特别显示,当您添加内核时,由于线程信号开销而导致的系统调用数量会急剧增加。这是因为线程现在在不同的内核上同时运行,这使得它们能够参与真正的 GIL 战斗。与共享单个 CPU 的多个线程相反。上述演示中的一个很好的总结要点是:
对于多个核心,受 CPU 限制的线程会同时调度(在不同的核心上),然后进行 GIL 战斗。
| 归档时间: |
|
| 查看次数: |
1286 次 |
| 最近记录: |