关于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. 关于Snowflake 生成53位ID

    1, bug现象: 没有经过处理的Snowflake 生成的是64位bit的唯一ID,但由于多数时候我们前台用到js,但是js只支持53位bit的数值.这样就导致了传到前台的64位的丢失精度. 解决思 ...

  2. 【工具类】获取请求头中User-Agent工具类

    public class AgentUserKit { private static String pattern = "^Mozilla/\\d\\.\\d\\s+\\(+.+?\\)&q ...

  3. Python中的可迭代Iterable和迭代器Iterator

    目录 Iterable可迭代对象 如何判断对象是否是可迭代对象Iterable Iterator迭代器 如何判断对象是否迭代器Iterator 将Iterable转换成Iterator Iterabl ...

  4. Linux-鸟菜-5-目录配置-FHS

    Linux-鸟菜-5-目录配置-FHS 这节内容比较休闲,主要就是介绍Linux的目录配置,也就是那些目录通常是干啥的,这个比较重要,需要我们去了解.但是我觉得通常看一遍记不住啥,也就记个大概,主要还 ...

  5. 【opencv】Java+eclipse+opencv 环境搭建 helloword入门demo

    文章为博主原创,纯属个人理解,如有错误欢迎指出. 如需转载,请注明出处. 引入jar包 引入配置文件 到此环境配置完成!!! 可能会出现的问题: 1. jdk版本不一致导致发生异常.如图 build ...

  6. Mybatis学习之自定义持久层框架(二) 自定义持久层框架设计思路

    前言 上一篇文章讲到了JDBC的基本用法及其问题所在,并提出了使用Mybatis的好处,那么今天这篇文章就来说一下该如何设计一个类似Mybatis这样的持久层框架(暂时只讲思路,具体的代码编写工作从下 ...

  7. Codeforces Round #687 (Div. 2, based on Technocup 2021 Elimination Round 2)

    A. Prison Break 题意:就是在一个n*m的矩阵中,以(1,1)为起点(n,m)为终点,每个点以每个单位1s的速度移动,问总共至少需要多少秒,所有的矩阵点就能够全部移动到(r,c)中 思路 ...

  8. c#操作可道云api帮助类

    代码: public class KodCloudHelper { static readonly string kodCloudUrl = Configs.GetString("KodCl ...

  9. 通过Dapr实现一个简单的基于.net的微服务电商系统(十一)——一步一步教你如何撸Dapr之自动扩/缩容

    上一篇我们讲到了dapr提供的bindings,通过绑定可以让我们的程序轻装上阵,在极端情况下几乎不需要集成任何sdk,仅需要通过httpclient+text.json即可完成对外部组件的调用,这样 ...

  10. Java安全之Filter权限绕过

    Java安全之Filter权限绕过 0x00 前言 在一些需要挖掘一些无条件RCE中,大部分类似于一些系统大部分地方都做了权限控制的,而这时候想要利用权限绕过就显得格外重要.在此来学习一波权限绕过的思 ...