Jvm 为什么对scrypt()的第一次调用只使用1%的CPU,在GCE中需要半个小时?

Jvm 为什么对scrypt()的第一次调用只使用1%的CPU,在GCE中需要半个小时?,jvm,google-compute-engine,jit,scrypt,Jvm,Google Compute Engine,Jit,Scrypt,[总结与回答:显然,问题在于随机数生成器的种子需要很长时间。请参阅下面我的答案。] 在Google Compute Engine(GCE)中,我的Java虚拟机应用程序对密码哈希函数发出的第一个请求需要很长时间,因为我想代码还没有及时编译。所以我在为scrypt做热身 虚拟scrypt(“pswd”,2,1,1)在服务器启动时调用。然而,发生的情况是,CPU上升到300%+,停留10-20秒,然后回落到1%,尽管对scrypt()的请求尚未完成。现在,CPU保持在1%的状态长达数分钟(最多半小时

[总结与回答:显然,问题在于随机数生成器的种子需要很长时间。请参阅下面我的答案。]

在Google Compute Engine(GCE)中,我的Java虚拟机应用程序对密码哈希函数发出的第一个请求需要很长时间,因为我想代码还没有及时编译。所以我在为scrypt做热身 虚拟
scrypt(“pswd”,2,1,1)
在服务器启动时调用。然而,发生的情况是,CPU上升到300%+,停留10-20秒,然后回落到1%,尽管对scrypt()的请求尚未完成。现在,CPU保持在1%的状态长达数分钟(最多半小时,使用2个GCE vCPU),直到最终完成scrypt()

为什么会有这种奇怪的行为

为什么scrypt()在完成之前不能继续以300%的CPU运行?不是 内存不足。看看下面的Docker统计数据

在第一个scrypt()请求之后,后续请求“立即”完成。例如,这:
SCryptUtil.scrypt(“pswd”,65536,8,1)
所需时间<0.2秒,尽管它比:
SCryptUtil.scrypt(“pswd”,2,1,1)
这(如前所述)是我的第一个scrypt()调用,通常需要几分钟,使用4个GCE vCPU,通常需要半小时左右,使用2个GCE vCPU

我正在使用一个带有4个vCPU、3.6 GB RAM的GCE实例。Docker 1.11.1。OpenJDK1.8.0_77。在Alpine Linux 3.3 Docker容器中,Ubuntu 16.04 Docker主机。无法在我的笔记本电脑上复制;在我的笔记本电脑上,scrypt总是很快,不需要任何热身

docker stats
,5-10秒后:(现在edp_play_1,第2行,使用300+%CPU)

docker stats
半分钟后:(现在edp_play_1只使用0.97%的CPU,并且保持这种状态长达半小时,直到完成)

如果你想在Scala&sbt中进行测试,这就是我在GCE中的表现:

scala> import com.lambdaworks.crypto.SCryptUtil
import com.lambdaworks.crypto.SCryptUtil

scala> def time[R](block: => R): R = { val t0 = System.nanoTime() ; val result = block ; val t1 = System.nanoTime() ; println("Elapsed time: " + (t1 - t0) + "ns") ; result ; }
time: [R](block: => R)R

scala> time { SCryptUtil.scrypt("dummy password 1", 2, 1, 1) }
Elapsed time: 313823012366ns   <-- 5 minutes
res0: String = $s0$10101$2g6nrD0f5gDOTuP44f0mKg==$kqEe4TWSFXwtwGy3YgmIcqAhDvjMS89acST7cwPf/n4=

scala> time { SCryptUtil.scrypt("dummy password 1", 2, 1, 1) }
Elapsed time: 178461ns
res1: String = $s0$10101$C0iGNvfP+ywAxDS0ARoqVw==$k60w5Jpdt28PHGKT0ypByPocCyJISrq+T1XwmPlHR5w=

scala> time { SCryptUtil.scrypt("dummy password 1", 65536, 8, 1) }
Elapsed time: 130900544ns   <-- 0.1 seconds
res2: String = $s0$100801$UMTfIuBRY6lO1asECmVNYg==$b8i7GABgeczVHKVssJ8c2M7Z011u0TMBtVF4VSRohKI=

scala> 313823012366L / 1e9
res3: Double = 313.823012366

scala> 130900544L / 1e9
res4: Double = 0.130900544
scala>import com.lambdaworks.crypto.SCryptUtil
导入com.lambdaworks.crypto.SCryptUtil
scala>def time[R](block:=>R):R={val t0=System.nanoTime();val result=block;val t1=System.nanoTime();println(“经过的时间:+(t1-t0)+“ns”);result;}
时间:[R](块:=>R)R
scala>time{SCryptUtil.scrypt(“伪密码1,2,1,1)}
运行时间:313823012366ns时间{SCryptUtil.scrypt(“伪密码1,2,1,1)}
运行时间:178461ns
res1:String=$s0$10101$C0iGNvfP+ywAxDS0ARoqVw==$k60w5Jpdt28PHGKT0ypByPocCyJISrq+T1XwmPlHR5w=
scala>time{SCryptUtil.scrypt(“伪密码1”,65536,8,1)}
运行时间:130900544ns 313823012366L/1e9
res3:Double=313.823012366
scala>130900544L/1e9
res4:Double=0.130900544

注意:这与Docker无关。我刚刚在Docker外部进行了测试,openjdk 8直接安装在GCE实例上,结果是一样的:
scrypt(…)
第一次大约需要3分钟,但CPU是90-100%空闲的。此后,对scrypt的请求将立即完成。

问题在于,为随机数生成器种子需要很长时间。Scrypt做到了这一点:

public static String scrypt(String passwd, int N, int r, int p) {
    try {
        byte[] salt = new byte[16];
        SecureRandom.getInstance("SHA1PRNG").nextBytes(salt);   <--- look

        byte[] derived = SCrypt.scrypt(passwd.getBytes("UTF-8"), salt, N, r, p, 32);
它从/dev/random读取随机字符,我已经让它运行了几分钟,但几分钟后,到目前为止只输出了3个字符。所以它非常慢

改为使用更少但更快的/dev/uradom,然后:

# < /dev/urandom stdbuf -o0 strings --bytes 1 | stdbuf -o0 tr -d '\n\t '
public static String scrypt(String passwd, int N, int r, int p) {
    try {
        byte[] salt = new byte[16];
        SecureRandom.getInstance("SHA1PRNG").nextBytes(salt);   <--- look

        byte[] derived = SCrypt.scrypt(passwd.getBytes("UTF-8"), salt, N, r, p, 32);
# < /dev/random stdbuf -o0 strings --bytes 1 | stdbuf -o0 tr -d '\n\t '
# < /dev/urandom stdbuf -o0 strings --bytes 1 | stdbuf -o0 tr -d '\n\t '
apt install rng-tools  # start using any hardware rand num gen, on Ubuntu