11

我在 Linux 机器(AMD 6 核,16 GB RAM)上使用 JVM(Oracle 1.7 64 位)来查看应用程序中的线程数如何影响性能。我希望测量上下文切换在哪一点会降低性能。

我创建了一个创建线程执行池的小应用程序:

Executors.newFixedThreadPool(numThreads)

numThreads每次运行程序时都会进行调整,以查看它的效果。

然后我将numThread作业(的实例java.util.concurrent.Callable)提交到池中。每个人增加一个AtomicInteger,做一些工作(创建一个随机整数数组并对其进行洗牌),然后睡一会儿。这个想法是模拟 Web 服务调用。最后,作业重新提交到池中,因此我始终有numThreads作业工作。

我正在测量吞吐量,如每分钟处理的作业数量。

有了几千个线程,我每分钟可以处理多达 400,000 个作业。超过 8000 个线程,结果开始变化很大,这表明上下文切换正在成为一个问题。但是我可以继续将线程数增加到 30,000 并且仍然获得更高的吞吐量(每分钟 420,000 到 570,000 个作业之间)。

现在的问题是:我得到了一个java.lang.OutOfMemoryError: Unable to create new native thread拥有超过 31,000 个工作岗位的职位。我试过设置-Xmx6000M这没有帮助。我试过玩,-Xss但这也无济于事。

我读过这ulimit可能很有用,但增加ulimit -u 64000并没有改变任何东西。

有关信息:

[root@apollo ant]# ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 127557
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 1024
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

所以问题 #1:我必须做什么才能创建更大的线程池?

问题 #2:我应该在什么阶段看到上下文切换真正降低吞吐量并导致进程停止?


这是一些结果,在我对其进行修改以进行更多处理(如建议的那样)并开始记录平均响应时间(也如建议的那样)之后。

// ( (n_cores x t_request) / (t_request - t_wait) ) + 1
// 300 ms wait, 10ms work, roughly 310ms per job => ideal response time, 310ms
// ideal num threads = 1860 / 10 + 1 = 187 threads
//
// results:
//
//   100 =>  19,000 thruput,  312ms response, cpu < 50%
//   150 =>  28,500 thruput,  314ms response, cpu 50%
//   180 =>  34,000 thruput,  318ms response, cpu 60%
//   190 =>  35,800 thruput,  317ms response, cpu 65%
//   200 =>  37,800 thruput,  319ms response, cpu 70%
//   230 =>  42,900 thruput,  321ms response, cpu 80%
//   270 =>  50,000 thruput,  324ms response, cpu 80%
//   350 =>  64,000 thruput,  329ms response, cpu 90%
//   400 =>  72,000 thruput,  335ms response, cpu >90%
//   500 =>  87,500 thruput,  343ms response, cpu >95%
//   700 => 100,000 thruput,  430ms response, cpu >99%
//  1000 => 100,000 thruput,  600ms response, cpu >99%
//  2000 => 105,000 thruput, 1100ms response, cpu >99%
//  5000 => 131,000 thruput, 1600ms response, cpu >99%
// 10000 => 131,000 thruput, 2700ms response, cpu >99%,  16GB Virtual size
// 20000 => 140,000 thruput, 4000ms response, cpu >99%,  27GB Virtual size
// 30000 => 133,000 thruput, 2800ms response, cpu >99%,  37GB Virtual size
// 40000 =>       - thruput,    -ms response, cpu >99%, >39GB Virtual size => java.lang.OutOfMemoryError: unable to create new native thread

我将它们解释为:

1) 即使应用程序在 96.7% 的时间内处于休眠状态,仍然需要完成大量线程切换 2) 上下文切换是可测量的,并显示在响应时间中。

有趣的是,在调整应用程序时,您可能会选择可接受的响应时间,例如 400 毫秒,并增加线程数,直到获得该响应时间,在这种情况下,这将使应用程序处理大约 95,000 个请求分钟。

人们常说,理想的线程数接近核心数。在具有等待时间(阻塞线程,例如等待数据库或 Web 服务响应)的应用程序中,计算需要考虑到这一点(参见上面的公式)。但是,当您查看结果或调整到特定响应时间时,即使是理论上的理想也不是实际理想。

4

2 回答 2

7

我得到一个 java.lang.OutOfMemoryError: Unable to create new native thread with more than about 31,000 jobs。我试过设置 -Xmx6000M 这没有帮助。我尝试使用 -Xss 但这也无济于事。

-Xmx 设置无济于事,因为线程堆栈不是从堆中分配的。

正在发生的事情是 JVM 正在向操作系统请求一个内存段(在堆之外!)来保存堆栈,而操作系统正在拒绝该请求。最可能的原因是 ulimit 或操作系统内存资源问题:

  • “数据段大小”ulimit 是无限的,所以这不应该是问题。

  • 这样就剩下内存资源了。一次 1Mb 的 30,000 个线程约为 30Gb,这比您拥有的物理内存多得多。我的猜测是 30Gb 的虚拟内存有足够的交换空间,但是您将边界推得太远了。

-Xss 设置应该会有所帮助,但您需要使请求的堆栈大小小于默认大小1m。此外,还有一个硬性的最小尺寸。

问题 #1:我必须做什么才能创建更大的线程池?

将默认堆栈大小减小到当前值以下,或增加可用虚拟内存量。(不建议使用后者,因为看起来您已经严重过度分配了。)

问题 #2:我应该在什么阶段看到上下文切换真正降低吞吐量并导致进程停止?

这是无法预测的。这将高度依赖于线程实际在做什么。事实上,我不认为你的基准测试会给你答案,告诉你一个真正的多线程应用程序将如何表现。


Oracle 网站关于线程堆栈空间的主题是这样说的:

在 Java SE 6 中,Sparc 的默认值在 32 位 VM 中为 512k,在 64 位 VM 中为 1024k。在 x86 Solaris/Linux 上,32 位 VM 中为 320k,64 位 VM 中为 1024k。

在 Windows 上,默认线程堆栈大小是从二进制文件 (java.exe) 中读取的。从 Java SE 6 开始,该值在 32 位 VM 中为 320k,在 64 位 VM 中为 1024k。

您可以通过运行 -Xss 选项来减小堆栈大小。例如:

  java -server -Xss64k

请注意,在某些版本的 Windows 上,操作系统可能会使用非常粗略的粒度对线程堆栈大小进行四舍五入。如果请求的大小比默认大小小 1K 或更多,则堆栈大小向上舍入到默认值;否则,堆栈大小将向上舍入为 1 MB 的倍数。

64k 是每个线程允许的最小堆栈空间量。

于 2013-01-27T10:46:57.757 回答
2

这里有一些要点/方法,我会遵循:

  1. 看看上下文切换中使用的数据。尝试使用一些大的 List 或 Map 而不是布尔值或字符串。

  2. 不要在开始时尝试创建固定池,而是尝试使用缓存池。

  3. 与其让线程在完成一些小工作后消失,不如让它们活着并一次又一次地回来做小块的工作。

  4. 尽量保持线程的处理时间更长。

于 2013-01-27T09:26:08.627 回答