使用Java在整数上执行位混洗的更快方法

时间:2015-02-20 23:39:34

标签: java algorithm bit-manipulation shuffle bits

我想知道是否有更快的方法来改变整数位而不是以下

public int shuffleBits(int number) {
   int int_width = 31;
   Random random = new Random();
   for(int i = 0; i < int_width; i++) {
         number = swapBit(number, i, random.nextInt(int_width - i) + i);
   }
}

2 个答案:

答案 0 :(得分:1)

这个不一定会更快,但可能会给出更均匀的分布,即使我没有完全考虑过它。

基本思想是我只是设置随机位,直到结果设置为与输入相同的位数。如果要进行优化,可以检查ones >= Integer.SIZE / 2是否为public final int shuffle(final int number) { final int ones = Integer.bitCount(number); int result = 0; while (Integer.bitCount(result) < ones) { final int position = this.rnd.nextInt(Integer.SIZE); result |= (1 << position); } return result; } ,在这种情况下,以所有位设置的整数开始,并将它们连续归零。不过,我怀疑这是值得的。 (更新:此优化可使性能提高约10%。)

rnd

我假设类型为java.util.Random的班级成员new。每次调用函数时创建一个public int shuffle(final int number) { final int ones = Integer.bitCount(number); int result; do { result = this.random.nextInt(); } while (Integer.bitCount(result) != ones); return result; } 随机生成器是一个真正的性能杀手,所以你不想这样做。

另一种更简单的方法是简单地生成均匀分布的随机整数,直到最终生成具有正确位数的一个。

swapBit

我已经为原始帖子(添加了Random函数)的代码实现了一个小基准,@garriual的建议和我的上述两个版本。公平地说,我对所有版本都应用了相同的微优化,并将import java.util.ArrayList; import java.util.List; import java.util.Random; interface BitShuffler { int shuffle(int number); } // Version by 'peter' (http://stackoverflow.com/q/28640108/1392132) with a few // minor stylistic edits and micro-optimizations. The 'swapBit' function // (missing in the OP) was implemented inspired by Sean Eron Anderson's "Bit // Twiddling Hacks" (http://graphics.stanford.edu/~seander/bithacks.html). final class Version0 implements BitShuffler { private final Random random = new Random(); @Override public int shuffle(int number) { for (int i = 0; i < Integer.SIZE - 1; ++i) { final int j = this.random.nextInt(Integer.SIZE - 1 - i) + i; number = Version0.swapBit(number, i, j); } return number; } private static int swapBit(final int n, final int i, final int j) { final int d = ((n >>> i) ^ (n >>> j)) & 1; return n ^ ((d << i) | (d << j)); } } // Version by 'garriual' (http://stackoverflow.com/a/28640666/1392132) with a // few minor stylistic edits and micro-optimizations. final class Version1 implements BitShuffler { private final Random random = new Random(); @Override public int shuffle(int number) { final int k = Integer.bitCount(number); int swaps = 0; int setBits = 0; for (int i = 0; i < Integer.SIZE - 1 && setBits < k; ++i) { final int j = this.random.nextInt(Integer.SIZE - 1 - i) + i; if (Version1.bitsAreDifferent(number, i, j)) { number ^= (1 << i) | (1 << j); } if (((number >> i) & 1) == 1) { ++setBits; } } return number; } private static boolean bitsAreDifferent(final int n, final int i, final int j) { return ((n >> i) & 1) != ((n >> j) & 1); } } // Version by '5gon12eder' (http://stackoverflow.com/a/28640257/1392132) with // additional optimization for numbers with more than half of the bits set. final class Version2 implements BitShuffler { private final Random random = new Random(); @Override public int shuffle(final int number) { final int ones = Integer.bitCount(number); final int bits = (ones <= Integer.SIZE / 2) ? ones : Integer.SIZE - ones; int result = 0; while (Integer.bitCount(result) < bits) { final int position = this.random.nextInt(Integer.SIZE); result |= (1 << position); } return (ones == bits) ? result : ~result; } } // Yet another version by '5gon12eder' // (http://stackoverflow.com/a/28640257/1392132). final class Version3 implements BitShuffler { private final Random random = new Random(); @Override public int shuffle(final int number) { final int ones = Integer.bitCount(number); int result; do { result = this.random.nextInt(); } while (Integer.bitCount(result) != ones); return result; } } public class Main { // Run that many iterations per benchmark. private static final int ITERATIONS = 10000000; // Run each benchmark that many times to allow the JIT compiler to "warm up". private static final int RUNS = 3; public static void main(String[] args) { final Random rnd = new Random(); final BitShuffler[] implementations = { new Version0(), new Version1(), new Version2(), new Version3(), }; for (final BitShuffler impl : implementations) { for (int i = 0; i < Main.RUNS; ++i) { final long t1 = System.nanoTime(); int dummy = 0; for (int j = 0; j < Main.ITERATIONS; ++j) { final int input = rnd.nextInt(); final int output = impl.shuffle(input); dummy ^= output; // prevent computation from being optimized away assert Integer.bitCount(input) == Integer.bitCount(output); } final long t2 = System.nanoTime(); final double seconds = 1.0E-9 * (t2 - t1); System.out.printf("%s (%08X): %5.2f s%n", impl.getClass().getCanonicalName(), dummy, seconds); } System.out.println(); } } } 对象的创建移出了函数。

Version0 (E53D1257):  8.79 s
Version0 (9B5AD10C):  8.85 s
Version0 (2F64EE10):  8.85 s

Version1 (B994EEFB): 10.45 s
Version1 (85F45427): 10.56 s
Version1 (351A72A6): 10.45 s

Version2 (E6A69739):  4.59 s
Version2 (B5DFC42C):  4.58 s
Version2 (816CA9A4):  4.58 s

Version3 (D42B8B0B):  7.16 s
Version3 (1FC7A303):  7.90 s
Version3 (3CB0C233):  8.33 s

我通过在笔记本电脑上运行基准测试得到了这些结果。

{{1}}

我会非常谨慎地在随机数生成算法中实现“快捷方式”而没有彻底的数学分析,这通常并不容易。如果你超调,你可能会很快得到一个功能很差的结果。我查看了一些直方图,并没有找到您的版本中存在缺陷的直接证据,但我甚至没有查看相关图。正如这种比较有希望证明的那样,更快的代码并不一定更复杂,但更简单的代码显然更难以出错。

答案 1 :(得分:1)

您当然可以优化和改进当前的方法。

(1)我们只想在第i和第j位不同时执行交换(如果两者都是0或1则不需要交换),我们可以简单地对两个位进行翻转。最多有k个交换,其中k是设置位的数量。

(2)我们可以使用计数器跟踪我们在循环时看到多少1,并在我们到达k时尽早退出。

public int shuffleBits(int number) 
{
    int int_width = 31;
    int swaps = 0;
    int K = Integer.bitCount(number);
    int setBits = 0;
    Random random = new Random();

    for(int i = 0; i < int_width && setBits < K; i++) {
       int j = random.nextInt(int_width - i) + i;

       if(bitsAreDifferent(number, i, j)) {
           number ^= (1 << i) | (1 << j);
       }

       if(((number >> i) & 1) == 1) setBits++;
    }
    return number;
 }

 private boolean bitsAreDifferent(int number, int i, int j) {
    return ((number >> i) & 1) != ((number >> j) & 1);
 }