Git Product home page Git Product logo

roaringbitmap's Introduction

RoaringBitmap

docs-badge Java 11 CI

Bitsets, also called bitmaps, are commonly used as fast data structures. Unfortunately, they can use too much memory. To compensate, we often use compressed bitmaps.

Roaring bitmaps are compressed bitmaps which tend to outperform conventional compressed bitmaps such as WAH, EWAH or Concise. In some instances, roaring bitmaps can be hundreds of times faster and they often offer significantly better compression. They can even be faster than uncompressed bitmaps.

Roaring bitmaps are found to work well in many important applications:

Use Roaring for bitmap compression whenever possible. Do not use other bitmap compression methods (Wang et al., SIGMOD 2017)

kudos for making something that makes my software run 5x faster (Charles Parker from BigML)

This library is used by

The YouTube SQL Engine, Google Procella, uses Roaring bitmaps for indexing. Apache Lucene uses Roaring bitmaps, though they have their own independent implementation. Derivatives of Lucene such as Solr and Elastic also use Roaring bitmaps. Other platforms such as Whoosh, Microsoft Visual Studio Team Services (VSTS) and Pilosa also use Roaring bitmaps with their own implementations. You find Roaring bitmaps in InfluxDB, Bleve, Cloud Torrent, Redpanda, and so forth.

There is a serialized format specification for interoperability between implementations. We have interoperable C/C++, Java and Go implementations.

(c) 2013-... the RoaringBitmap authors

This code is licensed under Apache License, Version 2.0 (AL2.0).

When should you use a bitmap?

Sets are a fundamental abstraction in software. They can be implemented in various ways, as hash sets, as trees, and so forth. In databases and search engines, sets are often an integral part of indexes. For example, we may need to maintain a set of all documents or rows (represented by numerical identifier) that satisfy some property. Besides adding or removing elements from the set, we need fast functions to compute the intersection, the union, the difference between sets, and so on.

To implement a set of integers, a particularly appealing strategy is the bitmap (also called bitset or bit vector). Using n bits, we can represent any set made of the integers from the range [0,n): the ith bit is set to one if integer i is present in the set. Commodity processors use words of W=32 or W=64 bits. By combining many such words, we can support large values of n. Intersections, unions and differences can then be implemented as bitwise AND, OR and ANDNOT operations. More complicated set functions can also be implemented as bitwise operations.

When the bitset approach is applicable, it can be orders of magnitude faster than other possible implementation of a set (e.g., as a hash set) while using several times less memory.

However, a bitset, even a compressed one is not always applicable. For example, if you have 1000 random-looking integers, then a simple array might be the best representation. We refer to this case as the "sparse" scenario.

When should you use compressed bitmaps?

An uncompressed BitSet can use a lot of memory. For example, if you take a BitSet and set the bit at position 1,000,000 to true and you have just over 100kB. That is over 100kB to store the position of one bit. This is wasteful even if you do not care about memory: suppose that you need to compute the intersection between this BitSet and another one that has a bit at position 1,000,001 to true, then you need to go through all these zeroes, whether you like it or not. That can become very wasteful.

This being said, there are definitively cases where attempting to use compressed bitmaps is wasteful. For example, if you have a small universe size. E.g., your bitmaps represent sets of integers from [0,n) where n is small (e.g., n=64 or n=128). If you can use an uncompressed BitSet and it does not blow up your memory usage, then compressed bitmaps are probably not useful to you. In fact, if you do not need compression, then a BitSet offers remarkable speed.

The sparse scenario is another use case where compressed bitmaps should not be used. Keep in mind that random-looking data is usually not compressible. E.g., if you have a small set of 32-bit random integers, it is not mathematically possible to use far less than 32 bits per integer, and attempts at compression can be counterproductive.

How does Roaring compare with the alternatives?

Most alternatives to Roaring are part of a larger family of compressed bitmaps that are run-length-encoded bitmaps. They identify long runs of 1s or 0s and they represent them with a marker word. If you have a local mix of 1s and 0, you use an uncompressed word.

There are many formats in this family:

  • Oracle's BBC (Byte-aligned Bitmap Code) is an obsolete format at this point: though it may provide good compression, it is likely much slower than more recent alternatives due to excessive branching.
  • WAH (Word Aligned Hybrid) is a patented variation on BBC that provides better performance.
  • Concise is a variation on the patented WAH. In some specific instances, it can compress much better than WAH (up to 2x better), but it is generally slower.
  • EWAH (Enhanced Word Aligned Hybrid) is both free of patent, and it is faster than all the above. On the downside, it does not compress quite as well. It is faster because it allows some form of "skipping" over uncompressed words. So though none of these formats are great at random access, EWAH is better than the alternatives.

There is a big problem with these formats however that can hurt you badly in some cases: there is no random access. If you want to check whether a given value is present in the set, you have to start from the beginning and "uncompress" the whole thing. This means that if you want to intersect a big set with a large set, you still have to uncompress the whole big set in the worst case...

Roaring solves this problem. It works in the following manner. It divides the data into chunks of 216 integers (e.g., [0, 216), [216, 2 x 216), ...). Within a chunk, it can use an uncompressed bitmap, a simple list of integers, or a list of runs. Whatever format it uses, they all allow you to check for the presence of any one value quickly (e.g., with a binary search). The net result is that Roaring can compute many operations much faster than run-length-encoded formats like WAH, EWAH, Concise... Maybe surprisingly, Roaring also generally offers better compression ratios.

API docs

http://www.javadoc.io/doc/org.roaringbitmap/RoaringBitmap/

Scientific Documentation

  • Daniel Lemire, Owen Kaser, Nathan Kurz, Luca Deri, Chris O'Hara, François Saint-Jacques, Gregory Ssi-Yan-Kai, Roaring Bitmaps: Implementation of an Optimized Software Library, Software: Practice and Experience 48 (4), 2018 arXiv:1709.07821
  • Samy Chambi, Daniel Lemire, Owen Kaser, Robert Godin, Better bitmap performance with Roaring bitmaps, Software: Practice and Experience 46 (5), 2016. arXiv:1402.6407 This paper used data from http://lemire.me/data/realroaring2014.html
  • Daniel Lemire, Gregory Ssi-Yan-Kai, Owen Kaser, Consistently faster and smaller compressed bitmaps with Roaring, Software: Practice and Experience 46 (11), 2016. arXiv:1603.06549
  • Samy Chambi, Daniel Lemire, Robert Godin, Kamel Boukhalfa, Charles Allen, Fangjin Yang, Optimizing Druid with Roaring bitmaps, IDEAS 2016, 2016. http://r-libre.teluq.ca/950/

Code sample

import org.roaringbitmap.RoaringBitmap;

public class Basic {

  public static void main(String[] args) {
        RoaringBitmap rr = RoaringBitmap.bitmapOf(1,2,3,1000);
        RoaringBitmap rr2 = new RoaringBitmap();
        rr2.add(4000L,4255L);
        rr.select(3); // would return the third value or 1000
        rr.rank(2); // would return the rank of 2, which is index 1
        rr.contains(1000); // will return true
        rr.contains(7); // will return false

        RoaringBitmap rror = RoaringBitmap.or(rr, rr2);// new bitmap
        rr.or(rr2); //in-place computation
        boolean equals = rror.equals(rr);// true
        if(!equals) throw new RuntimeException("bug");
        // number of values stored?
        long cardinality = rr.getLongCardinality();
        System.out.println(cardinality);
        // a "forEach" is faster than this loop, but a loop is possible:
        for(int i : rr) {
          System.out.println(i);
        }
  }
}

