关于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的线程实现的说明的更多相关文章

  1. numba学习教程

    一.对于python的基础介绍 Python是一种高效的动态编程语言,广泛用于科学,工程和数据分析应用程序..影响python普及的因素有很多,包括干净,富有表现力的语法和标准数据结构,全面的“电池包 ...

  2. Python笔记_第四篇_高阶编程_进程、线程、协程_5.GPU加速

    Numba:高性能计算的高生产率 在这篇文章中,笔者将向你介绍一个来自Anaconda的Python编译器Numba,它可以在CUDA-capable GPU或多核cpu上编译Python代码.Pyt ...

  3. Python数据预处理:使用Dask和Numba并行化加速

    如果你善于使用Pandas变换数据.创建特征以及清洗数据等,那么你就能够轻松地使用Dask和Numba并行加速你的工作.单纯从速度上比较,Dask完胜Python,而Numba打败Dask,那么Num ...

  4. 用 Numba 加速 Python 代码

    原文出自微信公众号:Python那些事 一.介绍 pip install numba Numba 是 python 的即时(Just-in-time)编译器,即当你调用 python 函数时,你的全部 ...

  5. 适用于CUDA GPU的Numba 随机数生成

    适用于CUDA GPU的Numba 随机数生成 随机数生成 Numba提供了可以在GPU上执行的随机数生成算法.由于NVIDIA如何实现cuRAND的技术问题,Numba的GPU随机数生成器并非基于c ...

  6. 适用于CUDA GPU的Numba例子

    适用于CUDA GPU的Numba例子 矩阵乘法 这是使用CUDA内核的矩阵乘法的简单实现: @cuda.jit def matmul(A, B, C): """Perf ...

  7. [ 高并发]Java高并发编程系列第二篇--线程同步

    高并发,听起来高大上的一个词汇,在身处于互联网潮的社会大趋势下,高并发赋予了更多的传奇色彩.首先,我们可以看到很多招聘中,会提到有高并发项目者优先.高并发,意味着,你的前雇主,有很大的业务层面的需求, ...

  8. [高并发]Java高并发编程系列开山篇--线程实现

    Java是最早开始有并发的语言之一,再过去传统多任务的模式下,人们发现很难解决一些更为复杂的问题,这个时候我们就有了并发. 引用 多线程比多任务更加有挑战.多线程是在同一个程序内部并行执行,因此会对相 ...

  9. 多线程爬坑之路-学习多线程需要来了解哪些东西?(concurrent并发包的数据结构和线程池,Locks锁,Atomic原子类)

    前言:刚学习了一段机器学习,最近需要重构一个java项目,又赶过来看java.大多是线程代码,没办法,那时候总觉得多线程是个很难的部分很少用到,所以一直没下决定去啃,那些年留下的坑,总是得自己跳进去填 ...

随机推荐

  1. 数据链路层协议(Ethernet、IEEE802.3、PPP、HDLC)

    目录 数据链路层协议 Ethernet以太网协议 以太网数据帧的封装 IEEE802.3协议 PPP协议 HDLC协议 数据链路层协议 首先Ethernet.IEEE802.3.PPP和HDLC都是数 ...

  2. HarmonyOS三方件开发指南(19)-BGABadgeView徽章组件

    目录: 1.引言 2.功能介绍 3.BGABadgeView 使用指南 4.BGABadgeView 开发指南 5.<HarmonyOS三方件开发指南>系列文章合集 引言 现在很多的APP ...

  3. 【python】Leetcode每日一题-删除排序链表中的重复元素2

    [python]Leetcode每日一题-删除排序链表中的重复元素2 [题目描述] 存在一个按升序排列的链表,给你这个链表的头节点 head ,请你删除链表中所有存在数字重复情况的节点,只保留原始链表 ...

  4. 容器随Docker启动而启动

    在容器开启状态下 docker container update --restart=always 容器名

  5. Postman报文进行解密之RSA私钥解密

    接口返回的数据也是加密的,需要对数据解密才能看到返回的数据是否正确,就需要用RSA解密. 返回数据的解析可以在postman的Tests进行后置处理,获取加密后的返回数据: var data = JS ...

  6. x265编码命令

    CQP: #/bin/bash ./x265 --input FourPeople_1280x720_60.yuv --input-res 1280x720 --fps 60 --qp 40 --fr ...

  7. BUA软件工程个人博客作业

    写在前面 项目 内容 所属课程 2020春季计算机学院软件工程(罗杰 任健) (北航) 作业要求 个人博客作业 课程目标 培养软件开发能力 本作业对实现目标的具体作用 阅读教材,了解软件工程,并比较各 ...

  8. [面向对象之继承应用(在子类派生重用父类功能(super),继承实现原理(继承顺序、菱形问题、继承原理、Mixins机制),组合]

    [面向对象之继承应用(在子类派生重用父类功能(super),继承实现原理(继承顺序.菱形问题.继承原理.Mixins机制),组合] 继承应用 类与类之间的继承指的是什么'是'什么的关系(比如人类,猪类 ...

  9. Spring context的refresh函数执行过程分析

    今天看了一下Spring Boot的run函数运行过程,发现它调用了Context中的refresh函数.所以先分析一下Spring context的refresh过程,然后再分析Spring boo ...

  10. checked 和 prop() (散列性比较少的)

    在<input  class="sex1" type="radio" checked>男 checked表示该框会被默认选上 prop()操作的是D ...