【问题标题】:Faster way to perform bit shuffling on an integer with Java使用 Java 对整数执行位改组的更快方法
【发布时间】:2015-02-20 23:39:34
【问题描述】:

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

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);
   }
}

【问题讨论】:

  • 比这更快。比 O(N) 快,其中 N 是整数的宽度
  • { return number; } 会很快产生number 的排列,但可能无法满足您未提及的其他要求。
  • 这里的目的到底是什么?
  • this question 中提供了更多方法,当您假设您已给定输入整数的位数时,该方法适用。

标签: java algorithm bit-manipulation shuffle bits


【解决方案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);
 }

【讨论】:

  • 逻辑或 (||) 应该是按位的 (|) 并且 shuffleBits 缺少 return 语句。更重要的是检查位是否不同比位交换本身要昂贵得多。一般来说,用一个整数计算来换取一个测试+分支几乎是不值得的。我做了一个简单的基准测试(包括一些开销),OP 的代码(如果我使用this code 来实现swapBit)需要 10.2 秒,你需要 11.9 秒,我需要 8.0 秒。
  • 感谢您指出代码中的错误。我刚刚对其进行了更正。很有趣的是,检查位差异的成本更高,但我认为您的性能实际上取决于有多少设置位。设置的位越多,发生碰撞的机会就越大,对吧?但无论如何你是对的,几乎不值得对其进行测试和基准测试。
  • 事实证明,碰撞并没有多大贡献。对具有超过一半位设置的数字实施优化使我的速度提高了 10%。有关改进基准的更详细结果,请参阅我的更新答案。
【解决方案2】:

这个不一定会更快,但可能会提供更均匀的分布,即使我没有充分考虑它。

基本思想是我只是设置随机位,直到结果与输入设置的位数相同。如果你想优化,你可以检查是否ones &gt;= Integer.SIZE / 2,在这种情况下,从一个所有位都设置的整数开始,然后依次将它们清零。不过,我怀疑这是否值得。 (更新:此优化使性能提高了约 10%。)

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;
}

我假设类成员 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 函数),@garriual 的建议和我的上述两个版本。公平地说,我对所有版本都应用了相同的微优化,并将 Random 对象的创建移出函数。

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

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

【讨论】:

    猜你喜欢
    • 2011-02-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-26
    • 2014-10-12
    • 1970-01-01
    • 1970-01-01
    • 2011-12-16
    相关资源
    最近更新 更多