当前位置:首页 > 科技  > 软件

通过研究案例,彻底掌握Python GIL

来源: 责编: 时间:2023-11-04 23:04:50 404观看
导读Python因其全局解释器锁(GIL)而声名狼藉。GIL限制了Python解释器一次只能执行一个线程。在现代多核CPU上,这是一个问题,因为程序无法利用多个核心。不过,尽管存在这种限制,Python仍已成为从后端Web应用到AI/ML和科学计算等

Python因其全局解释器锁(GIL)而声名狼藉。GIL限制了Python解释器一次只能执行一个线程。在现代多核CPU上,这是一个问题,因为程序无法利用多个核心。不过,尽管存在这种限制,Python仍已成为从后端Web应用到AI/ML和科学计算等领域的顶级语言。RkN28资讯网——每日最新资讯28at.com

1、训练数据管道的结构

对于大多数后端Web应用来说,GIL的限制并不是一个约束,因为它们通常受到I/O的限制。在这些应用中,大部分时间只是等待来自用户、数据库或下游服务的输入。系统只需具备并发性,而不一定需要并行性。Python解释器在执行I/O操作时会释放GIL,因此当线程等待I/O完成时,就会给另一个线程获得GIL并执行的机会。RkN28资讯网——每日最新资讯28at.com

GIL的限制不会影响大多数计算密集型的AI/ML和科学计算工作负载,因为像NumPy、TensorFlow和PyTorch等流行框架的核心实际上是用C++实现的,并且只有Python的API接口。大部分计算可以在不获取GIL的情况下进行。这些框架使用的底层C/C++内核库(如OpenBLAS或Intel MKL)可以利用多个核心而不受GIL的限制。RkN28资讯网——每日最新资讯28at.com

当同时有I/O和计算任务时会发生什么?RkN28资讯网——每日最新资讯28at.com

2、使用纯Python的计算任务

具体来说,可以考虑以下两个简单的任务。RkN28资讯网——每日最新资讯28at.com

import timedef io_task():    start = time.time()    while True:        time.sleep(1)        wake = time.time()        print(f"woke after: {wake - start}")        start = wake        def count_py(n):  compute_start = time.time()  s = 0  for i in range(n):      s += 1  compute_end = time.time()  print(f"compute time: {compute_end - compute_start}")  return s

在这里,通过休眠一秒钟来模拟一个I/O限制的任务,然后唤醒并打印它休眠了多长时间,然后再次休眠。count_py是一个计算密集型的任务,它简单地对数字n进行计数。如果同时运行这两个任务会发生什么?RkN28资讯网——每日最新资讯28at.com

import threadingio_thread = threading.Thread(target=io_task, daemnotallow=True)io_thread.start()count_py(100000000)

输出结果如下:RkN28资讯网——每日最新资讯28at.com

woke after: 1.0063529014587402woke after: 1.009704828262329woke after: 1.0069530010223389woke after: 1.0066332817077637compute time: 4.311860084533691

count_py需要大约4.3秒才能计数到一百万。但是io_task在同一时间内运行而不受影响,大约在1秒后醒来,与预期相符。尽管计算任务需要4.3秒,但Python解释器可以预先从运行计算任务的主线程中释放GIL,并给予io_thread获得GIL并运行的机会。RkN28资讯网——每日最新资讯28at.com

3、使用numpy的计算任务

现在,本文将在numpy中实现计数函数,并进行与之前相同的实验,但这次要计数到一千万,因为numpy的实现效率更高。RkN28资讯网——每日最新资讯28at.com

import numpy as npdef count_np(n):    compute_start = time.time()    s = np.ones(n).sum()    compute_end = time.time()    print(f"compute time: {compute_end - compute_start}")    return s  io_thread = threading.Thread(target=io_task, daemnotallow=True)io_thread.start()count_np(1000000000)

输出结果如下:RkN28资讯网——每日最新资讯28at.com

woke after: 1.0001161098480225woke after: 1.0008511543273926woke after: 1.0004539489746094woke after: 1.1320469379425049compute time: 4.1334803104400635

这显示的结果与上一次实验类似。在这种情况下,不是Python解释器预先释放了GIL,而是numpy自己主动释放了GIL。RkN28资讯网——每日最新资讯28at.com

这是否意味着在独立的线程中同时运行I/O任务和计算任务总是安全的?RkN28资讯网——每日最新资讯28at.com

4、使用自定义C++扩展的计算任务

现在,本文将用Python的C++扩展实现计数函数。RkN28资讯网——每日最新资讯28at.com

// importing Python C API Header#include <Python.h>#include <vector>static PyObject *count(PyObject *self, PyObject *args){  long num;  if (!PyArg_ParseTuple(args, "l", &num))         return NULL;  long result = 0L;  std::vector<long> v(num, 1L);  for (long i=0L; i<num; i++) {    result += v[i];   }  return Py_BuildValue("l", result);}// defining our functions like below:// function_name, function, METH_VARARGS flag, function documentsstatic PyMethodDef functions[] = {  {"count", count, METH_VARARGS, "Count."},  {NULL, NULL, 0, NULL}};// initializing our module informations and settings in this structure// for more informations, check head part of this file. there are some important links out there.static struct PyModuleDef countModule = {  PyModuleDef_HEAD_INIT, // head informations for Python C API. It is needed to be first member in this struct !!  "count",  // module name  NULL,  -1,  functions  // our functions list};// runs while initializing and calls module creation function.PyMODINIT_FUNC PyInit_count(void){  return PyModule_Create(&countModule);}

