在java中,使用byte或short代替int和float而不是double更有效吗?

时间:2013-01-25 20:22:12

标签: java performance int double primitive-types

我注意到无论数量需要多大或多小,我总是使用int和double。那么在java中,使用byteshort代替intfloat代替double是否更有效?

所以假设我有一个有大量整数和双打的程序。如果我知道这个数字适合的话,是否值得通过并将我的整数更改为字节或短片?

我知道java没有无符号类型,但如果我知道这个数字只是正数,那还有什么我可以做的吗?

通过高效我主要是指处理。如果所有变量都是一半大小并且计算可能会稍快一点,我认为垃圾收集器会快得多。 (我想因为我正在开发android,所以我也需要担心ram)

(我假设垃圾收集器只处理对象而不是原始但仍然删除被放弃对象中的所有基元吗?)

我尝试使用我的小型Android应用程序,但根本没有发现任何差异。 (虽然我没有'科学地"衡量任何东西。)

假设它应该更快更有效,我错了吗?我讨厌通过并改变大规模计划中的所有内容,以发现我浪费了我的时间。

当我开始一个新项目时,从一开始就值得做吗? (我的意思是我认为每一点都会有所帮助,但如果是这样的话,为什么看起来没有人会这样做。)

6 个答案:

答案 0 :(得分:94)

  

假设它应该更快更有效,我错了吗?我不想在一个大型计划中通过并改变一切,以发现我浪费了我的时间。

简短回答

是的,你错了。在大多数情况下,就使用的空间而言,它差别很小

不值得尝试对此进行优化...除非您有明确证据表明需要进行优化。如果你需要来优化对象字段的内存使用,你可能需要采取其他(更有效的)措施。

更长的答案

Java虚拟机使用(实际上)32位原始单元大小的倍数的偏移来建模堆栈和对象字段。因此,当您将局部变量或对象字段声明为(例如)byte时,变量/字段将存储在32位单元格中,就像int一样。

这有两个例外:

  • longdouble值需要2个原始32位单元格
  • 基本类型数组以压缩形式表示,因此(例如)一个字节数组每32位字保存4个字节。

因此可能值得优化longdouble ...以及大型基元数组的使用。但总的来说没有。

从理论上讲,JIT 可以能够优化它,但实际上我从来没有听说过JIT。一个障碍是JIT通常直到创建了正在编译的类的实例之后才能运行。如果JIT优化了内存布局,你可能会有两个(或更多)“同类”对象的“风味”......这会带来很大的困难。


再访

在@ meriton的答案中查看基准测试结果时,似乎使用shortbyte代替int会导致乘法的性能下降。实际上,如果你单独考虑这些操作,那么惩罚就很重要。 (你不应该......但那是另一回事。)

我认为解释是JIT可能在每种情况下使用32位乘法指令进行乘法运算。但在byteshort情况下,它会执行额外指令,以便在每个循环中将中间32位值转换为byteshort迭代。 (理论上,转换可以在循环结束时完成一次......但我怀疑优化器是否能够解决这个问题。)

无论如何,这确实指向切换到shortbyte作为优化的另一个问题。它可能会在算法和计算密集的算法中使性能更糟 ....

答案 1 :(得分:28)

这取决于JVM的实现以及底层硬件。大多数现代硬件不会从存储器(或甚至从第一级高速缓存)获取单个字节,即使用较小的基元类型通常不会减少存储器带宽消耗。同样,现代CPU的字大小为64位。它们可以在较少的位上执行操作,但这可以通过丢弃额外的位来工作,这也不会更快。

唯一的好处是较小的基元类型可以导致更紧凑的内存布局,最明显的是在使用数组时。这节省了内存,这可以改善引用的局部性(从而减少缓存未命中数)并减少垃圾收集开销。

一般来说,使用较小的基元类型并不快。

为证明这一点,请看以下基准:

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);
        }
    }
}

在我的旧笔记本上打印:

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

如您所见,性能差异非常小。优化算法远比原始类型的选择重要。

答案 2 :(得分:4)

如果您大量使用它们,则使用byte而不是int可以提高性能。这是一个实验:

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)");


}

}

此类测试创建新TestClass的速度。每项测试都进行了2000万次,有50次测试。

这是TestClass:

 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; 

 }

我已经运行了SpeedTest课程,最后得到了这个:

 Average Duration = 8.9625E8 ns = 896 ms (Approximately)

现在我将inC更改为TestClass中的字节并再次运行它。结果如下:

 Average Duration = 6.94375E8 ns = 694 ms (Approximately)

我相信这个实验表明,如果你实例化了大量的变量,使用byte而不是int可以提高效率

答案 3 :(得分:2)

字节通常被认为是8位。 short通常被认为是16位。

在一个“纯粹”的环境中,这不是java所有字节和longs的实现,而短路和其他有趣的东西一般都是隐藏的,字节更好地利用了空间。

但是,您的计算机可能不是8位,可能不是16位。这意味着 特别是要获得16或8位,它需要求助于“技巧”,这会浪费时间,以便假装它有能力在需要时访问这些类型。

此时,它取决于硬件的实现方式。但是,从我去过, 最好的速度是通过以块状存储的方式实现的,这些块对于CPU来说非常舒适。一个64位处理器喜欢处理64位元素,而任何低于此值的处理器通常都需要“工程魔术”来假装它喜欢处理它们。

答案 4 :(得分:0)

差异很难察觉!这更像是一个设计,适当性,一致性,习惯等问题......有时它只是一个品味问题。当你所关心的只是你的程序启动并运行而用float代替int不会损害正确性时,我认为除非你能用任何一个证明这一点,否则没有任何好处。类型改变表现。基于2或3字节不同类型的调优性能实际上是您应该关注的最后一件事;唐纳德克努特曾经说过:“过早优化是所有邪恶的根源”(不确定是他,如果你有答案就编辑)。

答案 5 :(得分:0)

short / byte / char性能较差的原因之一是缺少对这些数据类型的直接支持。通过直接支持,这意味着JVM规范没有提及这些数据类型的任何指令集。诸如存储,加载,添加等指令具有用于int数据类型的版本。但是它们没有short / byte / char的版本。例如。考虑下面的Java代码:

void spin() {
 int i;
 for (i = 0; i < 100; i++) {
 ; // Loop body is empty
 }
}

将相同的代码转换为机器代码,如下所示。

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。

void sspin() {
 short i;
 for (i = 0; i < 100; i++) {
 ; // Loop body is empty
 }
}

相应的机器代码将更改如下:

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

如您所见,要操作short数据类型,它仍然使用int数据类型指令版本,并在需要时将int显式转换为short。现在,由于这个原因,性能会降低。

现在,列举出不给予直接支持的原因如下:

  

Java虚拟机为数据的最直接支持   输入int。部分原因是期望有效的实施   虚拟机的操作数堆栈和局部变量   数组。它也受典型的int数据频率的影响   程式。其他整数类型的直接支持较少。没有   字节,字符或存储,加载或添加指令的简短版本,   例如。

引自当前的JVM规范here(第58页)。