【问题标题】:Is the performance/memory benefit of short nullified by downcasting?向下转换是否会抵消 short 的性能/内存优势?
【发布时间】:2013-02-13 15:20:12
【问题描述】:

我正在编写一个大型应用程序,我试图在其中尽可能多地节省内存并提高性能。因此,当我知道一个字段的值只能从 0 到 10 或从 -100 到 100 时,我会尝试使用 short 数据类型而不是 int

这对于其余代码意味着什么,但是当我调用这些函数时,我必须将简单的ints 向下转换为shorts。例如:

方法签名

public void coordinates(short x, short y) ...

方法调用

obj.coordinates((short) 1, (short) 2);

在我的整个代码中都是这样,因为文字被视为ints,并且不会根据函数参数自动向下转换或键入。

因此,一旦发生这种向下转换,任何性能或内存增益实际上是否显着?还是转换过程如此高效以至于我仍然可以获得一些收益?

【问题讨论】:

  • 如果您的值在固定范围内,您可以使用对象池来减少内存。
  • 虽然和你的问题不一样,但我建议阅读这里的讨论:stackoverflow.com/questions/2165246/… 和这里:stackoverflow.com/questions/2170872/…
  • @FrankvanPuffelen 谢谢,我会看看他们。
  • 我相信 Sun JVM 在内部将 byteshortint 存储为 32 位字。因此使用short 不会为您节省任何内存。
  • 关于“尽量节省内存并提高性能”,我建议您进行一些性能测试,以确保您确实正在节省内存并提高性能。我认为,如果事实证明你跳过所有这些铸造圈而几乎没有任何好处,那将是不幸的。

标签: java performance primitive downcast


【解决方案1】:

在 32 位平台上使用 short 与 int 并没有任何性能优势,除了 short[] 与 int[] 的情况外 - 即便如此,缺点通常超过优点。

假设您在 x64、x86 或 ARM-32 上运行:

  • 使用时,16 位 SHORT 存储在 32 位或 64 位长的整数寄存器中,与整数相同。 IE。使用 short 时,与 int 相比,您不会获得内存或性能优势。
  • 在堆栈上时,16 位 SHORT 存储在 32 位或 64 位“槽”中,以保持堆栈对齐(就像整数一样)。将 SHORT 与 INT 用于局部变量,您不会获得任何性能或内存优势。
  • 当作为参数传递时,SHORT 在被压入堆栈时会自动扩展为 32 位或 64 位(与刚刚压入的 int 不同)。与使用整数相比,您在此处的代码实际上性能略低,并且(代码)内存占用略大。
  • 在存储全局(静态)变量时,这些变量会自动扩展为占用 32 位或 64 位插槽,以保证指针(引用)的对齐。这意味着将 SHORT 与 INT 用于全局(静态)变量相比,您不会获得任何性能或内存优势。
  • 存储字段时,这些字段位于堆内存中的结构中,该结构映射到类的布局。在此类中,字段会自动填充为 32 位或 64 位,以保持堆上字段的对齐。将 SHORT 用于字段与 INT 相比,您不会获得性能或内存优势。

使用 SHORT 与 INT 相比,唯一的好处是在分配它们的数组的情况下。在这种情况下,一个 N 个短裤数组的长度大约是一个 N 个整数数组的一半。

除了在大量短裤中进行复杂但局部化的数学运算时将变量放在热循环中所带来的性能优势之外,您永远不会看到使用 SHORTS 与 INT 相比的优势。

ALL 其他情况下 - 例如用于字段、全局变量、参数和局部变量的 short,除了它可以存储的位数之外,没有 SHORT 和 INT 之间的区别。

我一如既往的建议是,在使您的代码更难阅读和人为限制之前,请尝试BENCHMARKING您的代码以查看内存和 CPU 瓶颈在哪里,然后解决这些问题.

我强烈怀疑,如果您曾经遇到过您的应用使用 int 而不是 short 的情况,那么您早就放弃 Java 以获得更少的内存/CPU 运行时,所以做所有前期的这项工作是白费力气。

【讨论】:

  • 很好的解释。它还表明,过早的优化实际上可能会导致性能下降。此外,所有这些 int 到 short casts 的东西都只会让代码的可读性变差。
  • 非常感谢。这是我想知道的所有关键点的一个很好的总结,将真正帮助我前进。我见过的最好的 SO 答案之一。 :) 几天后我可以打开这个赏金,我会看看我能不能给你一些比这个小小的支持和接受更多的代表。
  • +1 值得注意的是,虽然在这种情况下您可能不会节省任何内存,但 2 个字节的内存价值大约 0.0000005 美分,或者如果您是最低工资,大约 3 微秒您的时间。换句话说,如果您花费超过 10 微秒的时间来考虑节省 4 个字节,那么您实际上是在浪费您的时间。 ;)
  • 简单地说,如果您节省 1 KB,则值得您花费大约 1 ms 的时间,如果您节省 1 MB,则值得您花费大约 1 秒的时间并节省 1 GB 的内存值得你花费大约 20 分钟的时间,包括你必须做的任何设计、测试和调试,这真的是一个很小的改变。相反,如果您进行了一个小改动,但没有节省大约 1 GB,那么您可能会浪费您的时间。
  • @PeterLawrey 这在很大程度上取决于上下文,以及您讨论的是短期对象还是长期对象。在应用程序的整个生命周期中保留的 16 GB 用户笔记本电脑内存的价值比服务器上的 16 GB 垃圾收集要高出几个数量级。当然,什么时候应该关心这个原因通常是很明显的。
【解决方案2】:

