【问题标题】:Linux tuning for Java Concurrent Performance针对 Java 并发性能的 Linux 调优
【发布时间】:2017-01-16 14:58:31
【问题描述】:

我有一个关于为 java 性能调整 linux 的大问题,所以我从我的案例开始。

我有一个应用程序运行多个相互通信的线程。我的典型工作流程是:

1) 一些消费者线程在一个公共对象锁上同步并在其上调用 wait()。
2) 一些生产者线程通过 Selector 等待来自网络的数据。
2.1)生产者接收数据并形成具有接收时间戳(微秒精度)的对象。
2.2) 生产者将此数据包放入某个交换映射中,并在公共锁上调用 notifyAll。
3) 消费者线程唤醒并读取生产对象。
3.1)消费者创建新对象并在其中写入接收到的时间戳和当前时间戳之间的时间差(以微秒为单位)。这样我可以监控反应时间。

而这个反应时间就是整个问题。

当我在自己的机器上测试我的应用程序时,我通常会得到大约 200-400 微秒的反应时间,但是当我在我的生产 linux 机器上监控它时,我会得到 2000 到 4000 微秒的数字!

现在我正在运行 ubuntu 16.04 作为我的生产操作系统和 Oracle jdk 8-111。我有一个带有 2 个 Xeon 处理器的物理服务器。我在这台服务器上只运行常用的操作系统守护程序和我的应用程序,因此与我的开发笔记本相比,资源充足。

我将我的 java 应用程序作为带有标志的 jar 文件运行: sudo chrt -r 77 java -server -XX:+UseNUMA -d64 -Xmx1500m -XX:NewSize=1000m -XX:+UseG1GC -jar ...

我使用 sudo chrt 来更改优先级,因为我认为是这种情况,但它没有帮助。

我调整了 BIOS 以获得最佳性能并关闭了 C-States。

我还可以调整什么来加快反应时间和减少上下文切换?

