惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

K
Kaspersky official blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
B
Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
N
Netflix TechBlog - Medium
腾讯CDC
IT之家
IT之家
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
C
Cyber Attacks, Cyber Crime and Cyber Security
罗磊的独立博客
P
Privacy International News Feed
D
DataBreaches.Net
T
Threatpost
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Google DeepMind News
Google DeepMind News
博客园 - 司徒正美
博客园 - 三生石上(FineUI控件)
S
Securelist
P
Palo Alto Networks Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Spread Privacy
Spread Privacy
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
S
Secure Thoughts
Security Archives - TechRepublic
Security Archives - TechRepublic
L
LangChain Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
T
Troy Hunt's Blog
The Last Watchdog
The Last Watchdog
T
Tor Project blog
V
Vulnerabilities – Threatpost
T
Threat Research - Cisco Blogs
H
Heimdal Security Blog
Hugging Face - Blog
Hugging Face - Blog
Hacker News: Ask HN
Hacker News: Ask HN
阮一峰的网络日志
阮一峰的网络日志
博客园 - 叶小钗
S
SegmentFault 最新的问题
Know Your Adversary
Know Your Adversary
博客园_首页
N
News and Events Feed by Topic
G
GRAHAM CLULEY
L
LINUX DO - 热门话题
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Help Net Security
Help Net Security
Simon Willison's Weblog
Simon Willison's Weblog
H
Hacker News: Front Page
大猫的无限游戏
大猫的无限游戏
AWS News Blog
AWS News Blog
The Cloudflare Blog
AI
AI

博客园 - xiaoxi666

深度学习优化器生态演化图谱 神经网络常见的40多种激活函数(应用场景+数学公式+代码实现+函数图象) 程序员究竟要不要写文章 使用Lean4进行形式化建模(以Java线程池为例) 用好 JUnit 5 的高级特性:提升单测效率和质量 好端端的线程池,怎么就卡死了? DeepSeek又在节前放大招! Dubbo实战:四步实现注册中心平滑迁移 LLM中的Top-K/Top-p/温度都是怎么发挥作用的? RLHF各种训练算法科普 近期最值得关注的AI技术报告与Agent综述! 通俗理解自注意力机制和多头注意力机制 你的Docker部署能更简单吗?GitLab集成与多进程管理又怎么做? 什么是南北向流量和东西向流量? 如何实现LLM的通用function-calling能力? 推荐一枚宝藏Up主,顺便聊聊感想 Embedding 模型部署及效果评测 立即执行函数在前端国际化方案中的应用 推荐一款id生成器: Hashids
推荐一款工具,辅助估算线程池参数
xiaoxi666 · 2022-10-05 · via 博客园 - xiaoxi666

前言

相信接触过并发系统的小伙伴们基本都使用过线程池,或多或少调整过对应的参数。以 Java 中的经典模型来说,能够配置核心线程数、最大线程数、队列容量等等参数。

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
  this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
       Executors.defaultThreadFactory(), defaultHandler);
}

一般情况下,我们设置参数步骤是:

  1. 确定业务属性,比如IO密集型、CPU密集型、混合型等。

  2. 参考理想化的线程计算模型算出理论值。如《Java并发编程实战》一书中的理想化模型:

  1. 辅之以压测等手段对参数进行逐步调优。

  2. 再高级点,我们也可以对线程池进行监控,并实时对参数进行调整,也即参数动态化方案。可参考:Java线程池实现原理及其在美团业务中的实践

工具推荐

本文则推荐一款工具,它不关心任务内部是如何实现的,而是通过计算运行时的各种系统指标(包括 CPU计算时间、IO等待时间、内存占用等)来直接计算线程池参数的。我们可以直接在这些参数的基础上,再配合压测进行调优,避免盲目调参。

这个工具叫做 dark_magic,直译就是黑魔法,源码参见 https://github.com/sunshanpeng/dark_magic。里面的备注已经很详细,本文不再赘述。只提一下系统指标的计算方式。

指标的计算方式

CPU计算时间 和 IO等待时间 的计算:

  • 先执行两遍任务,进行预热。

  • 获取当前线程的 CPU计算时间,记为 C1

  • 再执行一遍任务

  • 获取当前线程的 CPU计算时间,记为 C2

  • 计算当前任务执行需要的 CPU计算时间:C2 - C1

  • 计算当前任务执行中的 IO等待 时间:总耗时 - CPU计算时间

其中,计算当前线程的 CPU计算时间使用 rt.jar 包中的方法:

ManagementFactory.getThreadMXBean().getCurrentThreadCpuTime()

内存占用的计算:

  • 生成1000个(可配置)任务加入到阻塞队列中

  • 循环调用 15次(可配置) System.gc() 函数,触发gc

  • 记录目前的内存使用情况,记为 M0

  • 再次生成1000个(可配置)任务加入到阻塞队列中

  • 循环调用 15次(可配置) System.gc() 函数,触发gc

  • 记录目前的内存使用情况,记为 M1

  • 计算当前任务执行需要的内存:M1 - M0

其中,计算内存使用 rt.jar 包中方法:

Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()

使用方法

该工具的使用方法也很简单:

  1. 把你的业务代码封装为一个函数,放到 createTask 函数中。

  2. 设定 CPU使用率的期望值、队列占用内存的期望值。

  3. 执行,等待结果输出。

下面分别展示一个CPU密集型和IO密集型的输出(我们设置的 CPU 使用率期望值为 60%,队列占用内存的期望值为 10MB ):

# CPU密集型

Target queue memory usage (bytes): 10240
createTask() produced threadpool.AsyncCPUTask which took 40 bytes in a queue
Formula: 10240 / 40
* Recommended queue capacity (bytes): 256
Number of CPU: 8
Target utilization: 0.59999999999999997779553950749686919152736663818359375
Elapsed time (nanos): 3000000000
Compute time (nanos): 2949786000
Wait time (nanos): 50214000
Formula: 8 * 0.59999999999999997779553950749686919152736663818359375 * (1 + 50214000 / 2949786000)
* Optimal thread count: 4.79999999999999982236431605997495353221893310546875000
# IO密集型

Target queue memory usage (bytes): 10240
createTask() produced threadpool.AsyncIOTask which took 40 bytes in a queue
Formula: 10240 / 40
* Recommended queue capacity (bytes): 256
Number of CPU: 8
Target utilization: 0.59999999999999997779553950749686919152736663818359375
Elapsed time (nanos): 3000000000
Compute time (nanos): 55528000
Wait time (nanos): 2944472000
Formula: 8 * 0.59999999999999997779553950749686919152736663818359375 * (1 + 2944472000 / 55528000)
* Optimal thread count: 259.19999999999999040767306723864749073982238769531250000

针对线程数的计算而言:

  • 对于 CPU 密集型任务,IO等待时间(Wait time) 远远小于 CPU计算时间(Compute time)。计算出来的推荐核心线程数为 4.8。

  • 对于 IO 密集型任务,IO等待时间(Wait time) 远远大于 CPU计算时间(Compute time)。计算出来的推荐核心线程数为 259。

而队列大小与任务中使用的对象大小有关,这里的内存使用是通过计算 gc 执行前后的内存大小差异得到的(本文中的例子均为 40 B)。由于该算法内部使用 System.gc() 触发 gc。但由于 gc 不一定真的会立刻执行,所以拿到的队列结果可能不一定准确,只能作为粗略参考。

总结

总的来说,dark_magic 这款工具以任务执行时的系统指标数据为基础,计算出比较合理的线程池参数,给我们进行后续的压测调参提供了相对比较合理的参考,值得推荐。