位運算這個概念並不陌生,大多數程式設計師在進入這個領域的時候或多或少都接觸過位運算,估計當時都寫過不少練習題的。
位運算本身不難,困難的是大家沒有學會在系統設計時用上它,提高系統效能,增加你的不可替代性。
就不做太多鋪墊了,直接說下今天講述的乾貨內容:
位運算使用場景
面試經常問
比如我曾經在面試騰訊的時候
O(1) 時間如何檢測整數 n 是否是 2 的冪次?
在看一道Google面試題:
有64瓶藥,其中63瓶是無毒的,一瓶是有毒的。如果做實驗的小白鼠喝了有毒的藥,3天后會死掉,當然喝了其它的藥,包括同時喝幾種就沒事。現在只剩下3天時間,請問最少需要多少隻小白鼠才能試出那瓶藥有毒?
這就不用龍su囉嗦了吧,穩穩的都是和位運算有關的。
類似面試題目還有很多,一個不注意就會被撂倒。
這部分的題目整體難度不大,本身不是一個很大的知識點,但是很容易被大家忽略,今天龍su就拿出來好好說說,大家可要記住喔,不然…
系統設計經常用
喜歡看原始碼的同學就會注意到,經常在裡面看到這樣的程式碼。
lucene原始碼
redis原始碼
龍叔的原始碼
有沒有發現這些程式碼驚人的相似,好的設計總是這樣不謀而合。
看了這麼多,想必大家已經知道這東西還是有些作用的,應該好好搞清楚他的原理。接下來就一起來盤他。
位運算原理
位 指的是位元位(bit),不是byte,所以位運算指的就是位元位計算。
CPU所有計算都是二進位制的計算,一個高效能的服務一定是把CPU資源利用到極致,也就是用最少資源換取最大收益。
當然隨著現代CPU的計算速度不斷加快,很多人在設計系統的時候完全不會去考慮這些效能點,然而真正的高併發系統都是極致效能的。
看看我們日常開發都是啥樣的,只要不涉及到高併發,開發程式碼就算是一坨屎,也沒關係,大多數人都是在這坨屎上繼續CRUD,也就會變成了一大坨。
沒辦法,老闆只看結果,懶得管你的程式碼是什麼樣的。哎呀,好像暴露了龍叔是個CRUD菜雞選手。
等到有一天發現加機器加到扛不住了,這時候就是最幸運的一批程式設計師誕生的時候,必須開始重構系統。為什麼最幸運,大家都知道了吧?機會不是天天有的,這就是千載難逢的良機啊。
哈,好像有點說遠了。
在計算機世界裡,萬物皆0、1,0、1生萬物。萬物到0、1的過程叫做編碼。
一個數在計算機中的二進位制表示形式, 叫做這個數的機器數。機器數是帶符號的,在計算機中用一個數的最高位存放符號, 正數為0, 負數為1。
計算機中對數字的編碼表示有三種方式:原碼,反碼,補碼:
原碼:原碼錶示法在數值前面增加了一位符號位(即最高位為符號位):正數該位為0,負數該位為1。比如十進位制3如果用8個二進位制位來表示就是 00000011, -3就是 10000011。
反碼:反碼錶示方法:正數的反碼是其本身;負數的反碼是在其原碼的基礎上,符號位不變,其餘各個位取反。
補碼:補碼錶示方法:正數的補碼是其本身;負數的補碼是在其原碼的基礎上,符號位不變,其餘各位取反,最後+1。 (即在反碼的基礎上+1)
這三種是編碼方式,但是在計算機系統中,數值一律用補碼來表示(儲存)。
舉個例子:
1. 10
原碼 反碼 補碼
00001010 --> 00001010 --> 00001010
2. -15
10001111 --> 11110000 --> 11110001
說完了資料編碼,基本已經知道一個資料是怎麼儲存在計算機中的,接下來就看看資料位元位之間是如何計算的。
各種程式語言都提供了對補碼的二進位制位直接進行運算的方法,即位運算。
符號 | 描述 | 規則 |
---|---|---|
& | 與 | 相同位的兩個數字都為1,則為1;若有一個不為1,則為0。 |
| | 或 | 相同位只要一個為1即為1。 |
~ | 非 | 0和1全部取反。 |
^ | 亦或 | 相同位不同則為1,相同則為0。 |
<< | 左移 | a << b就表示把a轉為二進位制後左移b位(在後面添b個0)。 |
>> | 右移 | a >> b表示二進位制右移b位(去掉末b位),相當於a除以2的b次方(取整)。 |
舉幾個例子
10 & -15 = 00001010 & 11110001
按位進行相與,相同為1則為1,否則為0,最終算的結果為00000000 即0
10 & 15 = 00001010 & 00001111
按位進行相與,相同為1則為1,否則為0,最終算的結果為00001010 即10
10 | 15 = 00001010 | 00001111
按位進行或邏輯,相同位只要一個為1即為1 ,00001111即15
15>>2
二進位制右移2位,左邊填符號號位,右邊抹掉,得到00000011 即3
15<<2
二進位制左移2位,左邊抹掉,符號位不變,右邊填0,得到00111100
原理還是比較簡單,主要就是對位元位進行邏輯操作。
位運算為什麼那麼快?
看到這裡其實大多數人已經明白為什麼位運算快了,但暖心的龍叔還是在囉嗦下原因,就算是錦上添花(畫蛇添足)了。
- 儲存更友好,位元位儲存,不用轉換後在儲存
- CPU更友好,直接位元位操作,減少機器數到位元位的轉換
- 定址次數更少,左移一位就乘2
說一個搜尋裡面位運算帶來的效能提升
比如你在百度搜尋 廣東富婆 ,分詞會分為 廣東 富婆 兩個詞,分別從兩個倒排中召回,假設 廣東 這個詞召回了100w個doc,富婆 召回了1000W個。
此時兩個doc鏈會進行一個合併,合併的返回結果是存在廣東的同時又要存在富婆的doc。
這個合併如果是通過位元位的方式操作的話,一個64位的CPU一個指令週期可以處理64個doc,如果採用普通合併的話,一次只能合併一個doc,這個效能提升很明顯的吧,是不是感覺高效能有點意思了。
像這種效能上的提升,是無法通過增加機器解決的。
總結
這次內容不難,講出來是希望大家在做系統設計時,效能考慮不是簡單的加機器,而是真的把CPU價值最大化。
小改動、大效果,一些小的改動,會對效能提升產生很多的效果。反正我這次設計時基本把一些計算都改為了位運算。
我是龍叔,一個在網際網路大器晚成的設計師,我們下期見。喜歡我,記得關注我。