【问题标题】:boolean[] vs. BitSet: Which is more efficient?boolean[] vs. BitSet:哪个更有效?
【发布时间】:2010-10-10 22:51:00
【问题描述】:

在内存和 CPU 使用率方面哪个更有效——booleans 的数组还是 BitSet?不使用具体的BitSet方法,只使用get/set/clear(==、=、Arrays.fill分别对应一个数组)。

【问题讨论】:

    标签: java arrays performance memory bitsets


    【解决方案1】:
    • Boolean[] 每个布尔值使用大约 4-20 个字节。
    • boolean[] 每个布尔值使用大约 1 个字节。
    • BitSet 每个布尔值使用大约 1 位。

    内存大小对您来说可能不是问题,在这种情况下 boolean[] 可能更易于编码。

    【讨论】:

    • 请注意,BitSet 中每个布尔值 1 位是渐近值。在幕后使用 long[],因此它被分成 64 位块。
    • 最好提一下,通常每个值只需要 4 字节指针。因为它被缓存了。除非您明确使用 new Boolean();但当然它不仅仅是 boolean[]
    【解决方案2】:

    来自一些使用 Sun JDK 1.6 用筛子计算素数的基准(最好的 10 次迭代来预热,给 JIT 编译器一个机会,并排除随机调度延迟,Core 2 Duo T5600 1.83GHz):

    BitSet 比 boolean[] 更节省内存,除了非常小的尺寸。数组中的每个布尔值都占用一个字节。来自 runtime.freeMemory() 的数字对于 BitSet 来说有点混乱,但更少。

    boolean[] 的 CPU 效率更高,但非常大的尺寸除外,它们几乎是均匀的。例如,大小为 100 万的 boolean[] 大约快四倍(例如 6ms 对 27ms),对于 10 和 1 亿,它们大约是偶数。

    【讨论】:

    • 我怀疑某些 BitSet 样式的操作(和、或、非)作为 BitSet 而不是数组更快。值得注意的是哪些操作更好。这个标题会误导大家不要再使用 BitSet
    • 本次测试不使用集合操作,偏写。
    • 这是一个误导性的答案,没有测试代码和特定的上下文。我鼓励任何阅读本文的人阅读此处的其他答案,并为自己考虑一下他们的具体情况。
    • 这些只是来自特定基准的事实,我看不出它们有什么误导性。当然,如果这对您很重要,请针对您的特定情况进行自己的基准测试。就我个人而言,我更喜欢BitSet,因为它表达了意图,除非我有很多运行时比特集相对较小并且需要针对运行时进行优化的情况。
    • @Utku 可能是缓存的影响,因此对于访问主内存,您还需要在写入字节时执行 read-update-write。请注意,100 万字节(boolean[] 更快的最大大小)大约是可以合理地放入缓存的大小。
    【解决方案3】:

    在这里,您可以看到比较 boolean[][] 三角矩阵与 BitSet[] 三角矩阵的内存/时间基准。

    我创建、设置和读取 (size * (size-1) / 2) 值并比较内存使用情况和时间...

    希望对您有所帮助...

    这里是代码...(只是一个快速肮脏的测试代码,抱歉;)

    import java.util.BitSet;
    import java.util.Date;
    
    public class BooleanBitSetProfiler {
    
        Runtime runtime;
        int sum = 0;
        public void doIt() {
    
            runtime = Runtime.getRuntime();
            long[][] bitsetMatrix = new long[30][2];
            long[][] booleanMatrix = new long[30][2];
            int size = 1000;
            for (int i = 0; i < booleanMatrix.length; i++) {
                booleanMatrix[i] = testBooleanMatrix(size);
                bitsetMatrix[i] = testBitSet(size);
                size += 2000;
            }
            int debug = 1;
            for (int j = 0; j < booleanMatrix.length; j++){
                System.out.print(booleanMatrix[j][0] + ";");
            }
            System.out.println();
            for (int j = 0; j < booleanMatrix.length; j++){
                System.out.print(booleanMatrix[j][1] + ";");
            }
            System.out.println();
            for (int j = 0; j < bitsetMatrix.length; j++){
                System.out.print(bitsetMatrix[j][0] + ";");
            }
            System.out.println();
            for (int j = 0; j < bitsetMatrix.length; j++){
                System.out.print(bitsetMatrix[j][1] + ";");
            }
            System.out.println();
        }
    
        private long memory () {
            return runtime.totalMemory() - runtime.freeMemory();
        }
        private long[] testBooleanMatrix(int size) {
            runtime.gc();
            long startTime = new Date().getTime();
            long startMemory = memory();
            boolean[][] matrix = new boolean[size][];
            for (int i = 0; i < size; i++) {
                matrix[i] = new boolean[size - i - 1];
            }
            long creationMemory = memory();
            long creationTime = new Date().getTime();
            for (int i = 0; i < size; i++)  {
                for (int j = 0; j < matrix[i].length; j++) {
                    matrix[i][j] = i % 2 == 0;
                }
            }
            long setMemory = memory();
            long setTime = new Date().getTime();
            for (int i = 0; i < size; i++)  {
                for (int j = 0; j < matrix[i].length; j++) {
                    if (matrix[i][j]) sum++;
                }
            }
            long readTime = new Date().getTime();
            System.out.println("Boolean[][] (size " + size + ")");
            System.out.println("Creation memory " + printMem(creationMemory-startMemory) + ", set memory " + printMem(setMemory-startMemory));
            System.out.println("Creation time " + printTime(creationTime-startTime) + ", set time " + printTime(setTime - creationTime) + " read time " + printTime(readTime - setTime) + "\n");
            runtime.gc();
            return new long[]{(setMemory-startMemory)/(1024*1024), (readTime-startTime)};
        }
        private long[] testBitSet(int size) {
            runtime.gc();
            long startTime = new Date().getTime();
            long startMemory = memory();
            BitSet[] matrix = new BitSet[size];
            for (int i = 0; i < size; i++) {
                matrix[i] = new BitSet(size - i - 1);
            }
            long creationMemory = memory();
            long creationTime = new Date().getTime();
            for (int i = 0; i < size; i++)  {
                for (int j = 0; j < matrix[i].size(); j++) {
                    matrix[i].set(j, (i % 2 == 0));
                }
            }
            long setMemory = memory();
            long setTime = new Date().getTime();
            for (int i = 0; i < size; i++)  {
                for (int j = 0; j < matrix[i].size(); j++) {
                    if (matrix[i].get(j)) sum++;
                }
            }
            long readTime = new Date().getTime();
            System.out.println("BitSet[] (size " + size + ")");
            System.out.println("Creation memory " + printMem(creationMemory-startMemory) + ", set memory " + printMem(setMemory-startMemory));
            System.out.println("Creation time " + printTime(creationTime-startTime) + ", set time " + printTime(setTime - creationTime) + " read time " + printTime(readTime - setTime) + "\n");
            runtime.gc();
            return new long[]{(setMemory-startMemory)/(1024*1024), (readTime-startTime)};
        }
    
        private String printMem(long mem) {
            mem = mem / (1024*1024);
            return mem + "MB";
        }
        private String printTime(long milis) {
            int seconds = (int) (milis / 1000);
            milis = milis % 1000;
            return seconds > 0 ? seconds + "s " + milis + "ms" : milis + "ms";
        }
    }
    

    【讨论】:

      【解决方案4】:

      您的问题有点左字段,但如果存储是一个问题,您可能需要查看Huffman compression。例如,00000001 可以按频率压缩到与{(7)0, (1)1} 等效的值。更“随机”的字符串 00111010 需要更复杂的表示,例如{(2)0, (3)1, (1)0, (1)1, (1)0},占用更多空间。根据您的位数据的结构,您可能会从它的使用中获得一些存储优势,超出BitSet

      【讨论】:

        【解决方案5】:

        至于内存,BitSet 的文档具有非常明确的含义。特别是:

        每个位集都有一个当前大小,即空间的位数 当前由位设置使用。请注意,大小与 位集的实现,因此它可能会随着实现而改变。这 位集的长度与位集的逻辑长度有关,并且是 独立于实现定义。

        Java 库类的源代码是公开的,可以很容易地check this for themselves。特别是:

        The internal field corresponding to the serialField "bits".
        89 
        90     private long[] words;
        

        至于速度;这取决于一个人在做什么。一般来说,不要提前考虑速度;使用在语义上最有意义的任何工具,并导致最清晰的代码。仅在观察到未满足性能要求并确定瓶颈后进行优化。

        出于多种原因,来到 SO 并询问 A 是否比 B 快是愚蠢的,包括但肯定不限于:

        1. 这取决于应用程序,通常没有响应的人可以访问。在使用它的上下文中对其进行分析和分析。确保它是一个真正值得优化的瓶颈。
        2. 此类询问速度的问题通常表明 OP 认为他们关心效率,但不愿意分析并且没有定义性能要求。在表面之下,这通常是一个危险信号,表明 OP 走错了路。

        我知道这是一个老问题,但最近才出现;我相信这是值得添加的。

        【讨论】:

          【解决方案6】:

          这取决于往常。 是的,BitSet 的内存效率更高,但是一旦您需要多线程访问 boolean[] 可能是更好的选择。例如,对于计算素数,您只需将布尔值设置为 true,因此您实际上并不需要同步。 Hans Boehm 已经写了一些关于此的论文,并且可以使用相同的技术来标记图中的节点。

          【讨论】:

          • 如果你的布尔数组没有增长,那肯定更适合并发使用。
          • 您仍然需要同步以确保所有线程都能看到其他线程已写入的内容。 Here 是一个很好的介绍。我很想阅读 Hans Boehm 的论文 - 太糟糕了,链接已失效。
          • 我想我找到了 Hans Boehm 的论文:hpl.hp.com/techreports/2004/HPL-2004-209.pdf 结果:您不需要同步。你只是希望线程看到其他人做了什么。如果他们不这样做也没问题,他们只会做重复的工作。但在实践中,变化通常是可见的,算法会线性加速。
          【解决方案7】:

          从 Java 到 CPU 完全是特定于 VM 的。例如,过去布尔值实际上是作为 32 位值实现的(今天很可能是这样)。

          除非您知道这很重要,否则最好编写清晰的代码,对其进行分析,然后修复运行缓慢或占用大量内存的部分。

          您可以随时执行此操作。例如,我曾经决定不对字符串调用 .intern() ,因为当我在分析器中运行代码时,它的运行速度太慢了(尽管使用的内存更少)。

          【讨论】:

            【解决方案8】:

            我相信 BitSet 的内存和 CPU 效率更高,它可以在内部将位打包成 int、longs 或本机数据类型,而 boolean[] 需要每个数据位一个字节。此外,如果您要使用其他方法(and、or 等),您会发现 BitSet 更有效,因为不需要遍历数组的每个元素;而是使用按位数学。

            【讨论】:

            • 内存效率 - 可能是真的。 CPU 效率 - 肯定不是。在 x86 上执行两次按位操作(移位/和或移位/或)和最多两次内存访问(尽管很可能是缓存的)几乎总是效率较低。
            • @EFraim:通过减少使用的内存量,您增加了将所有内容都放入缓存的机会。缓存未命中非常昂贵。看到这个因素使 BitArray 更快,我一点也不感到惊讶。
            • 例如:如果整个 bitset 都适合缓存,而不是 boolean[],则 bitset 的性能将优于 boolean[],并且需要随机访问。
            猜你喜欢
            • 1970-01-01
            • 2019-06-29
            • 1970-01-01
            • 2021-04-13
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多