boolean []与BitSet:哪个更有效?


68

在内存和CPU使用率方面,更有效的方法是booleans数组还是BitSet?不使用特定的BitSet方法,仅使用get / set / clear(分别为数组的==,=,Arrays.fill)。

Answers:


41

从一些带有Sun JDK 1.6计算质数的基准测试中筛选(最好进行10次迭代以进行预热,给JIT编译器一个机会,并排除随机调度延迟,Core 2 Duo T5600 1.83GHz):

除了非常小的大小,BitSet比boolean []更有效的存储。数组中的每个布尔值都占用一个字节。对于BitSet,runtime.freeMemory()中的数字有些混乱,但更少。

boolean []的CPU效率更高,除了非常大的大小(大约相等)之外。例如,对于大小为100万的boolean [],大约要快四倍(例如6ms对27ms),而对于一亿,则大约是偶数。


7
我怀疑某些BitSet样式操作(和/或不)比BitSet而不是数组要快。值得指出的是哪个操作更好。标题将误导所有人,使他们不再使用BitSet
basszero

1
该测试不使用set操作,并且偏向于编写。
starblue

10
这是一个没有测试代码和特定上下文的误导性答案。我鼓励任何阅读此书的人通读这里的其他答案,并自己思考一下自己的具体情况。
詹森·C

1
这些只是来自特定基准的事实,我看不出它们有什么误导。当然,如果这对您很重要,请针对您的特定情况制定自己的基准。就我个人而言,我更喜欢BitSet它,因为它表达了意图,除非我有许多运行时具有相对较小的位集并且需要针对运行时进行优化。
starblue 2014年

2
@Utku可能是缓存的一种效果,因此,要访问主内存,您还需要在写入字节时执行read-update-write。请注意,100万个字节(最大的boolean[]速度更快)大约是可以放入高速缓存的大小。
starblue '16

49
  • Boolean[] 每个布尔值使用大约4-20个字节。
  • boolean[] 每个布尔值使用大约1个字节。
  • BitSet 每个布尔值使用大约1位。

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


37
请注意,BitSet中每个布尔值1位是渐近值。在后台使用long [],因此将其细化为64位块。
mR_fr0g 2012年

3
值得一提的是,通常每个值只需要4个字节的指针。因为它已缓存。除非您明确使用new Boolean(); 但是,当然比boolean []更重要
keiki

16

在这里,您可以看到将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";
    }
}

5

您的问题有些遗漏,但是如果您担心存储问题,则可以考虑使用Huffman压缩技术。例如,00000001可以按频率压缩到等于{(7)0, (1)1}。更为“随机化”的字符串00111010将需要更复杂的表示形式,例如{(2)0, (3)1, (1)0, (1)1, (1)0},并占用更多空间。根据您的位数据的结构,您可能会从中获得一些存储利益,其好处是BitSet


3

至于内存,a的文档BitSet具有很明显的含义。特别是:

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

Java库类的源代码是公开可用的,并且可以轻松地自行检查这一点。特别是:

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

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

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

  1. 这取决于应用程序,通常没有人响应可以访问该应用程序。在使用它的上下文中对其进行分析和配置。请确保它是实际上值得优化的瓶颈。
  2. 诸如此类的询问速度的问题通常表明,OP认为他们关心效率,但不愿意描述并且没有定义性能要求。在表面之下,这通常是一个警告,表明OP朝错误的方向前进。

我知道这是一个老问题,但是最近才出现。我认为这值得补充。


3

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


如果您的布尔数组不增长,那么并发使用肯定会更好。
Randolpho

2
您仍然需要同步,以确保所有线程都可以看到其他线程已写的内容。是一个很好的介绍。我很想读汉斯·勃姆(Hans Boehm)的论文,可惜链接已死。
jcsahnwaldt恢复莫妮卡2015年

4
我想我找到了Hans Boehm的论文:hpl.hp.com/techreports/2004/HPL-2004-209.pdf结果:您不需要同步。您只希望线程能够看到其他线程所做的事情。如果没有,他们没问题,他们只会做重复的工作。但实际上,这些更改通常是可见的,并且算法将线性加速。
jcsahnwaldt恢复莫妮卡2015年

1

从Java到CPU完全是VM专有的。例如,过去曾经是布尔值实际上是作为32位值实现的(到目前为止很可能是正确的)。

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

您可以随时进行操作。例如,我曾经决定不对Strings调用.intern(),因为当我在事件探查器中运行代码时,它会使速度降低太多(尽管使用了较少的内存)。


-1

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


1
高效的内存-可能是正确的。CPU效率-肯定不是。与在x86上进行单个内存访问相比,执行两个按位操作(移位/和/或移位/或)和最多两个内存访问(尽管很可能已缓存)几乎总是效率较低。
EFraim

7
@EFraim:通过减少使用的内存量,您增加了将所有内容保存在缓存中的机会。高速缓存未命中非常昂贵。看到这个因素使BitArray更快,我一点也不惊讶。
乔恩·斯基特

1
例如:如果整个位组都适合缓存,则位组的性能将优于boolean [],但布尔型[]则不然,并且需要随机访问。
罗恩
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.