线程使用越多程序越快?别瞎整。。。

news2024/11/14 16:50:54
  • B站:啥都会一点的研究生
  • 公众号:啥都会一点的研究生

当运行 CPU 密集型的并行程序时,通常希望将线程或进程池的大小设置为计算机上的 CPU 核数量,但有没有考虑过是否真的是核数用的越多并行程序越快?

理论上线程过少,无法充分利用所有核心,线程过多,程序会因为多个线程争夺同一核心而变得运行缓慢

事实上,确定要运行多少个线程没那么容易

  • Python 标准库提供了多个获取此信息的 API,但没有一个是恰当的(稍后会举例)
  • 由于 CPU 具有指令级并行性和同时多线程等功能(在英特尔 CPU 上称为超线程),可以有效使用的核心数量取决于编写的代码

从 Python 获取 CPU 内核数

前述提到在Python中获取内核数的API是不准确的,为啥这么说,我们看个例子

Python提供 os.cpu_count() 函数,可以返回 “系统中逻辑 CPU 的数量”

文档说明 “len(os.sched_getaffinity(0))可以获取当前进程调用线程受限的逻辑 CPU 数量”,调度器亲和性是一种限制进程使用特定内核的方法

遗憾的是,这个 API 也不够恰当,例如使用Docker在创建容器时人为限制CPU数量,比如将 CPU 限制为2.25 个内核

$ docker run -i -t --cpus=2.25 python:3.12-slim
Python 3.12.1 (main, Dec  9 2023, 00:21:37) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import os
>>> os.cpu_count()
20
>>> len(os.sched_getaffinity(0))
20

在Docker中只提供了2.25个内核资源,但显然调用Python API时返回的数量仍不对

说完这个问题,还需要先了解物理和逻辑 CPU 内核是什么再进入正题

物理与逻辑 CPU内核

以英特尔 i7-12700K 处理器为例,它具有

  • 12 个物理内核(8 个高性能内核和 4 个性能较弱的内核)
  • 20 个逻辑内核

现代 CPU 内核可以并行执行多条指令,但如果 CPU 在等待从 RAM 中加载某些数据时卡住了,会发生什么情况?在此之前,它可能无法执行任何工作

为了充分利用这些可能被浪费的资源,CPU 物理内核的计算资源可以作为多个内核向操作系统公开。在这台电脑上,8 个高性能内核中的每一个都可以作为两个内核公开,总共有 16 个逻辑内核。成对的逻辑内核将共享单个物理内核的计算资源,例如,如果一个逻辑内核没有充分利用所有内部算术逻辑单元,比如因为它在等待内存加载,那么通过配对逻辑内核运行的代码仍然可以使用这些闲置资源

这种技术被称为同步多线程技术,英特尔称之为超线程技术。如果你有一台电脑,通常可以在 BIOS 中禁用它

这种解释非常不准确,而且不同型号的 CPU,即使是同一制造商生产的 CPU,实际执行情况也不尽相同。不过,逻辑内核与物理内核并不完全相同的一般意义足以满足这篇文章要表达的目的

现在又有了一个新问题,抛开调度器亲和性等因素不谈,我们应该使用物理内核数还是逻辑内核数作为线程池大小?

示例

在该例中,用 Numba 将两个函数编译成机器代码,确保释放 GIL 以实现并行

这两个函数做的事情一毛一样,但slow_threshold特意写成比较慢的方式而fast_threshold则更快(感兴趣的可以对比学习下为何另一个更快,很简单)。现在可以在多个线程上并行运行这些函数,在大多数人眼里,只需并行处理更多图像,就能线性提高吞吐量,直到内核耗尽,先从单核上进行测试

from numba import njit
import numpy as np

@njit(nogil=True)
def slow_threshold(img, noise_threshold):
    noise_threshold = img.dtype.type(noise_threshold)
    result = np.empty(img.shape, dtype=np.uint8)
    for i in range(result.shape[0]):
        for j in range(result.shape[1]):
            result[i, j] = img[i, j] // 256
    for i in range(result.shape[0]):
        for j in range(result.shape[1]):
            if result[i, j] < noise_threshold // 256:
                result[i, j] = 0
    return result