【问题讨论】:

    标签: java linux performance concurrency


    【解决方案1】:

    不,Linux 上没有单一的echo 1 > /proc/sys/unlock_concurrent_magic 选项可以全局提高并发性能。如果存在这样的选项,它会默认启用。

    事实上,尽管总体上是可调的,但您不会找到很多专门对 Linux 上的原始并发产生重大影响的可调参数。那些可能经常偶然与并发相关的帮助 - 即启用某些东西(比如说 THP)会减慢您的特定负载,并且由于至少部分缓慢发生在锁定状态下,因此整个并发吞吐量受到影响。

    Java 并发与操作系统

    然而,我的经验是,Java 应用程序很少直接受到操作系统级并发行为的影响。事实上,大多数 Java 并发是在不使用操作系统特性的情况下高效实现的,并且跨操作系统的行为相同。与并发相关的 JVM 与操作系统接触的主要地方是线程创建、销毁和等待/阻塞。最近的 Linux 内核对这三个内核都有很好的实现,因此您遇到瓶颈是不寻常的(实际上,一个经过良好调整的应用程序不应该创建大量线程,还应该尽量减少阻塞)。

    所以我发现性能差异很可能是由于硬件、应用程序配置、应用负载或其他方面的其他差异造成的。

    描述你的表现

    这是我首先尝试描述的开发主机和生产系统之间的性能差异。

    在顶层,差异要么是因为生产堆栈对于相关负载实际上较慢,要么是因为本地测试不能准确反映生产加载

    您可以做的一个快速测试来区分这些情况,即运行您正在运行的任何本地测试,以便在空载的生产服务器上获得 200-400us 的响应时间。如果服务器仍然得到的响应时间要差 10 倍,那么您就知道您的测试可能是合理的,并且差异确实在生产堆栈中。

    此时,问题可能仍然存在于操作系统、软件配置、硬件等方面。因此,您应该尝试平分生产堆栈和本地主机之间的差异 - 将任何可调参数设置为相同值,调查任何特定于应用程序的配置差异,尝试描述任何硬件差异。

    一个大问题是生产服务器通常采用多套接字配置,这可能会增加一个数量级的争用成本,因为需要跨套接字通信(通常 100 多个周期) - 而开发盒通常是多核但单插槽,因此争用开销包含在共享 L3 中(通常约为 30 个周期)。

    另一方面,您可能会发现您的本地测试在生产服务器上也正常运行,因此真正的问题是您的测试并不代表真正的生产负载。然后,您应该努力描述真实的生产负载,以便您可以复制它,然后在本地对其进行调整。如何在本地调整它当然可以写一两本书(或者需要一两本书的高薪承包商),所以你必须回到一个更窄的问题才能在这里获得有用的帮助。

    “大铁”与您的笔记本电脑

    一个普遍的谬论是,“大铁”在任何事情上都比你的小笔记本电脑更快。事实上,许多操作的情况恰恰相反,尤其是当您测量操作延迟(而不是总吞吐量)时。

    例如,服务器部分的内存延迟通常比客户端部分慢 50%,即使比较单套接字系统也是如此。 John McCalpin reports 客户端 Sandy Bridge 部分的主存延迟为 54.6 ns,相应的服务器部分为 79 ns。众所周知,服务器内存和内存控制器设计的途径是在延迟与吞吐量、可靠性以及支持更多内核和总 DRAM 的能力之间进行权衡1

    您特别提到您的生产服务器是“2 Xeon 处理器”,我认为这是双插槽系统。一旦你引入了第二个套接字,你就完全改变了同步机制。在单核系统上,当单独的线程争用时,最坏的情况是通过共享 L3 发送缓存行和一致性流量,延迟为 30-40 个周期。

    但是,在具有多个套接字的系统上,并发流量通常必须通过套接字之间的 QPI 链接,其延迟大约为 DRAM 访问量级,可能为 80 ns(即,在 3GHz 机器上为 240 个周期) .因此,仅硬件架构就可以降低近一个数量级的速度。

    此外,notifyAll 类型的场景正如您所描述的那样,您的工作流程通常会随着更多的内核和更多的线程而变得更糟。例如,使用更多内核,您不太可能在同一个超线程上运行两个通信进程(这会显着加快线程间协调,但在其他方面是不可取的),并且总争用和一致性流量可能与数量成比例地增加核心数(例如,因为当您唤醒线程时,缓存线必须围绕每个核心进行乒乓球运动)。

    因此,经常出现这样的情况,即竞争激烈(通常设计不佳)的算法在“大铁”上的表现比在单插槽消费者系统上的表现要差得多。


    1 例如,通过缓冲,这会增加延迟,但会增加主机的最大 RAM 容量。

    【讨论】:

    • 我的开发笔记本是 1 cpu windows,生产是 2 cpu linux。我觉得我的问题在于低级操作系统线程管理。我的工作线程在 linux 中的唤醒速度较慢,并且在 linux 中的中断比在 windows 中的多。所以我问了一个大致的方向,我可以在 linux 中调整这些东西。理想情况下,我希望将我的工作 java 线程分配给一个空闲的 CPU 内核并在那里运行,而无需其他线程进行上下文切换。
    • 不要“感觉”。测量。
    • 我在 JMH 中测量了我的计算代码,它运行得更快。因此,当我添加并发性时,我会降低速度。因此,我发布了一个关于调整 linux 和 java 的问题,涉及状态更改和同步以及如何使它们更快。我的并发工作流程也发布在原始问题中。你对我关于实际调音的实际问题有什么要说的吗?
    • 它运行“比什么快得多”?我没有说你的计算代码的速度。同样,您假设性能差异在某种程度上是由于 Linux 造成的,并且可以将其调整掉。 不太可能是这种情况。如果是这种情况,您可以轻松地展示它 - 通过在 same 硬件上运行 same 程序,使用不同的操作系统。如果有 10 倍的差异,那么您就取得了重大发现,并且您的问题会引起很多关注。
    • 这完全有可能,例如,差异是由于硬件 - 你提到你的生产服务器有“2个至强处理器”,而你的笔记本电脑是单核的。这使得大多数竞争并发操作的服务器变慢(延迟)。我会在答案中添加更多细节。
    猜你喜欢
    • 2015-08-02
    • 2013-11-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多