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

新手上路,请多包涵

我注意到我总是使用 int 和 double,无论数字需要多小或多大。 So in java, is it more efficient to use byte or short instead of int and float instead of double

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

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

我所说的高效主要是指处理。我假设如果所有变量都是一半大小,垃圾收集器会快很多,而且计算也可能会快一些。 (我想因为我在 android 上工作,所以我也需要有点担心 ram)

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

我用我拥有的一个小型 Android 应用程序进行了尝试,但并没有真正注意到有什么不同。 (虽然我没有“科学地”测量任何东西。)

我假设它应该更快、更高效是错误的吗?我不想通过并更改大型程序中的所有内容,然后发现我浪费了时间。

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

原文由 DisibioAaron 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 646
2 个回答

我假设它应该更快、更高效是错误的吗?我不想通过并更改大型程序中的所有内容,然后发现我浪费了时间。

简答

是的,你错了。在大多数情况下,它在所用空间方面 _几乎没有区别_。

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

更长的答案

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

有两个例外:

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

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

理论上,JIT 可能 能够对此进行优化,但在实践中,我从未听说过 JIT 可以做到这一点。一个障碍是 JIT 通常只有在创建了正在编译的类的实例之后才能运行。如果 JIT 优化了内存布局,您可能会拥有同一类对象的两种(或更多)“风味”……这将带来巨大的困难。


重访

查看@meriton 的答案中的基准测试结果,似乎使用 shortbyte 而不是 int 惩罚。事实上,如果您孤立地考虑这些操作,那么损失是巨大的。 (你不应该孤立地考虑它们……但那是另一个话题。)

我认为解释是 JIT 可能在每种情况下都使用 32 位乘法指令进行乘法运算。 But in the byte and short case, it executes extra instructions to convert the intermediate 32 bit value to a byte or short in each循环迭代。 (理论上,这种转换可以在循环结束时完成一次……但我怀疑优化器是否能够解决这个问题。)

无论如何,这确实指向另一个问题,即切换到 shortbyte 作为优化。它可能会使性能 变差……在算术和计算密集型算法中。


次要问题

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

不,无论如何不是在性能方面。 (There are some methods in Integer , Long , etc for dealing with int , long , etc as unsigned. But these don’ t 提供任何性能优势。那不是他们的目的。)

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

正确的。对象的字段是对象的 _一部分_。当对象被垃圾回收时,它就会消失。同样,当数组被收集时,数组的单元格也会消失。当字段或单元格类型是原始类型时,该值存储在字段/单元格中……它是对象/数组的一部分……并且已被删除。

原文由 Stephen C 发布,翻译遵循 CC BY-SA 4.0 许可协议

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

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

