不,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 容量。