可以通过运行python setup.py build来构建扩展,使用以下setup.pyRkN28资讯网——每日最新资讯28at.com

from distutils.core import setup, Extensioncount_module = Extension('count', sources=['count.cpp'])setup(name='python_count_extension',      versinotallow='0.1',      descriptinotallow='An Example For Python C Extensions',      ext_modules=[count_module],      )

然后,使用作为自定义扩展实现的计数函数运行实验:RkN28资讯网——每日最新资讯28at.com

import count def count_custom(n):    compute_start = time.time()    s = count.count(n)    compute_end = time.time()    print(f"compute time: {compute_end - compute_start}")    return sio_thread = threading.Thread(target=io_task, daemnotallow=True)io_thread.start()count_custom(1000000000)

得到如下结果:RkN28资讯网——每日最新资讯28at.com

woke after: 4.414866924285889compute time: 4.414893865585327

在这种情况下,计算任务持有GIL,并阻止I/O线程运行。RkN28资讯网——每日最新资讯28at.com

Python解释器只能在两个Python字节码指令之间预先释放GIL,在扩展中,是否自愿释放GIL取决于扩展的实现。RkN28资讯网——每日最新资讯28at.com

在这种情况下,本例进行了一个不会影响任何Python对象的琐碎计算,因此可以在C++的计数函数中使用宏Py_BEGIN_ALLOW_THREADSPy_END_ALLOW_THREADS来释放GIL:RkN28资讯网——每日最新资讯28at.com

static PyObject *count(PyObject *self, PyObject *args){  long num;  if (!PyArg_ParseTuple(args, "l", &num))         return NULL;  long result = 0L;  Py_BEGIN_ALLOW_THREADS  std::vector<long> v(num, 1L);  for (long i=0L; i<num; i++) {    result += v[i];   }   Py_END_ALLOW_THREADS  return Py_BuildValue("l", result);}

使用这种实现方式,当重新运行实验时,会得到如下结果:RkN28资讯网——每日最新资讯28at.com

woke after: 1.0026037693023682woke after: 1.003467082977295woke after: 1.0028629302978516woke after: 1.1772480010986328compute time: 4.186192035675049

5、结论

在使用Python时,了解GIL是很重要的。在大多数常见情况下,可能不会遇到它的限制。但是,如果使用包装C/C++库的第三方Python包(除了标准的NumPy、SciPy、TensorFlow或PyTorch),在涉及到任何重型计算时可能会遇到一些问题。在开发自定义扩展时,最好在进行重型计算之前释放GIL,以便其他Python线程有机会运行。RkN28资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-16939-0.html通过研究案例,彻底掌握Python GIL

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: Sed 原地替换文件时遇到的趣事

下一篇: 五个使用IntelliJ IDEA优化Java代码的小技巧

标签:
  • 热门焦点
  • Find N3入网:最高支持16+1TB

    OPPO将于近期登场的Find N3折叠屏目前已经正式入网,型号为PHN110。本次Find N3在外观方面相比前两代有很大的变化,不再是小号的横向折叠屏,而是跟别的厂商一样采用了较为常见的
  • 一加首款折叠屏!一加Open渲染图出炉:罕见单手可握小尺寸

    8月5日消息,此前就有爆料称,一加首款折叠屏手机将会在第三季度上市,如今随着时间临近,新机的各种消息也开始浮出水面。据悉,这款新机将会被命名为&ldquo;On
  • Golang 中的 io 包详解:组合接口

    io.ReadWriter// ReadWriter is the interface that groups the basic Read and Write methods.type ReadWriter interface { Reader Writer}是对Reader和Writer接口的组合,
  • 企业采用CRM系统的11个好处

    客户关系管理(CRM)软件可以为企业提供很多的好处,从客户保留到提高生产力。  CRM软件用于企业收集客户互动,以改善客户体验和满意度。  CRM软件市场规模如今超过580
  • 十个简单但很有用的Python装饰器

    装饰器(Decorators)是Python中一种强大而灵活的功能,用于修改或增强函数或类的行为。装饰器本质上是一个函数,它接受另一个函数或类作为参数,并返回一个新的函数或类。它们通常用
  • 微信语音大揭秘:为什么禁止转发?

    大家好,我是你们的小米。今天,我要和大家聊一个有趣的话题:为什么微信语音不可以转发?这是一个我们经常在日常使用中遇到的问题,也是一个让很多人好奇的问题。让我们一起来揭开这
  • 华为HarmonyOS 4升级计划公布:首批34款机型今日开启公测

    8月4日消息,今天下午华为正式发布了HarmonyOS 4系统,在更流畅的前提下,还带来了不少新功能,UI设计也有变化,会让手机焕然一新。华为宣布,首批机型将会在
  • 三星折叠屏手机去年销售近1000万台 今年目标定为1500万

    7月29日消息,三星率先发力可折叠手机市场,在全球市场已经取得了非常亮眼的成绩,接下来会进一步巩固和扩大这一优势。三星在推出Galaxy Z Flip5和Galax
  • 世界人工智能大会国际日开幕式活动在世博展览馆开启

    30日上午,世界人工智能大会国际日开幕式活动在世博展览馆开启,聚集国际城市代表、重量级院士专家、国际创新企业代表,共同打造人工智能交流平台。上海市副市
Top