使用 Java Long 包装器与原始 long 相加数字
Posted
技术标签:
【中文标题】使用 Java Long 包装器与原始 long 相加数字【英文标题】:Adding numbers using Java Long wrapper versus primitive longs 【发布时间】:2012-05-27 05:11:31 【问题描述】:我正在运行此代码并得到意外结果。我希望添加原语的循环会执行得更快,但结果不一致。
import java.util.*;
public class Main
public static void main(String[] args)
StringBuilder output = new StringBuilder();
long start = System.currentTimeMillis();
long limit = 1000000000; //10^9
long value = 0;
for(long i = 0; i < limit; ++i)
long i;
output.append("Base time\n");
output.append(System.currentTimeMillis() - start + "ms\n");
start = System.currentTimeMillis();
for(long j = 0; j < limit; ++j)
value = value + j;
output.append("Using longs\n");
output.append(System.currentTimeMillis() - start + "ms\n");
start = System.currentTimeMillis();
value = 0;
for(long k = 0; k < limit; ++k)
value = value + (new Long(k));
output.append("Using Longs\n");
output.append(System.currentTimeMillis() - start + "ms\n");
System.out.print(output);
输出:
基准时间 359 毫秒 使用多头 1842毫秒 使用多头 614毫秒
我尝试在它自己的 java 程序中运行每个单独的测试,但结果是一样的。是什么原因造成的?
小细节:运行 java 1.6
编辑: 我请另外 2 个人试用这段代码,其中一个得到了与我得到的完全相同的奇怪结果。另一个得到真正有意义的结果!我请那个获得正常结果的人给我们他的班级二进制文件。我们运行它,我们仍然得到奇怪的结果。问题不在编译时(我认为)。我正在运行 1.6.0_31,获得正常结果的人在 1.6.0_16,像我一样获得奇怪结果的人在 1.7.0_04。
编辑:在程序开始时使用 Thread.sleep(5000) 获得相同的结果。在整个程序中使用 while 循环也可以获得相同的结果(以查看在 java 完全启动后时间是否会收敛到正常时间)
【问题讨论】:
这可能是由于缺乏热身,操作被优化而没有使用值等等。自动装箱有很大的损失,如果你使用 Long 作为计数器你肯定会看到它的循环。 另一个需要考虑的是其余的代码。可能是 StringBuilder 在第一种情况下达到了调整大小限制。因为您不采用开始和停止时间戳,而只是将开始和停止时间戳作为输出构建的一部分,您将不知道。 改进这个测试 Fredrik 的好方法,但不确定它是否会导致 1 秒的差异! 确保你做适当的热身,修复代码如果你看到同样的事情我会很惊讶。你有没有检查过如果你按照相反的顺序做会发生什么? 我尝试以相反的顺序进行操作,结果相同。如何确保我得到适当的热身? 【参考方案1】:我怀疑这是 JVM 预热效应。具体来说,代码会在某个时间点进行 JIT 编译,这会扭曲您所看到的时间。
将所有内容置于一个循环中,并忽略报告的时间,直到它们稳定下来。 (但请注意,它们不会完全稳定。垃圾正在生成,因此 GC 需要偶尔启动。这可能会扭曲时间,至少有点。处理这个问题的最好方法是运行外循环的大量迭代,并计算/显示平均次数。)
另一个问题是,某些 Java 版本上的 JIT 编译器可能能够优化掉您尝试测试的内容:
可以发现Long
对象的创建和立即拆箱可以被优化掉。 (谢谢路易斯!)
它可以找出循环正在做“忙碌的工作”......并完全优化它们。 (每次循环结束后,value
的值不会被使用。)
FWIW,一般建议您使用Long.valueOf(long)
而不是new Long(long)
,因为前者可以使用缓存的Long
实例。但是,在这种情况下,我们可以预测除了前几次循环迭代之外的所有循环中都会出现缓存未命中,因此建议没有帮助。如果有的话,它可能会使有问题的循环变慢。
更新
我自己做了一些调查,结果如下:
import java.util.*;
public class Main
public static void main(String[] args)
while (true)
test();
private static void test()
long start = System.currentTimeMillis();
long limit = 10000000; //10^9
long value = 0;
for(long i = 0; i < limit; ++i)
long t1 = System.currentTimeMillis() - start;
start = System.currentTimeMillis();
for(long j = 0; j < limit; ++j)
value = value + j;
long t2 = System.currentTimeMillis() - start;
start = System.currentTimeMillis();
for(long k = 0; k < limit; ++k)
value = value + (new Long(k));
long t3 = System.currentTimeMillis() - start;
System.out.print(t1 + " " + t2 + " " + t3 + " " + value + "\n");
这给了我以下输出。
28 58 2220 99999990000000
40 58 2182 99999990000000
36 49 157 99999990000000
34 51 157 99999990000000
37 49 158 99999990000000
33 52 158 99999990000000
33 50 159 99999990000000
33 54 159 99999990000000
35 52 159 99999990000000
33 52 159 99999990000000
31 50 157 99999990000000
34 51 156 99999990000000
33 50 159 99999990000000
请注意,前两列相当稳定,但第三列在第 3 次迭代中显示出显着的加速...可能表明发生了 JIT 编译。
有趣的是,在我将测试分离到一个单独的方法之前,我没有看到第三次迭代的加速。这些数字看起来都像前两行。这似乎是说 JVM(我正在使用)不会 JIT 编译当前正在执行的方法......或类似的东西。
无论如何,这表明(对我而言)应该有一个热身效果。如果您没有看到预热效果,那么您的基准测试正在执行一些抑制 JIT 编译的操作......因此对于实际应用程序没有意义。
【讨论】:
我不禁想知道 JIT 是否会变得足够聪明,例如省略一些自动装箱。 (当然,如果我正在编写 JIT,那我肯定会想到......) 我同意。这当然是一种考虑的可能性......在解释他在修复热身缺陷后得到的结果时。但现在,这些数字根本不可靠。 我尝试通过在程序开始处放置 5 秒 Thread.sleep 来解决预热问题。我得到相同的结果。这是解决可能的热身问题的正确方法吗? @RodrigoSalazar - 请阅读我回答的第二段。它告诉你如何处理热身效果等。 哦,我才明白你的意思。会试试的!【参考方案2】:我也很惊讶。
我的第一个猜测是无意的“autoboxing”,但这显然不是您的示例代码中的问题。
此链接可能会提供线索:
http://docs.oracle.com/javase/1.5.0/docs/api/java/lang/Long.html
值
public static Long valueOf(long l)
返回一个 Long 实例,表示指定的 long 值。如果不需要新的 Long 实例,这个方法一般应该是 优先于构造函数 Long(long) 使用,因为此方法是 可能会产生显着更好的空间和时间性能 缓存频繁请求的值。
Parameters: l - a long value. Returns: a Long instance representing l. Since: 1.5
但是,是的,我希望使用包装器(例如“Long”)会花费更多时间和更多空间。我会不期望使用包装器是三倍更快!
================================================ ===================================
附录:
我用你的代码得到了这些结果:
Base time 6878ms
Using longs 10515ms
Using Longs 428022ms
我在一个 32 位单核 CPU 上运行 JDK 1.6.0_16。
【讨论】:
是的,你似乎得到了预期的结果。我找到了一个也能得到预期结果的人。让他们向我发送他们的二进制文件并运行它,我仍然得到与以前相同的结果。 IE。我确信某些环境会产生一些奇怪的效果,可能只是在 java 启动期间,我不确定。 很容易验证:1) 在同一个程序中运行几次测试。另外:2)直到最后才打印结果(分离出可能的 I/O 副作用)。让我们知道您的发现!【参考方案3】:好的 - 这是一个稍微不同的版本,以及我的结果(运行 JDK 1.6.0_16 pokey 32 位单代码 CPU):
import java.util.*;
/*
Test Base longs Longs/new Longs/valueOf
---- ---- ----- --------- -------------
0 343 896 3431 6025
1 342 957 3401 5796
2 342 881 3379 5742
*/
public class LongTest
private static int limit = 100000000;
private static int ntimes = 3;
private static final long[] base = new long[ntimes];
private static final long[] primitives = new long[ntimes];
private static final long[] wrappers1 = new long[ntimes];
private static final long[] wrappers2 = new long[ntimes];
private static void test_base (int idx)
long start = System.currentTimeMillis();
for (int i = 0; i < limit; ++i)
base[idx] = System.currentTimeMillis() - start;
private static void test_primitive (int idx)
long value = 0;
long start = System.currentTimeMillis();
for (int i = 0; i < limit; ++i)
value = value + i;
primitives[idx] = System.currentTimeMillis() - start;
private static void test_wrappers1 (int idx)
long value = 0;
long start = System.currentTimeMillis();
for (int i = 0; i < limit; ++i)
value = value + new Long(i);
wrappers1[idx] = System.currentTimeMillis() - start;
private static void test_wrappers2 (int idx)
long value = 0;
long start = System.currentTimeMillis();
for (int i = 0; i < limit; ++i)
value = value + Long.valueOf(i);
wrappers2[idx] = System.currentTimeMillis() - start;
public static void main(String[] args)
for (int i=0; i < ntimes; i++)
test_base (i);
test_primitive(i);
test_wrappers1 (i);
test_wrappers2 (i);
System.out.println ("Test Base longs Longs/new Longs/valueOf");
System.out.println ("---- ---- ----- --------- -------------");
for (int i=0; i < ntimes; i++)
System.out.printf (" %2d %6d %6d %6d %6d\n",
i, base[i], primitives[i], wrappers1[i], wrappers2[i]);
================================================ =========================
2012 年 5 月 28 日:
以下是一些额外的时序,来自运行 Windows 7/64 并运行相同 JDK 修订版 1.6.0_16 的更快(但仍然适中)双核 CPU:
/*
PC 1: limit = 100,000,000, ntimes = 3, JDK 1.6.0_16 (32-bit):
Test Base longs Longs/new Longs/valueOf
---- ---- ----- --------- -------------
0 343 896 3431 6025
1 342 957 3401 5796
2 342 881 3379 5742
PC 2: limit = 1,000,000,000, ntimes = 5,JDK 1.6.0_16 (64-bit):
Test Base longs Longs/new Longs/valueOf
---- ---- ----- --------- -------------
0 3 2 5627 5573
1 0 0 5494 5537
2 0 0 5475 5530
3 0 0 5477 5505
4 0 0 5487 5508
PC 2: "for loop" counters => long; limit = 10,000,000,000, ntimes = 5:
Test Base longs Longs/new Longs/valueOf
---- ---- ----- --------- -------------
0 6278 6302 53713 54064
1 6273 6286 53547 53999
2 6273 6294 53606 53986
3 6274 6325 53593 53938
4 6274 6279 53566 53974
*/
你会注意到:
我没有使用 StringBuilder,我将所有 I/O 分离出来,直到程序结束。
“long”原语始终等同于“no-op”
“长”包装器始终慢得多
“new Long()”比“Long.valueOf()”稍快
将循环计数器从“int”更改为“long”会使前两列(“base”和“longs”慢得多。
“JIT 预热”在前几次迭代后可以忽略不计...
... 提供 I/O(如 System.out)和潜在的内存密集型活动(如 StringBuilder)被移动到实际测试之外部分。
【讨论】:
我的输出与 core i7, java 1.6.0_34 Test Base longs Longs/new Longs/valueOf 2 0 0 1260 1275 @Rodrigo Salazar - 请列出所有行,而不仅仅是第一行。请增加“限制”,使您的 PC 上的所有时间都 > 0。以上是关于使用 Java Long 包装器与原始 long 相加数字的主要内容,如果未能解决你的问题,请参考以下文章