我试图通过将int原语更改为短裤来优化Android游戏的RAM使用率。在进行此操作之前,我对Java中原始类型的性能感兴趣。
因此,我使用卡尺库创建了这个小的测试基准。
public class BenchmarkTypes extends Benchmark {
@Param("10") private long testLong;
@Param("10") private int testInt;
@Param("10") private short testShort;
@Param("5000") private long resultLong = 5000;
@Param("5000") private int resultInt = 5000;
@Param("5000") private short resultShort = 5000;
@Override
protected void setUp() throws Exception {
Random rand = new Random();
testShort = (short) rand.nextInt(1000);
testInt = (int) testShort;
testLong = (long) testShort;
}
public long timeLong(int reps){
for(int i = 0; i < reps; i++){
resultLong += testLong;
resultLong -= testLong;
}
return resultLong;
}
public int timeInt(int reps){
for(int i = 0; i < reps; i++){
resultInt += testInt;
resultInt -= testInt;
}
return resultInt;
}
public short timeShort(int reps){
for(int i = 0; i < reps; i++){
resultShort += testShort;
resultShort -= testShort;
}
return resultShort;
}
}
测试结果令我惊讶。
测试情况
基准在Caliper库下运行。
检测结果
https://microbenchmarks.appspot.com/runs/0c9bd212-feeb-4f8f-896c-e027b85dfe3b
内部2.365 ns
长2.436 ns
短8.156 ns
测试结论?
短基元类型比长基元和int基元类型慢得多(3-4倍)?
题
为什么short原语比int或long长得多?我希望int原语类型在32位VM上最快,而long和short在时间上相等,或者short甚至更快。
Android手机也是如此吗?知道Android手机通常在32位环境中运行,如今,越来越多的手机开始配备64位处理器。
您尚未预热JIT。您没有完成足够的迭代。这不是您如何微平台化Java。
(最有可能是)由Java将短时间(每次)转换为int(或long)进行算术运算引起的
@GermannArlington-否。时间上相差1000倍的真正原因是基准未正确编写。请参阅链接的问答。
是的,这里没有真正的基准!但是慢1000倍?创建一个良好的基准真的会带来巨大的改变吗?
Rolf-阅读您链接到的答案的第一条评论。写下答案的人可能犯了与您相同的错误。
罗尔夫-当然可以。尝试按照建议重新编写基准,然后看看会得到什么。
为什么将此标记为重复?这肯定是另一个问题!
由于您的基准测试存在缺陷,因此您的"结果"无效,并且试图解释它也没有太多意义……好像它是真实的结果。 (这不仅是无效的。将1000倍的速度减慢根本是不可能的。)虽然此问题并非严格地与另一个问题重复,但其他问题的答案是对此问题的最佳回答。
如果您使用有效的基准和有效的结果更新了问题,则可能值得重新打开...
没有证据表明这个问题及其结果是无效的!你有证据吗?为何有人会在真正的生产应用程序永远不会运行的"特殊微平台"测试环境中进行测试?在这种环境下,速度可能会降低1000倍,而我只想知道为什么在这种环境下会这么慢。
遗传证明(ish)-stackoverflow.com/a/14532605/139985。问题不在于环境。这是因为您的基准测试与实际应用程序不一样……而且典型的JVM设计为有效运行。最有可能发生的是,基准测试的"慢速"部分因JIT编译而失真。您实际上正在测量JIT编译和优化代码所花费的时间!
使用Java caliper库用新的测试结果更新了问题。
请注意,您的操作是无操作,并且足够聪明的JIT可以将其全部销毁。这很容易解决(Id建议单独使用+=或与^=结合使用)。 @StephenC当前关闭的原因不再正确,因此,Id建议重新打开(尽管它可能会因为您链接的问题的重复而关闭)。
@RolfSmit最好证明您已尝试确保基准测试有意义,而不是要求提供证明。解释行为有时可能需要几个小时,我们不希望浪费时间。假设应用程序运行了足够长的时间(这就是Java的本意),那么基准测试工具的作用应该接近实际发生的情况。确实,在Android上可能有所不同。
我同意,我正在尝试优化RAM使用率,使用短裤会改善这种情况。但是我不希望性能下降。我该怎么办?使用短裤的对象被分配!
重新开放-这个问题现在有了一个比较合理的基准,其中包含一组更合理的数字。不过,我很确定,这个问题完全可以通过@StephenC链接的那个问题得到解答。使用short减少内存占用的可能性不大。
对于小于int的基本类型,Java字节码不支持基本操作(+,-,*,/,>>,>>>,<
使用javap签出生成的字节码,以查看short测试和int测试之间的区别。
VM / JIT优化显然偏向于int / long操作,这很有意义,因为它们是最常见的。
小于int的类型有其用途,但主要用于节省数组中的内存。它们不像简单的类成员那样适合(当然,当数据的适当类型时,您仍然会使用它们)。较小的成员甚至不能减小对象的大小。 (再次)主要为当前VM量身定制执行速度,因此VM甚至可以将字段与本机字边界对齐,以提高访问性能,但会浪费内存。
由于java / android处理小于int的基元的整数算术的方式,因此这是可能的。
如果在Java中添加了两个小于数据类型的原语,则它们会被自动提升为整数数据类型。通常需要强制转换以将结果转换回必要的数据类型。
技巧包括速记操作,例如+=,-=等,在隐式发生强制转换的地方,这样操作的最终结果是:
resultShort += testShort;
实际上类似于以下内容:
resultShort = (short)((int) resultShort + (int) testShort);
如果我们看一下方法的反汇编字节码:
public static int test(int a, int b){
a += b;
return a;
}
我们看:
public static int test(int, int);
Code:
0: iload_0
1: iload_1
2: iadd
3: istore_0
4: iload_0
5: ireturn
将其与替换为数据类型的相同方法进行简短比较,我们得到:
public static short test(short, short);
Code:
0: iload_0
1: iload_1
2: iadd
3: i2s
4: istore_0
5: iload_0
6: ireturn
注意附加指令i2s(整数,简称为short)。这可能是造成性能下降的原因。您可能会注意到的另一件事是,所有指令都是基于整数的,由前缀i表示(例如,iadd表示整数加法)。这意味着在iload阶段的某个地方,短裤被提升为整数,这也可能导致性能下降。
如果您能理解我的话,那么长算术的字节码与整数1相同,除了指令是长特定的(例如ladd而不是iadd)。
这个答案是正确的。 但是,重要的是要记住,JVM不会直接执行字节码(一旦它已被JIT编译)。 因此,字节码不能直接解释差异。 例如,如果本机指令集直接支持16算术,并且JIT编译器足够聪明,可以使用它,那么您可能希望short算术比long算术快。
但现实情况是,针对PC,服务器甚至智能电话设备的指令集已针对32位/ 64位操作而非16位操作进行了调整。 因此,执行16位算术通常需要更多的本机指令和更多的时钟周期,这使其比32位和64位要慢,如OP基准测试所示。 但是,这高度依赖于目标平台的硬件……并可能依赖于JIT编译器。