6

The function below takes two BitSets, makes a copy of the first (it must not be overridden), intersects the copy with the second (bitwise AND) and returns the cardinality of the result.

public int getIntersectionSize(BitSet bits1, BitSet bits2) {
    BitSet copy = (BitSet) bits1.clone();
    copy.and(bits2);
    return copy.cardinality();
}

I'm interested if this code can be sped up? This function is called billion of times so even a microsecond speed up makes sense plus I'm curious about the fastest possible code.

Brandon McHomery
  • 173
  • 1
  • 10
  • One idea: you could try to avoid creating a new BitSet that you're just throwing away. – Andy Turner Aug 02 '15 at 13:02
  • More information required: how long does it take to call a billion times? And can you change your algorithm to not call it a billion times? – Andy Turner Aug 02 '15 at 13:04
  • 1
    I didn't check the BitSet internals but it might be possible to do it all in one go, instead of doing an `and` and then `cardinality` try to count the cardinality **while** doing the `and` manually? – Mateusz Dymczyk Aug 02 '15 at 13:06
  • 2
    @MateuszDymczyk it looks like the `intersects` method could be adapted to do this, by replacing the `(a & b) != 0` with `Long.countBits(a & b)` and summing. **But** this would require access to `words`, which is private. – Andy Turner Aug 02 '15 at 13:11
  • Conventional wisdom suggests that, rather than the fastest way, you should develop a correct way that reads will and is maintainable. Execution speed optimization is something you delve into once you've proved that your solution is a performance bottleneck by benchmarking (most of the time, it isn't). – scottb May 22 '19 at 02:32

3 Answers3

2

If you're going to use each BitSet several times, it could be worthwhile to create a long array corresponding to each BitSet. For each BitSet:

long[] longs = bitset.toLongArray();

Then you can use the following method, which avoids the overhead of creating a cloned BitSet. (This assumes that both arrays are the same length).

int getIntersectionSize(long[] bits1, long[] bits2) {
    int nBits = 0;
    for (int i=0; i<bits1.length; i++)
        nBits += Long.bitCount(bits1[i] & bits2[i]);
    return nBits;
}
James Trimble
  • 1,868
  • 13
  • 20
1

Here is an alternative version, but I'm not sure if it is really faster, depends on nextSetBit.

public int getIntersectionsSize(BitSet bits1, BitSet bits2) {
   int count = 0;
   int i = bits1.nextSetBit(0);
   int j = bits2.nextSetBit(0);
   while (i >= 0 && j >= 0) {
      if (i < j) {
         i = bits1.nextSetBit(i + 1);
      } else if (i > j) {
         j = bits2.nextSetBit(j + 1);
      } else {
         count++;
         i = bits1.nextSetBit(i + 1);
         j = bits2.nextSetBit(j + 1);
      }
   }
   return count;
}

The above is the readable version, hopefully good enough for the compiler, but you could optimize it manually I guess:

public int getIntersectionsSize(BitSet bits1, BitSet bits2) {
   int count = 0;
   for (int i = bits1.nextSetBit(0), j = bits2.nextSetBit(0); i >= 0 && j >= 0; ) {
      while (i < j) {
         i = bits1.nextSetBit(i + 1);
         if (i < 0)
            return count;
      }
      if (i == j) {
         count++;
         i = bits1.nextSetBit(i + 1);
      }
      while (j < i) {
         j = bits2.nextSetBit(j + 1);
         if (j < 0)
            return count;
      }
      if (i == j) {
         count++;
         j = bits2.nextSetBit(j + 1);
      }
   }
   return count;
}
maraca
  • 8,468
  • 3
  • 23
  • 45
  • @BrandonMcHomery If you want to use BitSet functionality I think your version is probably the best you can get. The above could be good for "sparse" BitSets. I can delete it if you want to. – maraca Aug 02 '15 at 15:04
  • 1
    @maraca this will be slower because you are going bit by bit, internally `BitSet` works per word so the number of operations for let's say `and` is much smaller. – Mateusz Dymczyk Aug 02 '15 at 15:13
  • @MateuszDymczyk yes, could still be better for "sparse" BitSets (most bits are false) or not? – maraca Aug 02 '15 at 15:19
  • @maraca for extremely sparse sets most probably yes – Mateusz Dymczyk Aug 02 '15 at 15:28
  • @BrandonMcHomery you could try to implement Andy Turner's idea, but because you would need to call toLongArray on both bitSets you clone them both, so I guess that's not faster either. – maraca Aug 02 '15 at 15:35
0

I've been looking for a solution to this recently and here's what I came up with:

int intersectionCardinality(final BitSet lhs, final BitSet rhs) {
    int lhsNext;
    int retVal = 0;
    int rhsNext = 0;

    while ((lhsNext = lhs.nextSetBit(rhsNext)) != -1 &&
            (rhsNext = rhs.nextSetBit(lhsNext)) != -1) {
        if (rhsNext == lhsNext) {
            retVal++;
            rhsNext++;
        }
    }

    return retVal;
}

Perhaps someone would like to take the time to compare the different solutions here and post the results...

Yossi Farjoun
  • 2,180
  • 3
  • 17
  • 25