说明了阿姆达尔定律

本文将简单地解释阿姆达尔定律 我们将通过一个案例研究来演示当您更改执行任务的线程数时吞吐量和延迟如何变化。 我们还可以根据您自己的性能调整任务为您得出正确的结论。

首先,让我们刷新定义的记忆。

  • 吞吐量–每个时间单位关闭的计算任务数。 示例–一分钟内支付1,000张信用卡。
  • 延迟-调用操作与获得响应之间的延迟。 示例–处理信用卡交易最多需要25毫秒的时间。

说明了阿姆达尔定律 我们构建的案例是模拟一个典型的Java EE应用程序,该应用程序在JVM中进行一些计算并调用外部数据源。 就像许多Java EE应用程序一样,用户启动在应用程序服务器中处理的HttpRequest,依次调用关系数据库或Web服务。 该仿真通过消耗大量CPU周期来计算大型素数并强制线程休眠一段时间来实现此目的。 尽管乍一看听起来很奇怪,但是这与等待外部资源时如何处理线程类似。 睡眠/等待线程从等待其调度的线程列表中删除。 直到响应到达。

我们构建的应用程序还包含一个线程池。 同样,就像大多数现代应用程序服务器一样。 我们将更改池的大小。 然后,我们使用大量模拟的“ HttpRequest”轰炸该应用程序,以了解负载下的延迟和吞吐量如何。

但是在这样做之前,我们可以对结果是什么样子做出最好的猜测吗? 该测试是使用2010年中的Macbook Pro在2.66MHz Intel i7上进行的。 两个核心,启用超线程。 因此,四个虚拟内核可供我们的线程使用。 在任何给定的时刻,我们的四个核心中的每个核心都在一个线程中取得进步。

在单线程环境中,我们的测试中使用的代码包含一个片段,该片段燃烧CPU周期约50毫秒,睡眠1000毫秒。 因此,在JVM中执行代码与被外部调用阻止之间的比率为20-1。

考虑到总请求时间为1,050毫秒,其中有1,000毫秒处于等待状态,我们可以计算出每个内核要处理的最佳任务数。 每个内核的最佳任务数等于1050 /(1050-1000)= 21个任务。 当前正在处理其中之一,有20个正在等待外部资源。 考虑到我们总共有四个内核,线程的最佳数量应该接近84。

现在,在使用要执行的4000个任务运行测试并在1到3200之间改变池中的线程数之后,我们得到了以下结果:

说明了阿姆达尔定律

实际上,我们有一个很好的阿姆达尔定律案例-增加60-100个线程后增加线程数量不会对性能产生重大影响。 但这并不能完全证明我们最初的猜测在84个线程上具有最佳结果。 使用84个线程,我们每秒可以完成大约32个任务。 但是,当池的大小在200-1600个线程之间时,它们都能够每秒执行大约39个任务。 为什么这样? 显然,现代处理器比我们的幼稚计算要聪明得多,并且调度程序不只是应用简单的循环算法来选择线程。 如果我们的读者中有任何一个可以解释这个惊喜,那么我们非常渴望聆听。

但这是否表明对于这种特殊情况,我们可以抛出比最初指示的84个线程更多的线程? 没那么快。 让我们看一下另一个指标–延迟:

说明了阿姆达尔定律

我们看到了两个显着的延迟增加-从16个线程增加到32个线程时,中值延迟从1,100ms增加到1,500ms。从100增加到200时,中值延迟又增加了一个非常严重的时间-然后,增加的时间已经从3200增加到5,000,它保持快速增长。 指示上下文切换开始收费。

现在在现实生活中,这意味着在这种情况下抛出800个线程可能不是一个好的决定。 即使吞吐量很好,并且我们每秒处理大约39个请求,但平均用户必须等待18秒才能对他们的请求做出响应。

现在从这里可以得出什么结论? 如果没有吞吐量和延迟的实际要求,我们将无法进一步发展。 但是在现实生活中,您有一些要求。 形式为“任何请求都不能超过3,000ms,并且平均请求必须在2,000ms之内完成”。 并且“我们必须每秒能够处理30个请求”。 使用这些样本标准,我们看到我们的最佳结合点在64到100个线程之间。

另一个重要的注意事项–当将池中的线程数增加到3200(实际上已经是2400)时,我们已经用尽了可用资源,并且遇到了java.lang.OutOfMemoryError错误:无法创建新的本机线程消息。 JVM的这种说法是,操作系统为您的应用程序用尽了资源。 根据您的操作系统,您可以绕过这些限制(例如,Linux中的ulimit),但这超出了本文的范围。

参考: Plumbr博客博客上的JCG合作伙伴弗拉基米尔·索尔(Vladimir Sor) 说明阿姆达尔定律

翻译自: https://www.javacodegeeks.com/2013/02/amdahls-law-illustrated.html