为什么IO的Thread甜蜜点是20倍? [以前:在播放框架中使用哪个ExecutionContext?] - java

我确实知道如何创建自己的ExecutionContext或导入全局的play框架。但是我必须承认,我远不是多个context / executionServices如何在后台工作的专家。

所以我的问题是,为了提高服务的性能/行为,应该使用哪个ExecutionContext?

我测试了两个选项:

import play.api.libs.concurrent.Execution.defaultContext

implicit val executionContext = ExecutionContext.fromExecutorService(Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()))

两者导致可比的性能。

我使用的动作是在playframework 2.1.x中实现的。 SedisPool是我自己的对象,带有普通sedis / jedis客户端池的额外Future包装。

def testaction(application: String, platform: String) = Action {
    Async(
      SedisPool.withAsyncClient[Result] { client =>
        client.get(StringBuilder.newBuilder.append(application).append('-').append(platform).toString) match {
          case Some(x) => Ok(x)
          case None => Results.NoContent
        }
      })
  }

与Node.js和Go中完全相同的功能相比,这种性能差异表现得好或稍慢。但仍然比Pypy慢。但是比Java中的相同方法要快(在这种情况下,使用jedis使用阻塞调用redis)。我们使用加特林进行负载测试。我们在Redis的基础上为简单服务进行技术的“竞争”,其标准是“编码人员付出相同的努力”。
我已经使用fyrie进行了测试(除了我不喜欢API的事实),它的行为与此Sedis实现几乎相同。

但这不是我的问题。我只想了解有关Playframework / scala的这一部分的更多信息。

有建议的行为吗?还是有人可以指出我的方向?我现在开始使用scala,距离专家还很远,但是我可以带自己逐步找到代码答案。

谢谢你的帮助。

更新-更多问题!

篡改池中的线程数后,我发现:
Runtime.getRuntime()。availableProcessors()* 20

使我的服务性能提高了15%到20%(以每秒请求数和平均响应时间来衡量),实际上使它比node.js和go略好(尽管很少)。所以我现在有更多问题:
-我测试了15倍和25倍,而20倍似乎是一个不错的选择。为什么?有任何想法吗?
-是否会有其他更好的设置?其他“最佳景点”?
-最佳点是20倍,还是取决于我正在运行的机器/ jvm的其他参数?

更新-有关此主题的更多文档

在播放框架文档中找到更多信息。
http://www.playframework.com/documentation/2.1.0/ThreadPools

对于IO,他们确实建议我做些什么,但是提供了一种通过Akka.dispatchers进行操作的方法,该Akka.dispatchers可通过* .conf文件进行配置(这应该使我的操作感到高兴)。

所以现在我正在使用

implicit val redis_lookup_context: ExecutionContext = Akka.system.dispatchers.lookup("simple-redis-lookup")

由调度员配置

akka{
    event-handlers = ["akka.event.slf4j.Slf4jEventHandler"]
    loglevel = WARNING
    actor {
        simple-redis-lookup = {
            fork-join-executor {
                parallelism-factor = 20.0   
                #parallelism-min = 40
                #parallelism-max = 400
            }
        }
    }
}

一旦JVM变得“热”,它给我带来了大约5%的提升(现在将目光投向了),并提高了性能的稳定性。我的系统管理员很乐意使用这些设置,而无需重建服务。

我的问题仍然存在。为什么会有这个数字?

参考方案

我认为优化的方法是:

看一下单线程性能,然后
看看事情如何并行化,然后
冲洗并重复直到获得所需的性能或放弃为止。

单线程优化

通常,单个线程的性能将在代码的单个组件或部分上进行控制,并且可能是:

受CPU约束的部分,实际上可能是在从RAM读取时绑定的(这不是分页)。 JVM和更高级别的工具通常无法区分CPU和RAM。性能分析器(例如JProfiler)对于定位代码热点非常有用)

您可以通过优化代码以降低CPU使用率或RAM读写速率来提高性能。

分页问题,​​应用程序的内存不足,正在与磁盘分页或从磁盘分页

您可以通过添加RAM,减少内存使用,为进程分配更多的物理RAM或减少OS上的内存负载来提高性能。

延迟问题,线程正在等待从套接字,磁盘或类似对象读取数据,或者正在等待将数据提交到磁盘。

您可以通过使用更快的磁盘(例如,旋转生锈-> SSD),使用更快的网络(1GE-> 10GE)或通过提高所使用的网络应用程序的响应速度(调整数据库)来提高单线程性能。

但是,如果您可以运行多个线程,则单线程中的延迟不会太令人担忧。当一个线程被阻止时,另一个线程可以使用CPU(用于换出上下文并替换CPU缓存中的大多数项目的开销)。那么您应该运行多少个线程?

多线程

假设线程在CPU上花费了大约50%的时间,在IO上花费了50%的时间。在这种情况下,每个CPU可以被2个线程充分利用,您看到的吞吐量提高了2倍。如果线程花费大约1%的时间使用CPU,则(所有条件都相同)您应该能够同时运行100个线程。

但是,这是可能发生许多奇怪效果的地方:

