In java, is it more efficient to use byte or short instead of int and float instead of double?
我注意到我总是使用int和double,不管数字大小。因此,在Java中,使用EDCOX1、0或EDCOX1、1、EDCOX1、2、EDCX1、3,而不是EDCOX1,4?
所以假设我有一个包含大量整数和双数的程序。如果我知道这个数字合适的话,我是否值得把我的整数改成字节或是短路呢?
我知道Java没有无符号类型,但是如果我知道数字只有正数,我还能做什么?
我指的是有效的处理。我假设如果所有变量都是半大小,并且计算速度也可能更快,垃圾收集器会更快。(我想既然我在安卓系统上工作,我也需要担心RAM)
(我假设垃圾收集器只处理对象,而不处理原语,但仍然删除废弃对象中的所有原语,对吗?)
我试过用一个小的Android应用程序,但没有发现有什么不同。(尽管我没有"科学地"测量任何东西。)
假设它应该更快、更高效,我错了吗?我不想在一个庞大的计划中经历和改变一切,以发现我浪费了我的时间。
当我开始一个新项目时,从一开始就值得做吗?(我的意思是,我认为每一点都会有所帮助,但如果是这样,为什么没有人会这么做。)
Am I wrong in assuming it should be faster and more efficient? I'd hate to go through and change everything in a massive program to find out I wasted my time.
简短回答
是的,你错了。在大多数情况下,它在使用空间方面几乎没有什么区别。
不值得尝试优化…除非你有明确的证据表明需要优化。如果您确实需要优化对象字段的内存使用,那么您可能需要采取其他(更有效)措施。
更长的答案Java虚拟机使用实际上是32位原始单元大小的倍数的偏移量来建模堆栈和对象字段。因此,当您将局部变量或对象字段声明为(例如)
有两个例外:
long 和double 值需要2个原始32位单元- 基元类型的数组以压缩形式表示,因此(例如)一个字节数组每32位字可容纳4个字节。
因此,优化
理论上,JIT可能能够优化这一点,但实际上,我从未听说过这样的JIT。一个障碍是,在创建了正在编译的类的实例之前,JIT通常无法运行。如果JIT优化了内存布局,您可以拥有两个(或更多)同一类的"风格"对象…这将带来巨大的困难。
复述从@meriton答案中的基准结果来看,使用
我认为原因是JIT可能在每种情况下都使用32位乘法指令进行乘法运算。但在
不管怎样,这确实指出了另一个问题,即切换到
这取决于JVM的实现,以及底层硬件。大多数现代硬件不会从内存(甚至从一级缓存)中提取单个字节,即使用较小的基元类型通常不会减少内存带宽消耗。同样,现代CPU的字大小为64位。它们可以在较少的位上执行操作,但这是通过丢弃额外的位来实现的,这些位也不快。
唯一的好处是,较小的基元类型可以导致更紧凑的内存布局,尤其是在使用数组时。这样可以节省内存,从而改善引用的位置(从而减少缓存未命中的数量)并减少垃圾收集开销。
但是,一般来说,使用较小的原始类型并不快。
为了证明这一点,请看以下基准:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 | package tools.bench; import java.math.BigDecimal; public abstract class Benchmark { final String name; public Benchmark(String name) { this.name = name; } abstract int run(int iterations) throws Throwable; private BigDecimal time() { try { int nextI = 1; int i; long duration; do { i = nextI; long start = System.nanoTime(); run(i); duration = System.nanoTime() - start; nextI = (i << 1) | 1; } while (duration < 100000000 && nextI > 0); return new BigDecimal((duration) * 1000 / i).movePointLeft(3); } catch (Throwable e) { throw new RuntimeException(e); } } @Override public String toString() { return name +"\t" + time() +" ns"; } public static void main(String[] args) throws Exception { Benchmark[] benchmarks = { new Benchmark("int multiplication") { @Override int run(int iterations) throws Throwable { int x = 1; for (int i = 0; i < iterations; i++) { x *= 3; } return x; } }, new Benchmark("short multiplication") { @Override int run(int iterations) throws Throwable { short x = 0; for (int i = 0; i < iterations; i++) { x *= 3; } return x; } }, new Benchmark("byte multiplication") { @Override int run(int iterations) throws Throwable { byte x = 0; for (int i = 0; i < iterations; i++) { x *= 3; } return x; } }, new Benchmark("int[] traversal") { @Override int run(int iterations) throws Throwable { int[] x = new int[iterations]; for (int i = 0; i < iterations; i++) { x[i] = i; } return x[x[0]]; } }, new Benchmark("short[] traversal") { @Override int run(int iterations) throws Throwable { short[] x = new short[iterations]; for (int i = 0; i < iterations; i++) { x[i] = (short) i; } return x[x[0]]; } }, new Benchmark("byte[] traversal") { @Override int run(int iterations) throws Throwable { byte[] x = new byte[iterations]; for (int i = 0; i < iterations; i++) { x[i] = (byte) i; } return x[x[0]]; } }, }; for (Benchmark bm : benchmarks) { System.out.println(bm); } } } |
打印在我的旧笔记本上:
1 2 3 4 5 6 | int multiplication 1.530 ns short multiplication 2.105 ns byte multiplication 2.483 ns int[] traversal 5.347 ns short[] traversal 4.760 ns byte[] traversal 2.064 ns |
如您所见,性能差异非常小。优化算法比选择基元类型重要得多。
如果您大量使用字节而不是int,那么使用字节而不是int可以提高性能。下面是一个实验:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | import java.lang.management.*; public class SpeedTest { /** Get CPU time in nanoseconds. */ public static long getCpuTime() { ThreadMXBean bean = ManagementFactory.getThreadMXBean(); return bean.isCurrentThreadCpuTimeSupported() ? bean .getCurrentThreadCpuTime() : 0L; } public static void main(String[] args) { long durationTotal = 0; int numberOfTests=0; for (int j = 1; j < 51; j++) { long beforeTask = getCpuTime(); // MEASURES THIS AREA------------------------------------------ long x = 20000000;// 20 millions for (long i = 0; i < x; i++) { TestClass s = new TestClass(); } // MEASURES THIS AREA------------------------------------------ long duration = getCpuTime() - beforeTask; System.out.println("TEST" + j +": duration =" + duration +"ns =" + (int) duration / 1000000); durationTotal += duration; numberOfTests++; } double average = durationTotal/numberOfTests; System.out.println("-----------------------------------"); System.out.println("Average Duration =" + average +" ns =" + (int)average / 1000000 +" ms (Approximately)"); } |
}
这个类测试创建新测试类的速度。每项测试都要进行2000万次,共有50次测试。
下面是测试类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | public class TestClass { int a1= 5; int a2= 5; int a3= 5; int a4= 5; int a5= 5; int a6= 5; int a7= 5; int a8= 5; int a9= 5; int a10= 5; int a11= 5; int a12=5; int a13= 5; int a14= 5; } |
我已经运行了速度测试课程,最后得到了:
1 | Average Duration = 8.9625E8 ns = 896 ms (Approximately) |
现在,我将把ints改成testclass中的字节,然后再次运行它。结果如下:
1 | Average Duration = 6.94375E8 ns = 694 ms (Approximately) |
我相信这项实验表明,如果您要实例化大量的变量,那么使用byte而不是int可以提高效率。
字节通常被认为是8位。short通常被认为是16位。
在一个"纯"的环境中,它不是Java,因为字节和长度的所有实现,以及短裤,以及其他有趣的东西通常都是隐藏的,字节会更好地利用空间。
但是,您的计算机可能不是8位的,也可能不是16位的。这意味着特别是要获得16或8位,它需要借助"欺骗",浪费时间,以假装它有能力在需要时访问这些类型。
此时,这取决于硬件是如何实现的。不过,从我的处境来看,最好的速度是通过以块的形式存储东西来实现的,这对于您的CPU来说是很舒服的。64位处理器喜欢处理64位元素,而任何低于64位的元素都需要"工程魔法"来假装它喜欢处理它们。
short/byte/char性能下降的原因之一是缺少对这些数据类型的直接支持。通过直接支持,它意味着JVM规范没有提到这些数据类型的任何指令集。存储、加载、添加等指令具有int数据类型的版本。但它们没有short/byte/char版本。例如,考虑下面的Java代码:
1 2 3 4 5 6 | void spin() { int i; for (i = 0; i < 100; i++) { ; // Loop body is empty } } |
同样被转换成机器代码如下。
1 2 3 4 5 6 7 8 | 0 iconst_0 // Push int constant 0 1 istore_1 // Store into local variable 1 (i=0) 2 goto 8 // First time through don't increment 5 iinc 1 1 // Increment local variable 1 by 1 (i++) 8 iload_1 // Push local variable 1 (i) 9 bipush 100 // Push int constant 100 11 if_icmplt 5 // Compare and loop if less than (i < 100) 14 return // Return void when done |
现在,考虑将int改为short,如下所示。
1 2 3 4 5 6 | void sspin() { short i; for (i = 0; i < 100; i++) { ; // Loop body is empty } } |
相应的机器代码将更改如下:
1 2 3 4 5 6 7 8 9 10 11 12 | 0 iconst_0 1 istore_1 2 goto 10 5 iload_1 // The short is treated as though an int 6 iconst_1 7 iadd 8 i2s // Truncate int to short 9 istore_1 10 iload_1 11 bipush 100 13 if_icmplt 5 16 return |
正如您所观察到的,要操作短数据类型,它仍然使用int数据类型指令版本,并在需要时显式地将int转换为short。现在,由于这个原因,性能降低了。
现在,不给予直接支持的理由如下:
The Java Virtual Machine provides the most direct support for data of
type int. This is partly in anticipation of efficient implementations
of the Java Virtual Machine's operand stacks and local variable
arrays. It is also motivated by the frequency of int data in typical
programs. Other integral types have less direct support. There are no
byte, char, or short versions of the store, load, or add instructions,
for instance.
引自此处的JVM规范(第58页)。
这种差异几乎不明显!这更多的是一个设计、恰当性、一致性、习惯性等问题。有时候这只是一个品味问题。当你所关心的只是你的程序启动和运行,用一个