Please see the examples folder for more examples, which you can run with ./gradlew :examples:runAll, or run a specific one with ./gradlew :examples:runExampleBitmap64, etc.

Unsigned integers

Java lacks native unsigned integers but integers are still considered to be unsigned within Roaring and ordered according to Integer.compareUnsigned. This means that Java will order the numbers like so 0, 1, ..., 2147483647, -2147483648, -2147483647,..., -1. To interpret correctly, you can use Integer.toUnsignedLong and Integer.toUnsignedString.

Working with memory-mapped bitmaps

If you want to have your bitmaps lie in memory-mapped files, you can use the org.roaringbitmap.buffer package instead. It contains two important classes, ImmutableRoaringBitmap and MutableRoaringBitmap. MutableRoaringBitmaps are derived from ImmutableRoaringBitmap, so that you can convert (cast) a MutableRoaringBitmap to an ImmutableRoaringBitmap in constant time.

An ImmutableRoaringBitmap that is not an instance of a MutableRoaringBitmap is backed by a ByteBuffer which comes with some performance overhead, but with the added flexibility that the data can reside anywhere (including outside of the Java heap).

At times you may need to work with bitmaps that reside on disk (instances of ImmutableRoaringBitmap) and bitmaps that reside in Java memory. If you know that the bitmaps will reside in Java memory, it is best to use MutableRoaringBitmap instances, not only can they be modified, but they will also be faster. Moreover, because MutableRoaringBitmap instances are also ImmutableRoaringBitmap instances, you can write much of your code expecting ImmutableRoaringBitmap.

If you write your code expecting ImmutableRoaringBitmap instances, without attempting to cast the instances, then your objects will be truly immutable. The MutableRoaringBitmap has a convenience method (toImmutableRoaringBitmap) which is a simple cast back to an ImmutableRoaringBitmap instance. From a language design point of view, instances of the ImmutableRoaringBitmap class are immutable only when used as per the interface of the ImmutableRoaringBitmap class. Given that the class is not final, it is possible to modify instances, through other interfaces. Thus we do not take the term "immutable" in a purist manner, but rather in a practical one.

One of our motivations for this design where MutableRoaringBitmap instances can be casted down to ImmutableRoaringBitmap instances is that bitmaps are often large, or used in a context where memory allocations are to be avoided, so we avoid forcing copies. Copies could be expected if one needs to mix and match ImmutableRoaringBitmap and MutableRoaringBitmap instances.

The following code sample illustrates how to create an ImmutableRoaringBitmap from a ByteBuffer. In such instances, the constructor only loads the meta-data in RAM while the actual data is accessed from the ByteBuffer on demand.

        import org.roaringbitmap.buffer.*;

        //...

        MutableRoaringBitmap rr1 = MutableRoaringBitmap.bitmapOf(1, 2, 3, 1000);
        MutableRoaringBitmap rr2 = MutableRoaringBitmap.bitmapOf( 2, 3, 1010);
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        DataOutputStream dos = new DataOutputStream(bos);
        // If there were runs of consecutive values, you could
        // call rr1.runOptimize(); or rr2.runOptimize(); to improve compression
        rr1.serialize(dos);
        rr2.serialize(dos);
        dos.close();
        ByteBuffer bb = ByteBuffer.wrap(bos.toByteArray());
        ImmutableRoaringBitmap rrback1 = new ImmutableRoaringBitmap(bb);
        bb.position(bb.position() + rrback1.serializedSizeInBytes());
        ImmutableRoaringBitmap rrback2 = new ImmutableRoaringBitmap(bb);

Alternatively, we can serialize directly to a ByteBuffer with the serialize(ByteBuffer) method.

Operations on an ImmutableRoaringBitmap such as and, or, xor, flip, will generate a RoaringBitmap which lies in RAM. As the name suggest, the ImmutableRoaringBitmap itself cannot be modified.

This design was inspired by Druid.

One can find a complete working example in the test file TestMemoryMapping.java.

Note that you should not mix the classes from the org.roaringbitmap package with the classes from the org.roaringbitmap.buffer package. They are incompatible. They serialize to the same output however. The performance of the code in org.roaringbitmap package is generally superior because there is no overhead due to the use of ByteBuffer instances.

Kryo

Many applications use Kryo for serialization/deserialization. One can use Roaring bitmaps with Kryo efficiently thanks to a custom serializer (Kryo 5):

public class RoaringSerializer extends Serializer<RoaringBitmap> {
    @Override
    public void write(Kryo kryo, Output output, RoaringBitmap bitmap) {
        try {
            bitmap.serialize(new KryoDataOutput(output));
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException();
        }
    }
    @Override
    public RoaringBitmap read(Kryo kryo, Input input, Class<? extends RoaringBitmap> type) {
        RoaringBitmap bitmap = new RoaringBitmap();
        try {
            bitmap.deserialize(new KryoDataInput(input));
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException();
        }
        return bitmap;
    }

}

64-bit integers (long)

Though Roaring Bitmaps were designed with the 32-bit case in mind, we have extensions to 64-bit integers. We offer two classes for this purpose: Roaring64NavigableMap and Roaring64Bitmap.

The Roaring64NavigableMap relies on a conventional red-black-tree. The keys are 32-bit integers representing the most significant 32~bits of elements whereas the values of the tree are 32-bit Roaring bitmaps. The 32-bit Roaring bitmaps represent the least significant bits of a set of elements.

