关于Numba的线程实现的说明
关于Numba的线程实现的说明
由Numbaparallel目标执行的工作由Numba线程层执行。实际上,“线程层”是Numba内置库,可以执行所需的并发执行。在撰写本文时,有三个可用的线程层,每个线程层都通过不同的较低级别的host线程库实现。上thread线程和对于给定的应用/系统的thread线程的适当选择的更多信息可在发现 threading layer documentatio。
以下各节要关注的相关信息是,执行并行线程库中的parallel_for函数。此功能的工作是协调和执行并行任务。
本文引用的相关源文件是
- numba/np/ufunc/tbbpool.cpp
- numba/np/ufunc/omppool.cpp
- numba/np/ufunc/workqueue.c
这些文件分别包含TBB,OpenMP和工作队列线程池实现。每个函数都包含函数 set_num_threads(),get_num_threads()和和get_thread_id(),以及在各自的调度程序中用于线程屏蔽的相关逻辑。请注意,基本线程局部变量逻辑在这些文件中都是重复的,并且不在它们之间共享。
- numba/np/ufunc/parallel.py
该文件包含了Python和JIT兼容的封装器 set_num_threads(),get_num_threads()和get_thread_id(),以及代码加载上述库到Python和启动线程池。
- numba/parfors/parfor_lowering.py
该文件包含用于为并行后端生成代码的主要逻辑。在生成调度程序代码的代码中访问该线程掩码,并将其传递给相关的后端调度程序功能。
线程屏蔽
作为其设计的一部分,Numba绝不会在numba.np.ufunc.parallel._launch_threads() 运行首次并行执行时,最初启动的线程之外启动新线程。这是由于在实施线程屏蔽之前已在Numba中实现了线程。保留此限制是为了使设计简单,尽管将来可以删除它。因此,可以以编程方式设置线程数,但只能设置为小于或等于已启动的总数。这是通过“屏蔽”未使用的线程来完成的,从而使它们不起作用。例如,在16核计算机上,如果用户要调用set_num_threads,则Numba将始终存在16个线程,但是其中12个线程将处于空闲状态以进行并行计算。进一步调用 set_num_threads(16) ,导致这些相同的线程在以后的计算中起作用。
添加了线程掩码,使用户可以以编程方式更改在线程层中执行工作的线程数。事实证明,线程屏蔽难以实现,需要开发一种适合用户,易于推理且可以安全实现的编程模型,并在各个线程层上具有一致的行为。
编程模型
选择的编程模型与OpenMP中的模型相似。做出此选择的原因是,它对于许多用户来说是熟悉的,范围有限且简单。通过调用指定正在使用 set_num_threads的线程数,可以通过调用查询正在使用的线程数 get_num_threads。这两个函数与它们的OpenMP对应(与上述限制相同,即掩码必须小于或等于已启动的线程)。执行语义也与OpenMP相似,因为一旦启动并行区域,更改线程掩码不会对当前正在执行的区域产生影响,但会对随后执行的并行区域产生影响。
实施
除了对线程层库中已经存在的用户代码进行限制以外,对用户代码没有其它限制,需要仔细考虑线程掩码的设计。无法将“线程掩码”存储在全局值中,因为同时使用线程层,可能会导致值本身出现竞争形式。涉及具有这种全局价值的各种互斥量的众多设计,最终仅通过理想实验就打破了所有这些互斥量。最终发现,在某些OpenMP实现之后,“线程掩码”最好以a实现。这意味着每个执行Numba并行函数的线程,都将具有一个线程本地存储(TLS)插槽,其中包含在线程中调度线程thread localparallel_for时,要使用的线程掩码的值。
TLS使用一个线程掩模的上述概念是相对容易实现的, get_num_threads和set_num_threads简单地需要解决的TLS时隙在给定的线程层。这也意味着可以从运行时调用中得出并行区域的执行调度get_num_threads。这是通过众所周知的且相对容易实现的C 库函数注册模式,并将其包装在内部Numba来实现的。
除了满足原始的前期线程屏蔽要求之外,还需要考虑以下一些更复杂的方案。
嵌套并行
在所有线程层中,“主线程”将调用该parallel_for 函数,然后在并行区域中,根据线程层的不同,一些其它线程将有助于完成实际工作。如果工作包含对另一个并行函数的调用(即嵌套并行性),则使调用的线程必须知道主线程的“线程掩码”是什么,以便可以将其传递到主线程中。 parallel_for在执行嵌套并行函数时调用。此行为的实现是特定于线程层的,但一般原则是“主线程”始终将线程掩码的值从其TLS插槽“发送”到线程层中,在并行区域中处于活动状态的所有线程。这些活动线程将在执行任何工作之前,使用此值更新其TLS插槽。该实现细节的最终结果是:
- 线程掩码正确传递到嵌套函数中
- 并行区域中的每个线程仍然可以安全地使用不同的掩码来调用嵌套函数,如果未明确设置,则使用从“主线程”继承的掩码
- parallel_for成功容纳具有动态调度,且线程可能在执行期间加入和离开活动池的线程层
- 任何“主线程”线程掩码都与活动线程池中线程的线程掩码的流入特性完全分离
Python线程独立调用并行函数
严格保护线程层启动顺序,确保启动既是线程安全的,又是进程安全的,并且每个进程运行一次。在具有大量threading都使用Numba的Python模块线程的系统中,启动序列中的第一个线程将正确设置其线程掩码,但是没有其他线程可以运行启动序列。这意味着其它线程将需要以其它方式设置其初始线程掩码。这是在get_num_threads调用,且不存在线程掩码时实现的,在这种情况下,线程掩码将设置为默认值。在该实现中,“不存在线程掩码”由值表示,-1。“默认线程掩码”(未设置)由值表示0。执行set_num_threads(NUMBA_NUM_THREADS),此算子也会立即调用,因此如果有-1或0,由于遇到此结果get_num_threads(),而表示上述过程中存在错误。
操作系统fork()调用
使用TLS也是在由Linux(用于Numba使用最流行的平台)驱动,将TLS传输到子进程fork(2, 3P)clone(2)CLONE_SETTLS。
线程ID
私有get_thread_id()函数被添加到每个线程后端,该函数为每个线程返回唯一的ID。可以通过以下方式从Python访问 numba.np.ufunc.parallel._get_thread_id()(也可以在JIT编译函数中使用它)。线程ID函数对于测试线程屏蔽行为是否正确很有用,但不应在测试之外使用。例如,可以调用set_num_threads,收集_get_thread_id()并行区域中的所有唯一性,验证仅运行了4个线程。
注意事项
测试线程屏蔽时需要注意的一些注意事项:
- TBB后端可以选择调度少于给定掩码数的线程。因此,诸如上述测试,可能返回少于4个唯一线程。
- 工作队列后端不是线程安全的,因此尝试对其执行多线程嵌套并行处理,可能会导致死锁或其他未定义的行为。如果工作队列后端检测到嵌套的并行性,它将发出SIGABRT信号。
- 某些后端可能会重用主线程进行计算,但是不应依赖此行为(例如,如果传递异常)。
在代码生成中使用
get_num_threads在代码生成中使用的一般模式是
import llvmlite.llvmpy.core as lc
get_num_threads = builder.module.get_or_insert_function(
lc.Type.function(lc.Type.int(types.intp.bitwidth), []),
name="get_num_threads")
num_threads = builder.call(get_num_threads, [])
with cgutils.if_unlikely(builder, builder.icmp_signed('<=', num_threads,
num_threads.type(0))):
cgutils.printf(builder, "num_threads: %d\n", num_threads)
context.call_conv.return_user_exc(builder, RuntimeError,
("Invalid number of threads. "
"This likely indicates a bug in Numba.",))
# Pass num_threads through to the appropriate backend function here
请参阅中的代码numba/parfors/parfor_lowering.py。
num_threads严格禁止<= 0的防护措施是必要的,但是在线程屏蔽逻辑包含错误的情况下,它可以防止意外的错误行为。
该num_threads变量应传递给适当的后端函数,例如do_scheduling或parallel_for。如果以其它方式使用(而不是将其传递给后端函数),应考虑上述注意事项,确保num_threads安全使用变量。最好将这样的逻辑保留在线程后端中,而不是尝试在代码生成中进行。
关于Numba的线程实现的说明的更多相关文章
- numba学习教程
一.对于python的基础介绍 Python是一种高效的动态编程语言,广泛用于科学,工程和数据分析应用程序..影响python普及的因素有很多,包括干净,富有表现力的语法和标准数据结构,全面的“电池包 ...
- Python笔记_第四篇_高阶编程_进程、线程、协程_5.GPU加速
Numba:高性能计算的高生产率 在这篇文章中,笔者将向你介绍一个来自Anaconda的Python编译器Numba,它可以在CUDA-capable GPU或多核cpu上编译Python代码.Pyt ...
- Python数据预处理:使用Dask和Numba并行化加速
如果你善于使用Pandas变换数据.创建特征以及清洗数据等,那么你就能够轻松地使用Dask和Numba并行加速你的工作.单纯从速度上比较,Dask完胜Python,而Numba打败Dask,那么Num ...
- 用 Numba 加速 Python 代码
原文出自微信公众号:Python那些事 一.介绍 pip install numba Numba 是 python 的即时(Just-in-time)编译器,即当你调用 python 函数时,你的全部 ...
- 适用于CUDA GPU的Numba 随机数生成
适用于CUDA GPU的Numba 随机数生成 随机数生成 Numba提供了可以在GPU上执行的随机数生成算法.由于NVIDIA如何实现cuRAND的技术问题,Numba的GPU随机数生成器并非基于c ...
- 适用于CUDA GPU的Numba例子
适用于CUDA GPU的Numba例子 矩阵乘法 这是使用CUDA内核的矩阵乘法的简单实现: @cuda.jit def matmul(A, B, C): """Perf ...
- [ 高并发]Java高并发编程系列第二篇--线程同步
高并发,听起来高大上的一个词汇,在身处于互联网潮的社会大趋势下,高并发赋予了更多的传奇色彩.首先,我们可以看到很多招聘中,会提到有高并发项目者优先.高并发,意味着,你的前雇主,有很大的业务层面的需求, ...
- [高并发]Java高并发编程系列开山篇--线程实现
Java是最早开始有并发的语言之一,再过去传统多任务的模式下,人们发现很难解决一些更为复杂的问题,这个时候我们就有了并发. 引用 多线程比多任务更加有挑战.多线程是在同一个程序内部并行执行,因此会对相 ...
- 多线程爬坑之路-学习多线程需要来了解哪些东西?(concurrent并发包的数据结构和线程池,Locks锁,Atomic原子类)
前言:刚学习了一段机器学习,最近需要重构一个java项目,又赶过来看java.大多是线程代码,没办法,那时候总觉得多线程是个很难的部分很少用到,所以一直没下决定去啃,那些年留下的坑,总是得自己跳进去填 ...
随机推荐
- 从苏宁电器到卡巴斯基第23篇:难忘的三年硕士时光 I
初次接触逆向工程 不知不觉就来到了2013年的9月份,学校开学了,我开始正式体验研究生的生活了.按道理来说,硕士研究生是需要围绕在导师身边,每天朝九晚五地去实验室做项目的.不过我们老师没有项目,也不要 ...
- Android木马病毒com.schemedroid的分析报告
某安全公司移动病毒分析报告的面试题目,该病毒样本的代码量比较大,最大的分析障碍是该病毒样本的类名称和类方法名称以及类成员变量的名称被混淆为无法辨认的特殊字符,每个被分析的类中所有的字符串都被加密处理了 ...
- POJ3070矩阵快速幂简单题
题意: 求斐波那契后四位,n <= 1,000,000,000. 思路: 简单矩阵快速幂,好久没刷矩阵题了,先找个最简单的练练手,总结下矩阵推理过程,其实比较简单,关键 ...
- Win64 驱动内核编程-5.内核里操作文件
内核里操作文件 RING0 操作文件和 RING3 操作文件在流程上没什么大的区别,也是"获得文件句柄->读/写/删/改->关闭文件句柄"的模式.当然了,只能用内核 A ...
- Intel汇编程序设计-整数算术指令(下)
7.5 扩展加法和减法 扩展精度的假发和减法是指任意尺寸大小数字的加法和减法.例如要求你写一个C++程序,把两个1024位的整数相加,解决方案可不是那么简单!但在汇编语言中,ADC(带进位加)指令和 ...
- 13.PHP_ThinkPHP
ThinkPHP 先把百度百科上对这个框架的介绍粘贴过来: ThinkPHP是为了简化企业级应用开发和敏捷WEB应用开发而诞生的.最早诞生于2006年初,2007年元旦正式更名为ThinkPHP,并且 ...
- ERROR: Failed to Setup IP tables: Unable to enable SKIP DNAT rule
解释:执行docker-compose up -d时出现ERROR: Failed to Setup IP tables: Unable to enable SKIP DNAT rule 原因:防火墙 ...
- 游戏中的2D OBB碰撞模型的碰撞算法介绍和实践
前言 上一篇博文说道,射线与场景中模型上的所有三角形求交时,会大幅度影响效率且花费比较多的时间,因此会采取使用包围盒的形式,进行一个加速求交.在此文中介绍OBB碰撞模型的碰撞算法 OBB的碰撞模型 有 ...
- buaaoo_fourth_assignment
你轻轻地走了 一.架构设计 (1)第一次作业 类图 复杂度分析 如上图是本单元第一次作业的架构设计,由于本人最开始未发现可以直接继承官方的类,所以自己将所用到的各种type都重新建了类,于是这就导致了 ...
- 关于Annotation注解的理解
在编Java程序的时候,我们经常会碰到annotation.比如:@Override 我们在子类继承父类的时候,会经常用到这个annotation.它告诉编译器这个方法是override父类的方法的. ...