簡體   English   中英

使用Java在整數上執行位混洗的更快方法

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM