每日一面 - 爲何我們經常使用 2 的 N 次方作爲分片數量?

分片算法經常是計算一個值之後,對於分片個數取模,計算到底使用哪個分片。我們經常看到很多地方高性能的代碼設計,都是將分片數量設置爲 2 的 N 次方。例如 ForkJoinPool 的任務隊列 WorkQueue 的大小,MyCat 的某些分片算法在計算分片的時候對於分片數量如果是 2 的 N 次方也有優化,那麼爲什麼呢?

對於 2 的 N 次方取餘,相當於對 2 的 N 次方減一取與運算, N 爲正整數。爲什麼呢?通過下圖就能很容易理解:

十進制中,對於 10 的 N 次方取餘,直觀來看就是: image 其實就是將最後 n 位取出,就是餘數。 對於二進制,是一樣的: image 這個運算相當於,對於 n-1 取與: image

這個是一個很經典的位運算運用,廣泛用於各種高性能框架。例如在生成緩存隊列槽位的時候,一般生成2的n次方個槽位,因爲這樣在選擇槽位的時候,就可以用取與代替取餘;java 中的 ForkJoinPool 的隊列長度就是定爲 2 的 N 次方;netty 中的緩存池的葉子節點都是 2 的 N 次方,當然這也是因爲是平衡二叉查找樹算法的實現。

我們來看下性能會好多少:

@Benchmark
@Warmup(iterations = 0)
@Measurement(iterations = 300)
public void mod2_n_1(Generator generator) {
    int result = 0;
    for (int j = 0; j < generator.divide.length; j++) {
        int l = generator.divide[j];
        result += Integer.MAX_VALUE % l;
    }
}

@Benchmark
@Warmup(iterations = 0)
@Measurement(iterations = 300)
public void mod2_n_2(Generator generator) {
    int result = 0;
    for (int j = 0; j < generator.divide.length; j++) {
        int l = generator.divide[j];
        result += Integer.MAX_VALUE & (l - 1);
    }
}

結果:

Benchmark                                    Mode  Cnt         Score           Error  Units
BitUtilTest.mod2_n_1                        thrpt  300  10632698.855 ±   5843378.697  ops/s
BitUtilTest.mod2_n_2                        thrpt  300  80339980.989 ±  21905820.262  ops/s

同時,我們從這裏也可以引申出,判斷一個數是否是2的 N 次方的方法,就是看這個數與這個數減一取與運算看是否是0,如果是,則是 2 的 N 次方, N爲正整數image

每日一刷,輕鬆提升技術,斬獲各種offer:

image

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章