上下文切换需要一定的成本,因此理想情况下,您需要将它们最小化。如果您的等待时间很少且不多,而不是那么频繁,那么您将获得更高的整体系统性能。这种效果意味着将线程增加n x的数量,将永远无法完全提高n x的吞吐量。并且在临界点之后,随着您增加n,因此性能将下降。
同步,信号量和互斥量。通常,代码的一小部分会获得信号量或互斥量,以确保一次只能输入一个(或数量有限)线程。尽管只有几个线程,但这很少会影响性能。但是,如果此代码块花费任何可观的时间,并且有很多线程,这将成为影响系统性能的主要因素。例如,假设有一个受保护的单线程块需要10毫秒来执行,例如通过查询数据库来执行。由于一次只能输入一个线程,因此您实际可以执行的最大线程数为1000ms / 10ms或100。所有其他线程最终将在此块的队列中排在彼此后面。
资源:随着并行度的提高,您将加载各种以前轻载的组件。随着这些负载变得越来越重,其他线程最终将阻塞,等待来自它们的数据。最终,额外的并行性最终会在计算机上的所有线程中造成延迟。这些组件包括:

内存
磁盘通道
网络
网络服务(例如您的数据库)。我无法告诉您我已经优化Java几次,以至于DB限制了吞吐量。

如果发生这种情况,则您需要重新考虑算法,更改服务器,网络或网络服务或降低并行度。

影响可以运行多少线程的因素

从上面可以看到,其中涉及大量的因素。结果,线程/核心的最佳结合点是多种原因引起的事故,包括:

您使用的CPU的性能,尤其是:

核心数
是否SMT
缓存量
速度

您有多少RAM和内存总线的速度
操作系统和环境:

处理器上正在执行多少其他工作
Windows / Linux / BSD / etc等都有不同的多任务处理特性
JVM版本(每个版本具有不同的特征,其中一些特征与其他特征有更多不同)
网络上的流量和拥塞以及对所涉及的交换机和路由器的影响

您的密码

您的算法
您使用的库

根据经验,没有魔术公式可以计算出先验最佳线程数。正如您所做的那样,最好凭经验解决此问题(如上所示)。如果需要概括,则需要在所选操作系统上通过不同的CPU体系结构,内存和网络进行性能采样。

几个易于观察的指标在这里很有用:

每个核心的CPU利用率-帮助检测进程是否受CPU限制
平均负载-这报告了进程(或线程,如果使用LWP)如何等待CPU。如果这个数字上升到大于CPU核心数的数字,则说明您的CPU核心肯定受CPU限制。

如果需要优化,请获取最好的性能分析工具。您将需要一种特定的工具来监视操作系统(例如,DTrace用于Solaris),以及一个用于JVM(我个人很喜欢JProfiler)。这些工具可让您精确放大我在上面描述的区域。

结论

碰巧您的特定代码(在特定的Scala库版本,JVM版本,OS,服务器和Redis服务器上)运行,因此每个线程大约在95%的时间内等待I / O。 (如果运行单线程,您会发现CPU负载约为5%)。

在这种配置下,这允许大约20个线程最佳地共享每个CPU。

这是最好的地方,因为:

如果运行的线程较少,那么您将浪费CPU周期来等待数据
如果您运行更多线程:

体系结构的一个组件(例如磁盘或CPU RAM总线)已饱和,从而阻止了额外的吞吐量(在这种情况下,您会发现CPU利用率低于或远低于〜90%),或者
线程上下文切换成本开始超过增加线程的增量收益(您将看到CPU利用率达到> 95%)

JAVA:字节码和二进制有什么区别? - java

java字节代码(已编译的语言,也称为目标代码)与机器代码(当前计算机的本机代码)之间有什么区别?我读过一些书,他们将字节码称为二进制指令,但我不知道为什么。 参考方案 字节码是独立于平台的,在Windows中运行的编译器编译的字节码仍将在linux / unix / mac中运行。机器代码是特定于平台的,如果在Windows x86中编译,则它将仅在Win…

java:继承 - java

有哪些替代继承的方法? java大神给出的解决方案 有效的Java:偏重于继承而不是继承。 (这实际上也来自“四人帮”)。他提出的理由是,如果扩展类未明确设计为继承,则继承会引起很多不正常的副作用。例如,对super.someMethod()的任何调用都可以引导您通过未知代码的意外路径。取而代之的是,持有对本来应该扩展的类的引用,然后委托给它。这是与Eric…

Java:BigInteger,如何通过OutputStream编写它 - java

我想将BigInteger写入文件。做这个的最好方式是什么。当然,我想从输入流中读取(使用程序,而不是人工)。我必须使用ObjectOutputStream还是有更好的方法?目的是使用尽可能少的字节。谢谢马丁 参考方案 Java序列化(ObjectOutputStream / ObjectInputStream)是将对象序列化为八位字节序列的一种通用方法。但…

Java DefaultSslContextFactory密钥库动态更新 - java

我有一个使用org.restlet.engine.ssl.DefaultSslContextFactory的现有应用程序和一个在服务器启动时加载的密钥库文件。我有另一个应用程序,该应用程序创建必须添加的证书服务器运行时动态地更新到密钥库文件。为此,我在代码中创建了证书和私钥,然后将其写入到目录。该目录由bash脚本监视,该脚本检查是否有新文件,如果出现,它将…

Java-如何将此字符串转换为日期? - java

我从服务器收到此消息,我不明白T和Z的含义,2012-08-24T09:59:59Z将此字符串转换为Date对象的正确SimpleDateFormat模式是什么? java大神给出的解决方案 这是ISO 8601标准。您可以使用SimpleDateFormat simpleFormat = new SimpleDateFormat("yyyy-MM…