据我所知,强制转换本身应该没有运行时成本(使用 short 代替 int 是否实际上可以提高性能是值得商榷的,并且取决于您的应用程序的具体情况)。

考虑以下几点:

public class Main {
    public static void f(short x, short y) {
    }

    public static void main(String args[]) {
        final short x = 1;
        final short y = 2;
        f(x, y);
        f((short)1, (short)2);
    }
}

main() 的最后两行编译为:

  // f(x, y)
   4: iconst_1      
   5: iconst_2      
   6: invokestatic  #21                 // Method f:(SS)V

  // f((short)1, (short)2);
   9: iconst_1      
  10: iconst_2      
  11: invokestatic  #21                 // Method f:(SS)V

如您所见,它们是相同的。转换发生在编译时。

【讨论】:

  • 谢谢!好的答案,我喜欢编译代码的比较。您是否使用特定资源来查看?
  • 为了反汇编字节码,我在编译的类上使用了javap -c
  • +1,与其他答案相比,这应该有更多的选票。
【解决方案3】:

int 文字到 short 的类型转换发生在编译时,对运行时性能没有影响。

【讨论】:

    【解决方案4】:

    您需要一种方法来检查类型选择对内存使用的影响。如果在给定情况下 short 与 int 将通过减少内存占用来提高性能,那么对内存的影响应该是可衡量的。

    这里有一个简单的方法来测量正在使用的内存量:

          private static long inUseMemory() {
            Runtime rt = Runtime.getRuntime();
            rt.gc();
            final long memory = rt.totalMemory() - rt.freeMemory();
            return memory;
          }
    

    我还提供了一个程序示例,该示例使用该方法在某些常见情况下检查内存使用情况。分配一百万个短数组的内存增加证实了短数组每个元素使用两个字节。各种对象数组的内存增加表明更改一个或两个字段的类型几乎没有区别。

    这是一次运行的输出。 YMMV。

    Before short[1000000] allocation: In use: 162608 Change 162608
    After short[1000000] allocation: In use: 2162808 Change 2000200
    After TwoShorts[1000000] allocation: In use: 34266200 Change 32103392
    After NoShorts[1000000] allocation: In use: 58162560 Change 23896360
    After TwoInts[1000000] allocation: In use: 90265920 Change 32103360
    Dummy to keep arrays live -378899459
    

    本文其余部分为程序来源:

        public class Test {
          private static int BIG = 1000000;
          private static long oldMemory = 0;
    
          public static void main(String[] args) {
            short[] megaShort;
            NoShorts[] megaNoShorts;
            TwoShorts[] megaTwoShorts;
            TwoInts[] megaTwoInts;
            System.out.println("Before short[" + BIG + "] allocation: "
                + memoryReport());
            megaShort = new short[BIG];
            System.out
                .println("After short[" + BIG + "] allocation: " + memoryReport());
            megaTwoShorts = new TwoShorts[BIG];
            for (int i = 0; i < BIG; i++) {
              megaTwoShorts[i] = new TwoShorts();
            }
            System.out.println("After TwoShorts[" + BIG + "] allocation: "
                + memoryReport());
            megaNoShorts = new NoShorts[BIG];
            for (int i = 0; i < BIG; i++) {
              megaNoShorts[i] = new NoShorts();
            }
            System.out.println("After NoShorts[" + BIG + "] allocation: "
                + memoryReport());
            megaTwoInts = new TwoInts[BIG];
            for (int i = 0; i < BIG; i++) {
              megaTwoInts[i] = new TwoInts();
            }
            System.out.println("After TwoInts[" + BIG + "] allocation: "
                + memoryReport());
    
            System.out.println("Dummy to keep arrays live "
                + (megaShort[0] + megaTwoShorts[0].hashCode() + megaNoShorts[0]
                    .hashCode() + megaTwoInts[0].hashCode()));
    
          }
    
          private static long inUseMemory() {
            Runtime rt = Runtime.getRuntime();
            rt.gc();
            final long memory = rt.totalMemory() - rt.freeMemory();
            return memory;
          }
    
          private static String memoryReport() {
            long newMemory = inUseMemory();
            String result = "In use: " + newMemory + " Change "
                + (newMemory - oldMemory);
            oldMemory = newMemory;
            return result;
          }
        }
    
        class NoShorts {
          //char a, b, c;
        }
    
        class TwoShorts {
          //char a, b, c;
          short s, t;
        }
    
        class TwoInts {
          //char a, b, c;
          int s, t;
        }
    

    【讨论】:

    • +1 如果您使用-XX:-UseTLAB,您可以获得更准确的内存使用情况。 (即使分配Object
    • @PeterLawrey 感谢 -UseTLAB 提示。
    【解决方案5】:

    首先我想确认内存节省,因为我看到了一些疑问。根据教程中short 的文档:http://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html

    short:short 数据类型是一个 16 位有符号二进制补码整数。它的最小值为 -32,768,最大值为 32,767(含)。与字节一样,适用相同的准则:您可以在大型数组中使用 short 来节省内存,在内存节省实际上很重要的情况下。

    通过使用short,您确实可以将内存保存在大型数组中(希望如此),因此使用它是个好主意。

    现在回答你的问题:

    short 的性能/内存优势是否因向下转换而无效?

    简短的回答是否定的。int 向下转换到short 本身发生在编译时,因此从性能角度来看没有向下影响,但是因为您正在节省内存,它可能从而在内存阈值场景中获得更好的性能

    【讨论】:

      猜你喜欢
      • 2011-12-18
      • 1970-01-01
      • 2011-04-02
      • 2015-10-18
      • 2019-08-03
      • 2012-12-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多