然而,一般来说,使用较小的原始类型并不会更快。

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

 public class Benchmark {

    public static void benchmark(String label, Code code) {
        print(25, label);

        try {
            for (int iterations = 1; ; iterations *= 2) { // detect reasonable iteration count and warm up the code under test
                System.gc(); // clean up previous runs, so we don't benchmark their cleanup
                long previouslyUsedMemory = usedMemory();
                long start = System.nanoTime();
                code.execute(iterations);
                long duration = System.nanoTime() - start;
                long memoryUsed = usedMemory() - previouslyUsedMemory;

                if (iterations > 1E8 || duration > 1E9) {
                    print(25, new BigDecimal(duration * 1000 / iterations).movePointLeft(3) + " ns / iteration");
                    print(30, new BigDecimal(memoryUsed * 1000 / iterations).movePointLeft(3) + " bytes / iteration\n");
                    return;
                }
            }
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    private static void print(int desiredLength, String message) {
        System.out.print(" ".repeat(Math.max(1, desiredLength - message.length())) + message);
    }

    private static long usedMemory() {
        return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
    }

    @FunctionalInterface
    interface Code {
        /**
         * Executes the code under test.
         *
         * @param iterations
         *            number of iterations to perform
         * @return any value that requires the entire code to be executed (to
         *         prevent dead code elimination by the just in time compiler)
         * @throws Throwable
         *             if the test could not complete successfully
         */
        Object execute(int iterations);
    }

    public static void main(String[] args) {
        benchmark("long[] traversal", (iterations) -> {
            long[] array = new long[iterations];
            for (int i = 0; i < iterations; i++) {
                array[i] = i;
            }
            return array;
        });
        benchmark("int[] traversal", (iterations) -> {
            int[] array = new int[iterations];
            for (int i = 0; i < iterations; i++) {
                array[i] = i;
            }
            return array;
        });
        benchmark("short[] traversal", (iterations) -> {
            short[] array = new short[iterations];
            for (int i = 0; i < iterations; i++) {
                array[i] = (short) i;
            }
            return array;
        });
        benchmark("byte[] traversal", (iterations) -> {
            byte[] array = new byte[iterations];
            for (int i = 0; i < iterations; i++) {
                array[i] = (byte) i;
            }
            return array;
        });

        benchmark("long fields", (iterations) -> {
            class C {
                long a = 1;
                long b = 2;
            }

            C[] array = new C[iterations];
            for (int i = 0; i < iterations; i++) {
                array[i] = new C();
            }
            return array;
        });
        benchmark("int fields", (iterations) -> {
            class C {
                int a = 1;
                int b = 2;
            }

            C[] array = new C[iterations];
            for (int i = 0; i < iterations; i++) {
                array[i] = new C();
            }
            return array;
        });
        benchmark("short fields", (iterations) -> {
            class C {
                short a = 1;
                short b = 2;
            }

            C[] array = new C[iterations];
            for (int i = 0; i < iterations; i++) {
                array[i] = new C();
            }
            return array;
        });
        benchmark("byte fields", (iterations) -> {
            class C {
                byte a = 1;
                byte b = 2;
            }

            C[] array = new C[iterations];
            for (int i = 0; i < iterations; i++) {
                array[i] = new C();
            }
            return array;
        });

        benchmark("long multiplication", (iterations) -> {
            long result = 1;
            for (int i = 0; i < iterations; i++) {
                result *= 3;
            }
            return result;
        });
        benchmark("int multiplication", (iterations) -> {
            int result = 1;
            for (int i = 0; i < iterations; i++) {
                result *= 3;
            }
            return result;
        });
        benchmark("short multiplication", (iterations) -> {
            short result = 1;
            for (int i = 0; i < iterations; i++) {
                result *= 3;
            }
            return result;
        });
        benchmark("byte multiplication", (iterations) -> {
            byte result = 1;
            for (int i = 0; i < iterations; i++) {
                result *= 3;
            }
            return result;
        });
    }
}

在我的 Intel Core i7 CPU @ 3.5 GHz 上运行 OpenJDK 14,打印:

      long[] traversal     3.206 ns / iteration      8.007 bytes / iteration
      int[] traversal     1.557 ns / iteration      4.007 bytes / iteration
    short[] traversal     0.881 ns / iteration      2.007 bytes / iteration
     byte[] traversal     0.584 ns / iteration      1.007 bytes / iteration
          long fields    25.485 ns / iteration     36.359 bytes / iteration
           int fields    23.126 ns / iteration     28.304 bytes / iteration
         short fields    21.717 ns / iteration     20.296 bytes / iteration
          byte fields    21.767 ns / iteration     20.273 bytes / iteration
  long multiplication     0.538 ns / iteration      0.000 bytes / iteration
   int multiplication     0.526 ns / iteration      0.000 bytes / iteration
 short multiplication     0.786 ns / iteration      0.000 bytes / iteration
  byte multiplication     0.784 ns / iteration      0.000 bytes / iteration

如您所见,唯一显着的速度节省发生在遍历大型数组时;使用较小的对象字段产生的好处可以忽略不计,而且在小数据类型上的计算实际上会稍微慢一些。

总体而言,性能差异非常小。优化算法远比选择原始类型重要。

原文由 meriton 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题