[英]Faster way to perform bit shuffling on an integer with Java
我想知道是否有更快的方法來改變整數的位而不是以下
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);
}
}
這個不一定會更快,但可能會給出更均勻的分布,即使我沒有完全考慮過它。
基本思想是我只是設置隨機位,直到結果設置為與輸入相同的位數。 如果要進行優化,可以檢查是否ones >= 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;
}
我假設類型為java.util.Random
的類成員rnd
。 每次調用函數時創建一個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
函數)實現了一個小的基准測試, swapBit
的建議和我的上述兩個版本。 公平地說,我對所有版本都應用了相同的微優化,並將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
我會非常謹慎地在隨機數生成算法中實現“快捷方式”而沒有徹底的數學分析,這通常並不容易。 如果你超調,你可能會很快得到一個功能很差的結果。 我查看了一些直方圖,並沒有找到您的版本中存在缺陷的直接證據,但我甚至沒有查看相關圖。 正如這種比較有希望證明的那樣,更快的代碼不一定更復雜,但更簡單的代碼顯然更難以出錯。
您當然可以優化和改進當前的方法。
(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);
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.