The newer Roaring64Bitmap approach relies on the ART data structure to hold the key/value pair. The key is made of the most significant 48~bits of elements whereas the values are 16-bit Roaring containers. It is inspired by The Adaptive Radix Tree: ARTful Indexing for Main-Memory Databases by Leis et al. (ICDE '13).

    import org.roaringbitmap.longlong.*;

    
    // first Roaring64NavigableMap
    LongBitmapDataProvider r = Roaring64NavigableMap.bitmapOf(1,2,100,1000);
    r.addLong(1234);
    System.out.println(r.contains(1)); // true
    System.out.println(r.contains(3)); // false
    LongIterator i = r.getLongIterator();
    while(i.hasNext()) System.out.println(i.next());


    // second Roaring64Bitmap
    bitmap1 = new Roaring64Bitmap();
    bitmap2 = new Roaring64Bitmap();
    int k = 1 << 16;
    long i = Long.MAX_VALUE / 2;
    long base = i;
    for (; i < base + 10000; ++i) {
       bitmap1.add(i * k);
       bitmap2.add(i * k);
    }
    b1.and(bitmap2);

The serialization of 64-bit Roaring bitmaps is specified: see https://github.com/RoaringBitmap/RoaringFormatSpec#extention-for-64-bit-implementations

However, it is implemented only by Roaring64NavigableMap, by switching:

Roaring64NavigableMap.SERIALIZATION_MODE = Roaring64NavigableMap.SERIALIZATION_MODE_PORTABLE

Range Bitmaps

RangeBitmap is a succinct data structure supporting range queries. Each value added to the bitmap is associated with an incremental identifier, and queries produce a RoaringBitmap of the identifiers associated with values that satisfy the query. Every value added to the bitmap is stored separately, so that if a value is added twice, it will be stored twice, and if that value is less than some threshold, there will be at least two integers in the resultant RoaringBitmap.

It is more efficient - in terms of both time and space - to provide a maximum value. If you don't know the maximum value, provide a Long.MAX_VALUE. Unsigned order is used like elsewhere in the library.

var appender = RangeBitmap.appender(1_000_000);
appender.add(1L);
appender.add(1L);
appender.add(100_000L);
RangeBitmap bitmap = appender.build();
RoaringBitmap lessThan5 = bitmap.lt(5); // {0,1}
RoaringBitmap greaterThanOrEqualTo1 = bitmap.gte(1); // {0, 1, 2}
RoaringBitmap greaterThan1 = bitmap.gt(1); // {2}
RoaringBitmap equalTo1 = bitmap.eq(1); // {0, 1}
RoaringBitmap notEqualTo1 = bitmap.neq(1); // {2}

RangeBitmap is can be written to disk and memory mapped:

var appender = RangeBitmap.appender(1_000_000);
appender.add(1L);
appender.add(1L);
appender.add(100_000L);
ByteBuffer buffer = mapBuffer(appender.serializedSizeInBytes());
appender.serialize(buffer);
RangeBitmap bitmap = RangeBitmap.map(buffer);

The serialization format uses little endian byte order.

Prerequisites

  • Version 0.7.x requires JDK 8 or better
  • Version 0.6.x requires JDK 7 or better
  • Version 0.5.x requires JDK 6 or better

To build the project you need maven (version 3).

Download

You can download releases from github: https://github.com/RoaringBitmap/RoaringBitmap/releases

Maven repository

If your project depends on roaring, you can specify the dependency in the Maven "pom.xml" file:

        <dependencies>
          <dependency>
            <groupId>org.roaringbitmap</groupId>
            <artifactId>RoaringBitmap</artifactId>
            <version>0.9.9</version>
          </dependency>
        </dependencies>

where you should replace the version number by the version you require.

For up-to-date releases, we recommend configuring maven and gradle to depend on the Jitpack repository.

Usage

  • Get java

  • ./gradlew assemble will compile

  • ./gradlew build will compile and run the unit tests

  • ./gradlew test will run the tests

  • ./gradlew :roaringbitmap:test --tests TestIterators.testIndexIterator4 runs just the test TestIterators.testIndexIterator4; ./gradlew -i :roaringbitmap:test --tests TestRoaringBitmap.issue623 runs just the test issue623 in the class TestRoaringBitmap while printing out to the console.

  • ./gradlew bsi:test --tests BufferBSITest.testEQ run just the test BufferBSITest.testEQ in the bsi submodule

  • ./gradlew checkstyleMain will check that you abide by the code style and that the code compiles. We enforce a strict style so that there is no debate as to the proper way to format the code.

IntelliJ and Eclipse

If you plan to contribute to RoaringBitmap, you can have load it up in your favorite IDE.

  • For IntelliJ, in the IDE create a new project, possibly from existing sources, choose import, gradle.
  • For Eclipse: File, Import, Existing Gradle Projects, Select RoaringBitmap on my disk

Contributing

Contributions are invited. We enforce the Google Java style. Please run ./gradlew checkstyleMain on your code before submitting a patch.

FAQ

  • I am getting an error about a bad cookie. What is this about?

In the serialized files, part of the first 4 bytes are dedicated to a "cookie" which serves to indicate the file format.

If you try to deserialize or map a bitmap from data that has an unrecognized "cookie", the code will abort the process and report an error.

This problem will occur to all users who serialized Roaring bitmaps using versions prior to 0.4.x as they upgrade to version 0.4.x or better. These users need to refresh their serialized bitmaps.

  • How big can a Roaring bitmap get?

Given N integers in [0,x), then the serialized size in bytes of a Roaring bitmap should never exceed this bound:

8 + 9 * ((long)x+65535)/65536 + 2 * N

That is, given a fixed overhead for the universe size (x), Roaring bitmaps never use more than 2 bytes per integer. You can call RoaringBitmap.maximumSerializedSize for a more precise estimate.

  • What is the worst case scenario for Roaring bitmaps?

There is no such thing as a data structure that is always ideal. You should make sure that Roaring bitmaps fit your application profile. There are at least two cases where Roaring bitmaps can be easily replaced by superior alternatives compression-wise:

  1. You have few random values spanning in a large interval (i.e., you have a very sparse set). For example, take the set 0, 65536, 131072, 196608, 262144 ... If this is typical of your application, you might consider using a HashSet or a simple sorted array.

  2. You have dense set of random values that never form runs of continuous values. For example, consider the set 0,2,4,...,10000. If this is typical of your application, you might be better served with a conventional bitset (e.g., Java's BitSet class).

  • How do I select an element at random?

       Random random = new Random();
       bitmap.select(random.nextInt(bitmap.getCardinality()));
    

Benchmark

To run JMH benchmarks, use the following command:

     $ ./gradlew jmhJar

You can also run specific benchmarks...

     $ ./jmh/run.sh 'org.roaringbitmap.aggregation.and.identical.*'

Mailing list/discussion group

https://groups.google.com/forum/#!forum/roaring-bitmaps

Funding

This work was supported by NSERC grant number 26143.

roaringbitmap's People

Contributors

ashishkf avatar blacelle avatar danielthomas avatar gianm avatar gpunti avatar gskeno avatar gssiyankai avatar huangfeng1993 avatar incaseoftrouble avatar jervenbolleman avatar kishoreg avatar larsk-db avatar lemire avatar maciej avatar maithem avatar marshallpierce avatar navis avatar okrische avatar opeyrusse avatar owenkaser avatar ppiotrow avatar richardstartin avatar rorygraves avatar samytto avatar sethp-jive avatar simeonpilgrim avatar sivaraam avatar smmathews-cision-us avatar weijietong avatar xtonik avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

roaringbitmap's Issues

Implement copy-on-write

Currently, containers are eagerly cloned during computations. It might be worth the effort to delay the copy until a write occurs.

10-15x performance opportunity for ImmutableRoaringBitmap

Hi

I noticed that RoaringBitmap is several thousands times faster than ImmutableRoaringBitmap and got curious as to why this is.

I wrote a few JMH tests and ran them in mission control and so far I managed to increase the speed by 10-15x by doing the following.

  1. Skip the clone in MutableRoaringArray.appendCopy.

  2. buffer.asShortBuffer().slice() in ImmutableRoaringArray.getContainerAtIndex steals a lot of performance. Things speed up quite a lot by creating ShortBuffer once and providing it to getContainerAtIndex instead.

The slice also steal performance but that requires a bigger change I suppose. I cannot prove it but I suspect that a regular ByteBuffer might be faster. Or even better, using Martin Thompson's DirectBuffer.

Since i'm unfamiliar with the code i'm not sure if these changes breaks some internals? Seems workable from the tests I made.

Cheers,
-Kristoffer

Port all benchmarks to jmh

@bobymicroby Pointed out that JMH benchmarking should be the way to go (in PR https://github.com/lemire/RoaringBitmap/pull/19 )

@gssiyankai contributed an initial set of JMH benchmarks in PR https://github.com/lemire/RoaringBitmap/issues/23

I think we should go all JMH now and port the existing junit benchmarks (e.g. see https://github.com/lemire/RoaringBitmap/tree/master/src/test/java/org/roaringbitmap ) to JMH.

This would have a few benefits:

  • Hopefully improve the quality of the benchmarks,
  • Separate testing code from benchmarking code, thus making the builds faster.

Temporarily assigning to @gssiyankai feel free to reassign to me.

RoaringBitmap.or(RoaringBitmap) has side effect on argument

Hello,
In the JUnit test below, r1 is modified after been passed as an argument to or method (tested on 0.5.13):

import static org.junit.Assert.*;
import org.junit.Test;
import org.roaringbitmap.RoaringBitmap;
public class OrBugTest {
    @Test
    public void test() {
        RoaringBitmap r1 = new RoaringBitmap();
        // Strange thing: Replace this line by r1.add(131000) and the bug vanishes!
        r1.flip(131000, 131001);
        RoaringBitmap r2 = new RoaringBitmap();
        r2.add(220000);
        RoaringBitmap r3 = new RoaringBitmap();
        int killingPosition = 66000;
        r3.add(killingPosition);

        assertFalse(r1.contains(killingPosition)); //ok
        r2.or(r1);
        assertFalse(r1.contains(killingPosition)); //ok
        r2.or(r3);
        assertFalse(r1.contains(killingPosition)); //ko
    }
}

Best regards,
Jean-Marc Astesana

Make Add and Remove return boolean values

Hi,

When dealing with Roaring bitmaps in an application, I have found that it could be helpful if one is able to know, after inserting/deleting an integer to/from a Roaring bitmap using the add/remove method, if that integer was existing before in the bitmap or no. Same kind of thing done by Java collections add and remove methods.

If one has to maintain the cardinality of integers stored in a set of Roaring bitmaps, he needs to know if an inserted/removed integer was exsiting before in the bitmap, in order to increment/decrement the global cardinality. Such a job could be realized by calling the contains method before inserting or removing an integer. However, this will make two accesses to containers. However, if the add/remove method is able to return a true or false indicating if the inserted/removed integer was or not in the bitmap, one would be able to avoid a call to contains in such situations and preserve a supplementary access to containers.

Greetings!

Range removal fails, when runlength encoding was applied/removed

Hello,

my test fails, when i remove via a range, after added and removed runlength encoding

    @Test
    public void testRangeRemoval() {
        final RoaringBitmap bitmap = new RoaringBitmap();
        bitmap.add(1);
        bitmap.runOptimize();
        bitmap.removeRunCompression();
        bitmap.remove(0,1);
        assertTrue(bitmap.isEmpty());
    }

It gets a little bit more odd, when i have random bits involved:

    @Test
    public void testRemovalWithAddedAndRemovedRunOptimization() {
        // with runlength encoding
        testRangeRemovalWithRandomBits(true);
    }

    @Test
    public void testRemovalWithoutAddedAndRemovedRunOptimization() {
        // without runlength encoding
        testRangeRemovalWithRandomBits(false);
    }

    private void testRangeRemovalWithRandomBits(boolean withRunCompression) {
        final int iterations = 4096;
        final int bits = 8;

        for (int i = 0; i < iterations; i++) {
            final BitSet bitset = new BitSet(bits);
            final RoaringBitmap bitmap = new RoaringBitmap();

            // check, if empty
            assertTrue(bitset.isEmpty());
            assertTrue(bitmap.isEmpty());

            // fill with random bits
            final int added = fillWithRandomBits(bitmap, bitset, bits);

            // check for cardinalities and if not empty
            assertTrue(added > 0 ? !bitmap.isEmpty() : bitmap.isEmpty());
            assertTrue(added > 0 ? !bitset.isEmpty() : bitset.isEmpty());
            assertEquals(added, bitmap.getCardinality());
            assertEquals(added, bitset.cardinality());

            // apply runtime compression or not
            if (withRunCompression) {
                bitmap.runOptimize();
                bitmap.removeRunCompression();
            }

            // clear and check bitmap, if really empty
            bitmap.remove(0, bits);         
            assertEquals("fails with bits: "+bitset, 0, bitmap.getCardinality());
            assertTrue(bitmap.isEmpty());

            // clear java's bitset
            bitset.clear(0, bits);
            assertEquals(0, bitset.cardinality());
            assertTrue(bitset.isEmpty());

        }
    }

    private static int fillWithRandomBits(final RoaringBitmap bitmap, final BitSet bitset, final int bits) {
        int added = 0;
        for (int j = 0; j < bits; j++) {
            if (ThreadLocalRandom.current().nextBoolean()) {
                added++;
                bitmap.add(j);
                bitset.set(j);
            }
        }
        return added;
    }

Parallelize

Roaring is ideally suited for parallel execution, as all containers can be operated upon in distinct threads.

Reduce temporary object allocation

RoaringBitmap creates lots of temporary objects in the course of running even the smallest bit of code. This puts a lot of stress on the garbage collector and degrades real-world performance.

There is need for a good benchmark that would determine where the problems are. For example, there are indications that the call to "asShortBuffer" followed by "slice" create unnecessary objects.

Possibly relevant benchmarks:

      $ cd RoaringBitmap
      $ git pull
      $ cd jmh
      $ ./run.sh needwork

Map failed OOM error

Hi guys,

I am using roaring to create inverted indexed on several columns of data. for each column I try to load the roaring immutable container by doing something like this.

Each file has all the bitmap containers for each unique value present in that column.

bitmaps[k] = new ImmutableRoaringBitmap(channel.map(MapMode.READ_ONLY, offsets[k], offsets[k + 1] - offsets[k]));

The problem here is when we are dealing with high cardinality columns and several index files .... we are crossing the max-map-count very easily...

what do you guys propose I do to solve this...

the error I get is

Caused by: java.lang.OutOfMemoryError: Map failed
at sun.nio.ch.FileChannelImpl.map0(Native Method)
at sun.nio.ch.FileChannelImpl.map(FileChannelImpl.java:745)

flip() of ImmutableRoaringBitmap skips last bit of bitmap

flip() of ImmutableRoaringBitmap does not work correctly.
I skips the last bit of bitmap.
For example, with following test codes, the second test fails as ImmutableRoaringBitmap.flip() results in {1, 2} not {1}.
Both RoaringBitmap.flip() and MutableRoaringBitmap.flip() work fine.

  @Test
  public void fliptest1() {
    final MutableRoaringBitmap rb = new MutableRoaringBitmap();
    rb.add(0);
    rb.add(2);
    final MutableRoaringBitmap rb2 = MutableRoaringBitmap.flip(rb, 0, 3);
    final MutableRoaringBitmap result = new MutableRoaringBitmap();
    result.add(1);

    Assert.assertEquals(result, rb2);
  }

  @Test
  public void fliptest2() {
    final MutableRoaringBitmap rb = new MutableRoaringBitmap();
    rb.add(0);
    rb.add(2);
    final MutableRoaringBitmap rb2 = ImmutableRoaringBitmap.flip(rb, 0, 3);
    final MutableRoaringBitmap result = new MutableRoaringBitmap();
    result.add(1);

    Assert.assertEquals(result, rb2);
  }

Suggested persistence strategy ?

Hello. I assume that memory mapped bitmaps are read-only.
(Because otherwise it will involve managing free space in a map and so forth)
If my assumption is correct - I'm thinking about creating a sequence of mutable bitmaps (each of the equal size (number of bits represented)) and saving those that were changed at particular time interval after bitmap optimization in key-value storage.
Does this approach make sense ? Thanks

Include some benchmarks...

Even if it is not perfect... try to include some benchmarks... For example, you could start by comparing the speed and memory usage of RoaringBitmap with the Java BitSet class.

RoaringBitmap uses 2 bytes for every set bit?

Hey guys,

I read about RoaringBitmaps and tried 0.4.8 in the Scala console, and when I serialize it to a ByteBuffer, it consumes 2 bytes for every set bit. Is this intentional? I have followed the examples for serializing and the API is pretty simple. If so, it seems like the serialized representation would be very wasteful and in fact worse than using BitSet if the density of set bits is more than (# of elements / 16). Please help me, I must be misunderstanding something!

scala> val rb = new RoaringBitmap()
rb: org.roaringbitmap.RoaringBitmap = {}

scala> rb.add(34)

scala> rb.add(55_
<console>:1: error: Invalid literal number
       rb.add(55_
              ^

scala> rb.add(55)

scala> rb.add(56)

scala> import java.io.ByteArray
ByteArrayInputStream    ByteArrayOutputStream

scala> import java.io.ByteArrayOutputStream
import java.io.ByteArrayOutputStream

scala> import java.io.DataOutput
DataOutput         DataOutputStream

scala> import java.io.DataOutputStream
import java.io.DataOutputStream

scala> val bos = new ByteArrayOutputStream
bos: java.io.ByteArrayOutputStream =

scala> val dos = new DataOutputStream(bos)
dos: java.io.DataOutputStream = java.io.DataOutputStream@73848f6a

scala> rb.serialize
serialize               serializedSizeInBytes

scala> rb.serialize(dos)

scala> dos.close()

scala> bos.toByteArray
res5: Array[Byte] = Array(58, 48, 0, 0, 1, 0, 0, 0, 0, 0, 2, 0, 16, 0, 0, 0, 34, 0, 55, 0, 56, 0)

scala> rb.getCardinality
res6: Int = 3

scala> res5.length
res7: Int = 22

scala> (0 to 47).foreach { n => rb.add(1024 + n) }

scala> bos.
asInstanceOf   close          flush          isInstanceOf   reset          size           toByteArray
toString       write          writeTo

scala> bos.reset

scala> val dos = new DataOutputStream(bos)
dos: java.io.DataOutputStream = java.io.DataOutputStream@6e25f782

scala> rb.serialize
serialize               serializedSizeInBytes

scala> rb.serialize(dos)

scala> dos.close()

scala> bos.to
toByteArray   toString

scala> bos.toByteArray
res12: Array[Byte] = Array(58, 48, 0, 0, 1, 0, 0, 0, 0, 0, 50, 0, 16, 0, 0, 0, 34, 0, 55, 0, 56, 0, 0, 4, 1, 4, 2, 4, 3, 4, 4, 4, 5, 4, 6, 4, 7, 4, 8, 4, 9, 4, 10, 4, 11, 4, 12, 4, 13, 4, 14, 4, 15, 4, 16, 4, 17, 4, 18, 4, 19, 4, 20, 4, 21, 4, 22, 4, 23, 4, 24, 4, 25, 4, 26, 4, 27, 4, 28, 4, 29, 4, 30, 4, 31, 4, 32, 4, 33, 4, 34, 4, 35, 4, 36, 4, 37, 4, 38, 4, 39, 4, 40, 4, 41, 4, 42, 4, 43, 4, 44, 4, 45, 4, 46, 4, 47, 4)

scala> res12.length
res13: Int = 118

Unserialising empty ImmutableRoaringBitmaps results in a RuntimeException

After adapting the example code for loading a ImmutableRoaringBitmap from a ByteBuffer to load two consecutive empty bitmaps results in the following exception:

Exception in thread "main" java.lang.RuntimeException: I failed to find one of the right cookies. 0
    at org.roaringbitmap.buffer.ImmutableRoaringArray.<init>(ImmutableRoaringArray.java:102)
    at org.roaringbitmap.buffer.ImmutableRoaringBitmap.<init>(ImmutableRoaringBitmap.java:406)
…

Code (in Scala):

object EmptyBitmapLoadingBug extends App {
  val rr1 = MutableRoaringBitmap.bitmapOf()
  val rr2 = MutableRoaringBitmap.bitmapOf()
  val bos = new ByteArrayOutputStream()
  val dos = new DataOutputStream(bos)
  rr1.serialize(dos)
  rr2.serialize(dos)
  dos.close()
  val bb = ByteBuffer.wrap(bos.toByteArray)
  val rrback1 = new ImmutableRoaringBitmap(bb)
  bb.position(bb.position() + rrback1.serializedSizeInBytes())
  val rrback2 = new ImmutableRoaringBitmap(bb)
}

The exception is thrown on loading the second bitmap.

I'm using RoaringBitmaps 0.5.9 on Java 1.8.0_65.

Flyweight primitive Iterator: disappointing performance

I have merged the flyweight primitive Iterator by @bobymicroby in the main branch.

The performance results are disappointing:

  $ mvn -Dtest=TestBenchmarkIterator test
  Running org.roaringbitmap.TestBenchmarkIterator
  TestBenchmarkIterator.testFlyweight: [measured 50000 out of 50003 rounds, threads: 1 (sequential)]
   round: 0.00 [+- 0.00], round.block: 0.00 [+- 0.00], round.gc: 0.00 [+- 0.00], GC.calls: 0, GC.time: 0.00, time.total: 0.68, time.warmup: 0.00, time.bench: 0.68
  TestBenchmarkIterator.testStandard: [measured 50000 out of 50003 rounds, threads: 1 (sequential)]
   round: 0.00 [+- 0.00], round.block: 0.00 [+- 0.00], round.gc: 0.00 [+- 0.00], GC.calls: 6, GC.time: 0.01, time.total: 0.42, time.warmup: 0.00, time.bench: 0.42
  Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.816 sec - in org.roaringbitmap.TestBenchmarkIterator

See
https://github.com/lemire/RoaringBitmap/blob/master/src/test/java/org/roaringbitmap/TestBenchmarkIterator.java

Should we keep these new iterators?

ClassCastException on cloning ImmutableRoaringReverseIntIterator

in ImmutableRoaringReverseIntIterator.clone(),

public IntIterator clone() {
    try {
        ImmutableRoaringIntIterator x = (ImmutableRoaringIntIterator) super.clone();
        x.iter = this.iter.clone();
        x.cp = this.cp.clone();
        return x;
    } catch (CloneNotSupportedException e) {
        return null;// will not happen
    }
}

super.clone() is not ImmutableRoaringIntIterator, making CCE.

RoaringBitmap.andNot results in a NegativeArraySizeException

For some 2 bitmaps the RoaringBitmap.andNot operation results in the following error:

java.lang.NegativeArraySizeException
    at org.roaringbitmap.ArrayContainer.<init>(ArrayContainer.java:40)
    at org.roaringbitmap.BitmapContainer.toArrayContainer(BitmapContainer.java:772)
    at org.roaringbitmap.BitmapContainer.andNot(BitmapContainer.java:224)
    at org.roaringbitmap.Container.andNot(Container.java:142)
    at org.roaringbitmap.RoaringBitmap.andNot(RoaringBitmap.java:105)

Bitmaps were created adding elements by one by one. trim() and runOptimize() were not run. Running those operations fixes the problem. However, I think it's still a bug.

If the error cannot be reproduced purely from on the stack trace, I will try to extract the piece of data that caused the issue. Unfortunately, I cannot provide the whole bitmaps.

Running Oracle's jdk1.8.0_60
RoaringBitmaps 0.5.9
OSX 10.11.1

Question: Benchmark tests

Hello,
as I'm currently in the process of porting RoaringBitmaps to C#, I'm also porting those real data tests.
While my implementation has the same output cardinality as the unit tests, I can't really follow the time results of those tests.
For example:

  <testcase name="test[dataset=census-income, type=roaring, immutable=false]" classname="org.roaringbitmap.realdata.RealDataBenchmarkOrTest" time="1.469"/>
  <testcase name="test[dataset=census-income, type=roaring, immutable=true]" classname="org.roaringbitmap.realdata.RealDataBenchmarkOrTest" time="3.822"/>

The values appear to be too high, do they include the ZIP file part (loading and parsing the census data) or are there multiple iterations to the actual test. It uses JMH, which should handle all that warmup etc. stuff, but as I haven't done any Java work for over ten years I do not really know how e.g. the @benchmark attribute works.

Are there any real performance benchmarks against the real data, which only handle the actual bitwise operations?

Possible Endianess Mismatch in Mutable / Immutable Roaring Array Serialisation?

We are saving down mutable roaring bitmaps using memory mapped files which we later read using Immutable Roaring bitmaps.

However, after version 0.4.9 we have noticed that we get the "I failed to find the one of the right cookies" when previously we've been fine.

Note that when we serialise down a mutable bitmap the cookie token/sentinal is byte reversed (from https://github.com/lemire/RoaringBitmap/blob/master/src/main/java/org/roaringbitmap/buffer/MutableRoaringArray.java):

public void serialize(DataOutput out) throws IOException {
        int startOffset=0;
        boolean hasrun = hasRunCompression();
        if (hasrun) {
            out.writeInt(Integer.reverseBytes(SERIAL_COOKIE | ((this.size-1) << 16)));

...
        } else {
            out.writeInt(Integer.reverseBytes(12346));
            out.writeInt(Integer.reverseBytes(this.size));
            var6 = 8 + this.size * 4 + this.size * 4;
        }

And deserialising a Mutable bitmap also assumes a reversal:

public void deserialize(DataInput in) throws IOException {
        this.clear();
        // little endian
        final int cookie = Integer.reverseBytes(in.readInt());
        if ((cookie & 0xFFFF) != SERIAL_COOKIE && cookie != SERIAL_COOKIE_NO_RUNCONTAINER)
            throw new IOException("I failed to find the one of the right cookies.");

However when we then build up an Immutable Bitmap passing in the memory mapped buffer serialised this way we get the error. Noting the code in https://github.com/lemire/RoaringBitmap/blob/master/src/main/java/org/roaringbitmap/buffer/ImmutableRoaringArray.java , we see it does not reverse the bytes:

    protected ImmutableRoaringArray(ByteBuffer bbf) {
        buffer = bbf.slice();
        buffer.order(ByteOrder.LITTLE_ENDIAN);
        final int cookie = buffer.getInt(0);
        if ((cookie & 0xFFFF) != SERIAL_COOKIE && cookie != SERIAL_COOKIE_NO_RUNCONTAINER)
            throw new RuntimeException("I failed to find one of the right cookies. "+ cookie);

It is here we are seeing the bad cookie (value 976224256).

I think the issue is probably in the last block of code where instead of an explicit byte reversal it does:

buffer.order(ByteOrder.LITTLE_ENDIAN);
final int cookie = buffer.getInt(0);

Which is inconsistent with the other explicit reversal ops, which is probably fine if we assume we are writing to BIG endian buffers (perhaps?) but since we are Always using LE buffers (in our case), It may be that we are seeing the following occur:

  • Write 123457 ( explicitly reversed) to a LE buffer in mutable serialise (effectively the cookie would be BE now right?)
  • Later on using the same LE Buffer , Switch it to LE (redundant since we are already LE) ``buffer.order(ByteOrder.LITTLE_ENDIAN);` then read back non-reversed bytes.
  • Compare the reversed cookie with our expected one

Does that make sense?

What assumptions (if any) does Roaring make about endianess of the underlying Byte Buffer? We may have hit an edge case - but for us we want to run cross platform, and where possible be able to utilise native byte order.

Is this a bug or is this intentional? If the later case, then how should we be using it to prevent this issue? As far as I can tell our usage is pretty standard. We've been using roaring for quite a while and this seemed to just suddenly occur around the 0.5 releases (I initially thought it was something we were doing wrong but now I'm not so sure).

If you are having issues replicating we'll put a little java repro together for you.

Thanks and apologies for the essay :)

RoaringBitmap serialize and deserialize

I plan to serialize RoaringBitmap to mysql or Redis, but I can not deserialize the result. But I got I failed to find the right cookie. Anyone can help, thanks.

import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;

import org.roaringbitmap.buffer.ImmutableRoaringBitmap;
import org.roaringbitmap.buffer.MutableRoaringBitmap;

public class RedisTest {

    public static void main(String[] args) throws IOException {
        MutableRoaringBitmap mrb = MutableRoaringBitmap.bitmapOf(1, 2, 3, 1000);
        System.out.println("starting with  bitmap " + mrb);
        ByteBuffer outbb = ByteBuffer.allocate(mrb.serializedSizeInBytes());
        mrb.serialize(new DataOutputStream(new OutputStream() {
            ByteBuffer mBB;

            OutputStream init(ByteBuffer mbb) {
                mBB = mbb;
                return this;
            }

            public void close() {
            }

            public void flush() {
            }

            public void write(int b) {
                mBB.put((byte) b);
            }

            public void write(byte[] b) {
                mBB.put(b);
            }

            public void write(byte[] b, int off, int l) {
                mBB.put(b, off, l);
            }
        }.init(outbb)));

        outbb.flip();


        // convert bytebuffer to byte[] and save byte[] to redis
        byte[] bytes = new byte[outbb.capacity()];      
        outbb.get(bytes,0,bytes.length);
        System.out.println("byte:"+bytes);

        //str value is from variable bytes. simulate get bitmap deserialize from redis. The result '[B@5df86e79' changes every run.
        String str ="[B@5df86e79";
        ByteBuffer bb = ByteBuffer.wrap( str.getBytes("utf-8") );


        // wrong occur
        ImmutableRoaringBitmap rev = new ImmutableRoaringBitmap(bb);
        System.out.println("read bitmap " + rev);

    }
}

Roaring bitmaps for matrix operations

Hi, I apologize if this isn't the right venue for asking this question?

But I was wondering if it wouldn't be ridiculous to implement a SparseMatrix using a collection of RoaringBitmaps as its rows?

What do you think?

Cheers,
David

Discussion: What is the best way to create millions of RoaringBitmaps?

Imagine this, i have an array with millions of java's Bitset instances. Now i convert each of them into RoaringBitmap instances.

The naive approach, create a new mutable RoaringBitmap and copy bitset into it, this will create a memory mess with a lot of short[] hanging around. Either Java hops out with Heap space error or GC limit exceeded.

Right now i use a work around, that uses a master RoaringBitmap, i copy each Bitset into it, clone the master, remove all bits and move on with the next Bitset. Uses way less memory. This works fine.

What do you think? Anyone having any other ideas?

If anyone wants to test out, these are my junit tests:

    @Test
    public void testBitsetToRoaringWithSingleInstancePerBitset() {
        // create n bitsets with random, max nbits
        final int size = 4000000;
        final int nbits = 50;
        final Object[] rowBitmaps = randomBitSets(size, nbits);

        for(int i = 0; i < size; i++) {
            // for each bitset, create a single roaringbitmap, then copy via roaring.add()
            // -> hogs memory with a lot of short[], GC is not fast enough to clear up, will end up with GC limit exceeded or heap out of space
            rowBitmaps[i] = toRoaringWithUsingAddToCopyBitset((BitSet)rowBitmaps[i]);
        }       
    }

    @Test
    public void testBitsetToRoaringWithReusingInstanceAndCloneWithByteBuffer() throws IOException {     
        // create n bitsets with random, max nbits
        final int size = 4000000;
        final int nbits = 50;       
        final Object[] rowBitmaps = randomBitSets(size, nbits);

        final ByteArrayOutputStream bos = new ByteArrayOutputStream();
        final MutableRoaringBitmap master = new MutableRoaringBitmap();
        for(int i = 0; i < size; i++) {
            // for each bitset, copy into master roaring bitset, then serialize to byte[], then use roaring bitmap on byte[]
            // -> then clear master without using master.clear()
            // -> reset buffer
            rowBitmaps[i] = toRoaringWithUsingSerializationToCopyBitset((BitSet)rowBitmaps[i], master, bos);
            master.remove(0, nbits);
            bos.reset();
        }       
    }

    private static Object[] randomBitSets(final int size, final int nbits) {
        final Random random = new Random(1011);
        final Object[] bitsets = new Object[size];
        for(int i = 0; i < size; i++) {
            bitsets[i] = fillWithRandomBits(nbits, random);         
        }
        return bitsets;
    }


    private static ImmutableRoaringBitmap toRoaringWithUsingAddToCopyBitset(final BitSet bitset) {
        final MutableRoaringBitmap bitmap = new MutableRoaringBitmap();
        copyBitSetToRoaring(bitset, bitmap);
        return bitmap;      
    }

    private static ImmutableRoaringBitmap toRoaringWithUsingSerializationToCopyBitset(final BitSet bitset, final MutableRoaringBitmap master, final ByteArrayOutputStream bos) throws IOException {
        copyBitSetToRoaring(bitset, master);
        try (final DataOutputStream dos = new DataOutputStream(bos)) {
            master.serialize(dos);
        }
        return new ImmutableRoaringBitmap(ByteBuffer.wrap(bos.toByteArray()));

    }
    private static void copyBitSetToRoaring(final BitSet bs, final MutableRoaringBitmap master) {
        for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i + 1)) {
            // operate on index i here
            if (i == Integer.MAX_VALUE) {
                break; // or (i+1) would overflow
            }
            master.add(i);
        }
    }
    private static BitSet fillWithRandomBits(final int nbits, final Random random) {
        final BitSet bitset = new BitSet(nbits);
        for (int j = 0; j < nbits; j++) {
            if (random.nextBoolean()) {
                bitset.set(j);
            }
        }
        return bitset;
    }

Switch to branchless binary search

We use a conventional binary search for our array containers. In a random microbenchmark, we get that a branchless version is significantly faster.

  ShortBinarySearch.branchlessBinarySearch2     32  avgt    5   12194.481 ±   13.544  ns/op
  ShortBinarySearch.branchyBinarySearch         32  avgt    5   16102.804 ±  245.875  ns/op

  ShortBinarySearch.branchlessBinarySearch2    128  avgt    5   16681.683 ±   23.848  ns/op
  ShortBinarySearch.branchyBinarySearch        128  avgt    5   24971.793 ±  116.461  ns/op

  ShortBinarySearch.branchlessBinarySearch2   1024  avgt    5   26024.953 ±   31.438  ns/op
  ShortBinarySearch.branchyBinarySearch       1024  avgt    5   43340.094 ±   98.262  ns/op

  ShortBinarySearch.branchlessBinarySearch2   2048  avgt    5   31097.226 ±  167.894  ns/op
  ShortBinarySearch.branchyBinarySearch       2048  avgt    5   47803.827 ± 3093.436  ns/op

As you can see, the gain can be large. It is not 2x, but nearly so.

Here is the new function that could be put in the code:

https://github.com/lemire/microbenchmarks/blob/master/src/main/java/me/lemire/microbenchmarks/binarysearch/ShortBinarySearch.java#L118-L135

However, we should not trust the microbenchmark. One should check whether it does improve performance genuinely in cases we care about.

ImmutableRoaringBitmap.EMPTY ?

In some scenarios it is useful to have a null object empty ImmutableRoaringBitmap. That would eliminate having to evaluate a null condition conditions on ImmutableRoaringBitmap.or(ImmutableRoaringBitmap, ImmutableRoaringBitmap), #and(ImmutableRoaringBitmap, ImmutableRoaringBitmap) and other operations.
Do you think it makes sense?

Array index out of bound in 0.5.10 RunContainer.flip

Reproducible with the following:

public static void main(String[] args) {
int[] array = new int[]{343798, 343799, 343800, 343801, 343803, 343804, 343805, 343807, 343809, 343811, 343812, 343815, 343816, 343817, 343818, 343819,
343821, 343825, 343827, 343828, 343830, 343831, 343832, 343833, 343835, 343836, 343837, 343838,
343839, 343840, 343841, 343842, 343843, 343844, 343845, 343847, 343848, 343849, 343850, 343851, 343853, 343854, 343855, 343856, 343858, 343859,
343860, 343861, 343862, 343863, 343864, 343865, 343866, 343868, 343869, 343874, 343875,
343877, 343879, 343880, 343881, 343882, 343883, 343887, 343889, 343890, 343891, 343894, 343895, 343898, 343899, 343900, 343901, 343902, 343904,
343906, 343907, 343908, 343909, 343910, 343911, 343912, 343913, 343914, 343915, 343916,
343917, 343918, 343919, 343921, 343922, 343923, 343924, 343927, 343928, 343929, 343930, 343931, 343932, 343933, 343934, 343935, 343938, 343939,
343941, 343942, 343943, 343944, 343945, 343946, 343949, 343951, 343953, 343954, 343955,
343956, 343958, 343959, 343961, 343962, 343964, 343965, 343966, 343967, 343968, 343969, 343971, 343972, 343973, 343974, 343976, 343978, 343979,
343981, 343982, 343983, 343985, 343987, 343988, 343989, 343992, 343993, 343994, 343995,
343996, 343997, 343998, 344000, 344001, 344002, 344003, 344004, 344006, 344008, 344009, 344011, 344012, 344013, 344015, 344017, 344019, 344020,
344021, 344023, 344025, 344026, 344027, 344028, 344029, 344030, 344031, 344034, 344035,
344036, 344037, 344038, 344039, 344040, 344042, 344043, 344046, 344047 };
RoaringBitmap roaringBitmap = new RoaringBitmap();
append(roaringBitmap, array);
}

static private boolean append(RoaringBitmap bitmap, int... indexes) {
    if (indexes.length == 1) {
        bitmap.add(indexes[0]);
    } else if (indexes.length > 1) {
        int rangeStart = 0;
        for (int rangeEnd = 1; rangeEnd < indexes.length; rangeEnd++) {
            if (indexes[rangeEnd - 1] + 1 != indexes[rangeEnd]) {
                if (rangeStart == rangeEnd - 1) {
                    bitmap.add(indexes[rangeStart]);
                } else {
                    bitmap.flip(indexes[rangeStart], indexes[rangeEnd - 1] + 1);
                }
                rangeStart = rangeEnd;
            }
        }
        if (rangeStart == indexes.length - 1) {
            bitmap.add(indexes[rangeStart]);
        } else {
            bitmap.flip(indexes[rangeStart], indexes[indexes.length - 1] + 1);
        }
    }
    return true;
}

Optimize iterators over mapped bitmaps

Currently, when iterating over an ImmutableRoaringBitmap, one instantiates object containers and then iterator objects.

We could streamline this process by not creating the object containers and iterating directly over the original ByteBuffer.

Range removal fails with specific bits, when runlength encoding was applied/removed

When addding these bits and then runOptimize and removing it, range removal does not empty the bitset. But i only found this combination, that does not work.

    @Test
    public void testRangeRemovalWithEightBitsAndRunlengthEncoding() {
        final MutableRoaringBitmap bitmap = new MutableRoaringBitmap();
        bitmap.add(1); // index 1
        bitmap.add(2);
        bitmap.add(3);
        bitmap.add(4);
        bitmap.add(5);
        bitmap.add(7); // index 7

        bitmap.runOptimize();
        bitmap.removeRunCompression();

        // should remove from index 0 up to index 7 (excluding 8th)
        bitmap.remove(0, 8);
        assertTrue("fails with cardinality: "+bitmap.getCardinality(), bitmap.isEmpty());
    }

Potential of renaming classes

org.roaringbitmap and org.roaringbitmap.buffer both have the same class names (2x RoaringBitmap). In my experience this leads to horrible confusion (errors similar to RoaringBitmap not assignable to RoaringBitmap, due to picking the wrong import in one of the files).

I propose renaming the .buffer classes to BufferedRoaringBitmap or similar.

Thoughts?

Improve the semantics of equality between containers

The semantics of equality between container is based on the content stored in the container. If two containers have the same content (as a set of integers), even if they are stored differently, they are considered equal.

Currently, a bitmap container is considered always different from an array container. That works fine in practice because they are always different in the sense that they should not have the same cardinality.

However, a better approach, when testing the equality between two containers of type array and bitmap, would be to first compare their cardinality, and if they have the same cardinality (even if this should not happen), one should then check the content.

hashCode() on RoaringBitmap

Hello,

i create 1000 identical RoaringBitmap instances. Then i put them into a set.

Without runOptimize(), the size is 1 as expected.
With runOptimize(), the size is 1000, and not 1.

I would assume, runOptimize() should not make any difference. The hashCode is the problem. equals() works fine.

Here are my tests:

    @Test
    public void testRoaringWithOptimize() {
        // create the same bitmap over and over again, with optimizing it
        final Set<RoaringBitmap> setWithOptimize = Sets.newHashSet();
        final int max = 1000;
        for(int i = 0; i < max; i++) {
            final RoaringBitmap bitmapWithOptimize = new RoaringBitmap();
            bitmapWithOptimize.add(1);
            bitmapWithOptimize.add(2);
            bitmapWithOptimize.add(3);
            bitmapWithOptimize.add(4);
            bitmapWithOptimize.runOptimize();
            setWithOptimize.add(bitmapWithOptimize);
        }
        assertEquals(1, setWithOptimize.size());        
    }

    @Test
    public void testRoaringWithoutOptimize() {
        // create the same bitmap over and over again, without optimizing it
        final Set<RoaringBitmap> setWithoutOptimize = Sets.newHashSet();
        final int max = 1000;
        for(int i = 0; i < max; i++) {
            final RoaringBitmap bitmapWithoutOptimize = new RoaringBitmap();
            bitmapWithoutOptimize.add(1);
            bitmapWithoutOptimize.add(2);
            bitmapWithoutOptimize.add(3);
            bitmapWithoutOptimize.add(4);
            setWithoutOptimize.add(bitmapWithoutOptimize);
        }
        assertEquals(1, setWithoutOptimize.size());
    }

Request to add nextSetBit and nextClearBit perhaps other BitSet APIs

As obviously useful as this work is, it would be very much easier to move to if the API provided direct replacements for the standard Java BitSet API, even if in some cases they came perhaps with a performance warning.

While I implemented nextSetBit and nextClearBit trivially outside the package, it is probably done more efficiently within the package code I suspect (no time to look in to it I am afraid). All I did in external code is:

    final static int nextClearBit(RoaringBitmap rbm, int b) {

        while (rbm.contains(b)) {
            b++;
        }
        return b;
    }

    final static int nextSetBit(RoaringBitmap rbm, int b) {

        if (rbm.contains(b)) {
            return b;  // The bit we were given is set
        }

        for (final int i : rbm) {

            if (i > b) {
                return i;
            }
        }
        return -1; // There are no set bits including or after the one given
    }

It does not matter to me that the API method names are slightly different but it might to others. Initially I was going to extend the RoaringBitmap class, but you have made it final so that cannot be done. So creating a wrapper to replace BitSet is not possible without creating a class the delegates to RoaringBitMap - just a thought that you might remove the final?

Thanks for publishing this.

AND/OR of bitmaps within a range

Currently And/or are evaluated on the entire range. In some cases we know that we only care about the result for give range start to end. Similar to flip interface, it will be great to have something like

or(ImmutableRoaringBitmap... bitmaps, int rangeStart, int rangeEnd)
and(ImmutableRoaringBitmap... bitmaps, int rangeStart, int rangeEnd)

thread safety

Hi,

Have you considered making roaring bitmaps thread safe so they can be read while being updated? With read-write reentrant locks at container level the thread contention would be very low.

I made some tests with external synchronization and the performance penalty is already very low by locking on the whole segment.

Would such contribution be welcomed and if so what would be the best approach? A child implementation synchronizing on protected methods maybe ?

Memory benchmark fails to provide useful information (broken)

In the memory subdirectory, we used to have a program that would provide useful information about memory usage. It was replaced by a unit test that appears to provide no usable information:

$ cd memory
$ ./run.sh
(...)
[INFO] ------------------------------------------------------------------------
[INFO] Building Memory benchmark : RoaringBitmap 0.4.11-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ memory ---
[INFO] Deleting /Users/lemire/CVS/github/RoaringBitmap/memory/./target
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ memory ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /Users/lemire/CVS/github/RoaringBitmap/memory/./src/main/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ memory ---
[INFO] No sources to compile
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ memory ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /Users/lemire/CVS/github/RoaringBitmap/memory/./src/test/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ memory ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to /Users/lemire/CVS/github/RoaringBitmap/memory/./target/test-classes
[INFO]
[INFO] --- maven-surefire-plugin:2.18.1:test (default-test) @ memory ---
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3.797 s
[INFO] Finished at: 2015-11-13T20:38:52-05:00
[INFO] Final Memory: 14M/82M
[INFO] ------------------------------------------------------------------------

Feature Request: Set operations (does_it_intersects, or_cardinality, and_cardinality...)

It should be possible to implement fairly fast set operations like intersects(RoaringBitmap) (a method that even BitSet has) and containsAll(RoaringBitmap) (a method that BitSet lacks, but could be quite handy in some cases).

Also a fast method to count the bits of intersections/unions might be useful (I'm using RoaringBitmaps for a Jaccard Index, where both is needed – so a method that does both fast would be even more awesome). Granted, the latter is really specific, so I have no hard feelings if it doesn't get implemented.

Perhaps providing a method Stream<Pair> pairStream(RoaringBitmap, RoaringBitmap) where each Pair has an index + two longs with the bits wherever at least one of the bitmaps has bits could be used to solve all of the above issues in a generic way?

orCardinality could be faster/use less memory

Currently andCardinaly and orCardinality are implemented in such a way that intermediate containers are constructed and immediately discarded. This could be optimized significantly.

Memory usage issues

Hi,

I've been using EWAH to manage my bitmap indices for some time. Thought I'd take a look at switching to Roaring, but am running out of heap space and/or hitting the GC overhead limit.

Test code to reproduce (scenario is building an index of 5 million rows over a column with 10000 distinct values - one bitmap per value):

@Test
public void testRoaring() throws Throwable {
    final RoaringBitmap[] bitmaps = new RoaringBitmap[10000];
    for (int i = 0; i < 10000; i++) {
        bitmaps[i] = new RoaringBitmap();
    }
    final Random random = new Random();
    for (int i = 0; i < 5000000; i++) {
        final int x = random.nextInt(10000);
        bitmaps[x].add(i);
    }
}

The equivalent using EWAH runs without problem in just over a second.

Is the problem in Roaring, or am I using it incorrectly?

Thanks in advance for your help.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.