@njit(nogil=True)
def fast_threshold(img, noise_threshold):
    noise_threshold = np.uint8(noise_threshold // 256)
    result = np.empty(img.shape, dtype=np.uint8)
    for i in range(result.shape[0]):
        for j in range(result.shape[1]):
            value = img[i, j] >> 8
            value = (
                0 if value < noise_threshold else value
            )
            result[i, j] = value
    return result

rng = np.random.default_rng(12345)

def make_image(size=256):
    noise = rng.integers(0, high=1000, size=(size, size), dtype=np.uint16)
    signal = rng.integers(0, high=5000, size=(size, size), dtype=np.uint16)
    # A noisy, hard to predict image:
    return noise | signal

NOISY_IMAGE = make_image()
assert np.array_equal(
    slow_threshold(NOISY_IMAGE, 1000),
    fast_threshold(NOISY_IMAGE, 1000)
)

借助timeit测试单核上运行每个功能的性能,结果如下

%timeit slow_threshold(NOISY_IMAGE, 1000)

# 90.6 µs ± 77.7 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)
%timeit fast_threshold(NOISY_IMAGE, 1000)

# 24.6 µs ± 10.8 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)

结果如前所述,确实fast_threshold表现更好

并行化示例

现在我们使用线程池处理上述函数

from multiprocessing.dummy import Pool as ThreadPool

def apply_in_thread_pool(
    num_threads, function, images
):
    with ThreadPool(num_threads) as pool:
        result = pool.map(
            lambda img: function(img, 1000),
            images,
            chunksize=10
        )
        assert len(result) == len(images)

借助benchit绘制不同线程数运行不同函数所需的时间图

import benchit
benchit.setparams(rep=1)

# 4000 images to run through the pool:
IMAGES = [make_image() for _ in range(4000)]

def slow_threshold_in_pool(num_threads):
    apply_in_thread_pool(num_threads, slow_threshold, IMAGES)

def fast_threshold_in_pool(num_threads):
    apply_in_thread_pool(num_threads, fast_threshold, IMAGES)

# Measure the two functions with 1 to 24 threads:
timings = benchit.timings(
    [slow_threshold_in_pool, fast_threshold_in_pool],
    range(1, 25),
    input_name="Number of threads"
)
timings.plot(logy=True, logx=False)

绘制的图片如下

在这里插入图片描述

可以注意到随着线程数变多,运行时间先是有明显下降,但到一定程度后无明显改进,且另一个发现是每个函数的最佳线程数不同

timings.to_dataframe().idxmin(axis="rows")
FunctionsOptimal number of threads
slow_threshold19
fast_threshold9

slow_threshold函数基本上可以利用所有逻辑内核,单线程可能无法充分利用特定物理内核的所有可用处理能力,因此逻辑内核允许更多并行性

相比之下,fast_threshold函数使用超过 9 个内核后,速度就开始减慢。可能遇到计算以外的瓶颈,比如内存带宽

总结

  • 考虑到操作系统限制 CPU 使用的所有不同方式,很难获得准确的内核数量
  • 最佳并行程度(如线程数)取决于工作量
  • 内核数量并不是唯一的瓶颈

如果有一个长期运行的数据处理任务,需要在多个线程中运行相同的代码一段时间,通常也值得这样做,花一点时间根据经验测算出最佳线程数

https://pythonspeed.com/articles/cpu-thread-pool-size

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1369585.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

归并排序-排序算法

前言 如果一个数组的左右区间都有序&#xff0c;我们可以使用一种方法&#xff08;归并&#xff09;&#xff0c;使这个数组变得有序。 如下图&#xff1a; 过程也很简单&#xff0c;分别取左右区间中的最小元素&#xff0c;再把其中较小的元素放到临时数组中&#xff0c;例如…

《网络是怎样连接的》2.5节图表(自用)

图5.1&#xff1a;ip包结构 图5.2&#xff1a;ip网络包的传输方式 1.以太网的部分也可以替换成其他的东西&#xff0c;例如无线局域网、ADSL、FTTH等&#xff0c;它们都可以替代以太网的角色帮助IP协议来传输网络包 2.根据ARP协议&#xff0c;客户端可以根据ip地址得到下一个路…

近似点梯度法

最优化笔记——Proximal Gradient Method 最优化笔记&#xff0c;主要参考资料为《最优化&#xff1a;建模、算法与理论》 文章目录 最优化笔记——Proximal Gradient Method一、邻近算子&#xff08;1&#xff09;定义 二、近似点梯度法&#xff08;1&#xff09;迭代格式&…

【JAVA】怎么确保一个集合不能被修改

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a; JAVA ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 前言 正文 示例&#xff1a; 不可修改的List&#xff1a; 不可修改的Set&#xff1a; 不可修改的Map&#xff1a; 结语 我的其他博…

基于Springboot的计算机学院校友网(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的计算机学院校友网(有报告)。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff0c;通过Spring…

【数据库】mysql事务

一、事务的基本概念 1、事务的定义 事务可由一条非常简单的SQL语句组成&#xff0c;也可以由一组复杂的SQL语句组成。。 在 MySQL 中只有使用了 Innodb 数据库引擎的数据库或表才支持事务。事务处理可以用来维护数据库的完整性&#xff0c;保证成批的 SQL 语句要么全部执行&…

Java项目:112SSM在线电影订票系统

博主主页&#xff1a;Java旅途 简介&#xff1a;分享计算机知识、学习路线、系统源码及教程 文末获取源码 一、项目介绍 在线电影订票系统基于SpringSpringMVCMybatis开发&#xff0c;系统分为前台和后台&#xff0c;前台主要用来用户浏览电影信息&#xff0c;订票&#xff0c…

第7章-第9节-Java中的Stream流(链式调用)

1、什么是Stream流 Lambda表达式&#xff0c;基于Lambda所带来的函数式编程&#xff0c;又引入了一个全新的Stream概念&#xff0c;用于解决集合类库既有的鼻端。 2、案例 假设现在有一个需求&#xff0c; 将list集合中姓张的元素过滤到一个新的集合中&#xff1b;然后将过滤…

Dell 机架式服务器 - 高级定制服务

Dell 机架式服务器 - 高级定制服务 1. Dell Technologies2. 机架式服务器 - 高级定制服务2.1. Servers & Storage (服务器及存储) -> Servers2.2. Rack Servers (机架式服务器)2.3. Shop2.4. PowerEdge Rack Servers (PowerEdge 机架式服务器)2.5. PowerEdge R760 Rack …

个性化Python GUI计算器搭建

大家好&#xff0c;本文将介绍在Python中使用Tkinter几分钟内制作自己的全功能GUI计算器。 要完成所提到的功能&#xff0c;除了通常随Python标准库一起安装的Tkinter之外&#xff0c;不需要任何额外的库。 如果使用的是Linux系统&#xff0c;可能需要安装&#xff1a; $ pi…

519基于单片机的自动切割流程控制系统

基于单片机的自动切割流程控制系统[proteus仿真] 自动切割流程控制系统这个题目算是课程设计和毕业设计中常见的题目了&#xff0c;本期是一个基于单片机的自动切割流程控制系统 需要的源文件和程序的小伙伴可以关注公众号【阿目分享嵌入式】&#xff0c;赞赏任意文章 2&…

双指针(简化哈希)力扣15.三数之和

题目 给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k &#xff0c;同时还满足 nums[i] nums[j] nums[k] 0 。请 你返回所有和为 0 且不重复的三元组。 注意&#xff1a;答案中不可以包含重复的三元组。 …

密码学(二)

文章目录 前言一、Certificate Authorities二、Key Agreement Protocols 前言 本文来自 Intel SGX Explained 请参考&#xff1a;密码学&#xff08;一&#xff09; 一、Certificate Authorities 非对称密钥密码学中的公钥和私钥假设每个参与方都拥有其他参与方的正确公钥。…

【上海】买套二手房需要多少钱?

上次我们看了苏州的二手房&#xff0c;这次我们一起来看下上海的二手房价格如何。 数据来源 数据来自贝壳二手房&#xff0c;每个区最多获取了3千条房源信息&#xff0c;数据共计4万条左右 对数据感兴趣的朋友&#xff0c;公众号后台发送上海二手房获取数据文件 各区房源单价…

【VRTK】【VR开发】【Unity】19-VRTK实现旋转运动

课程配套学习项目源码资源下载 https://download.csdn.net/download/weixin_41697242/88485426?spm=1001.2014.3001.5503 【背景】 在实际开发中,旋转运动也是时常需要模拟的重要运动类型。常见的场景有开关门,方向盘轮胎以及拉动拉杆等等。 旋转运动的实现可以基于物理系…

STM32入门教程-2023版【3-3】gpio输入

关注 星标公众号 不错过精彩内容 大家好&#xff0c;我是硬核王同学&#xff0c;最近在做免费的嵌入式知识分享&#xff0c;帮助对嵌入式感兴趣的同学学习嵌入式、做项目、找工作! 上两小节我们已经把GPIO的结构和8种输入输出模式都讲完了&#xff0c;到这里还不懂的可以回…

JVM主要的几种垃圾回收算法

1、Java 为什么要实现自动内存管理 &#xff1f; 简化开发过程&#xff1a;通过内存自动管理可以避免手动分配和释放内存的麻烦&#xff0c;减少了内存泄漏和内存错误的风险&#xff0c;让研发能更专注于业务逻辑&#xff0c;不必纠结于内存管理的细节。 提高开发效率&#xff…

vivado 创建编译后工程

创建后期合成项目 合成后项目以合成网表、完全生成的块设计、完全生成的IP以及相应的约束。然后&#xff0c;您可以分析、布局和实施设计 注意&#xff1a;您可以使用XST或第三方合成工具来创建合成网表。 重要&#xff01;使用EDIF和NGC文件时&#xff0c;顶部单元格名称必…

ChatGPT扩展系列之网易数帆ChatBI

在当今数字化快速发展的时代,数据已经成为业务经营与管理决策的核心驱要素。无论是跨国大企业还是新兴创业公司,正确、迅速地洞察数据已经变得至关重要。然而,传统的BI工具往往对用户有一定的技术门槛,需要熟练的操作技能和复杂的查询语句,这使得大部分的企业员工难以深入…

业界首款PCIe 4.0/5.0多通道融合接口SSD技术解读

之前小编写过一篇文章劝大家不要碰PCIe 5.0 SSD&#xff0c;详细内容&#xff0c;可以再回顾下&#xff1a; 扩展阅读&#xff1a;当下最好不要入坑PCIe 5.0 SSD 如果想要进一步了解PCIe 6.0&#xff0c;欢迎点击阅读&#xff1a; 浅析PCIe 6.0功能更新与实现的挑战 PCIe 6.…