owned this note
owned this note
Published
Linked with GitHub
# 2020q3 Homework3 (quiz3)
contributed by < `Sisker1111` >
###### tags: `進階電腦系統理論與實作`
> [測驗題](https://hackmd.io/@sysprog/2020-quiz3)
>
## 1. ASR
考慮到程式碼的相容性,目的是實作一套可跨越平台的 ASR,假設在二補數系統,無論標的環境的 shift 究竟輸出為何,我們的 ASR 總可做到:
`-3 是 (-5) / 2 的輸出,並往負無限大的方向前進`
```cpp=
int asr_i(signed int m, unsigned int n)
{
const int logical = (((int) -1) OP1) > 0;
unsigned int fixu = -(logical & (OP2));
int fix = *(int *) &fixu;
return (m >> n) | (fix ^ (fix >> n));
}
```
* logical 是用來判斷 compiler 的 >> 是 logical shift 還是 arithmetic shift.
* 若是 arithmetic shift , logical 會是 0 且 fixu = 0,第 6 行的`fix ^ (fix >> n)`會變成`2b'000.....0 ^ 2b'000.....0 = 2b'000.....0`(不影響結果)
* 若是 logical shift , logical 會是 1 且 fixu = 111.....1( 32 個 1),第 6 行的`fix ^ (fix >> n)`會變成`2b'111.....1 ^ 2b'000.....1 = 2b'111.....0`( 1 的數量等同 n)
## 2. int __builtin_ctz & int __builtin_clz
>Built-in Function: int __builtin_ctz (unsigned int x)
>* Returns the number of trailing 0-bits in x, starting at the least significant bit position. If x is 0, the result is undefined.
>
>Built-in Function: int __builtin_clz (unsigned int x)
>* Returns the number of leading 0-bits in x, starting at the most significant bit position. If x is 0, the result is undefined.
用 __builtin_ctz 來實作 LeetCode [342 . Power of Four](https://leetcode.com/problems/power-of-four/),假設 int 為 32 位元寬度。實作程式碼如下:
```c=
bool isPowerOfFour(int num)
{
return num > 0 && (num & (num - 1))==0 &&
!(__builtin_ctz(num) OPQ);
}
```
此題是要確認該數字是否為 4 的冪次方,若該數為 4 的冪次方 32bits 中只會有 1 個 bit 為 1 且該 bit 右邊的 bits 數量應該偶數個,`num > 0 && num & (num-1)`首先確認該數是否為正數且只有 1 個 bit 為 1,`!(__builtin_ctz(num) & 0x1)`則是在判斷右邊0的數量是否為偶數個.
### 不同的實作
`(num & (num - 1))==0`的部分可以使用`!(__builtin_popcount(num) ^ 1)`來代替,原本想用 `__builtin_clz(num)` 來代替`num > 0`,但會因為 undefined behavior <s>噴 compiler error</s>.
:::danger
避免說「噴 compiler error」這樣不精準的話,因為
1. 閱讀者無法區分是 internal compiler error (ICE,在 gcc 開發過程中常見),抑或是你遇到編譯器拋出的一般錯誤訊息;
2. 漢語的「[噴](http://dict.revised.moe.edu.tw/cgi-bin/cbdic/gsweb.cgi?ccd=it446W&o=e0&sec=sec1&op=v&view=0-1)」意義和上述的編譯器行為無涉;
你可額外加上 `num > 0` 的檢查條件,置於 clz 呼叫之前
:notes: jserv
:::
### 練習 LeetCode [1009. Complement of Base 10 Integer](https://leetcode.com/problems/complement-of-base-10-integer/submissions/)
```c=
int bitwiseComplement(int N) {
if(!N)
return 1;
int offset = (1 << (32-__builtin_clz(N)) ) - 1;
return N ^ offset ;
}
```
* Runtime : 0 ms, faster than 100.00% of C++ online submissions.
Memory Usage : 6.1 MB, less than 40.99% of ...
### 練習 LeetCode [41. First Missing Positive](https://leetcode.com/problems/first-missing-positive/)
```c=
int firstMissingPositive(vector<int>& nums) {
if(nums.size()==0) return 1;
for(int i=0; i<nums.size(); i++){
if(nums[i] > 0 && nums[i] <= nums.size()){
int pos = nums[i]-1;
if( nums[pos] != nums[i]){
nums[pos] ^= nums[i];
nums[i] ^= nums[pos];
nums[pos] ^= nums[i];
i--;
}
}
}
for(int i=0; i<nums.size(); i++){
if(nums[i]!=(i+1))
return i+1;
}
return nums.size()+1;
}
```
* Runtime : 4 ms, faster than 79.63% of C++ online submissions.
Memory Usage : 9.9 MB, less than 49.44% of ...
## 3. __builtin_popcount
針對 LeetCode [1342. Number of Steps to Reduce a Number to Zero](https://leetcode.com/problems/number-of-steps-to-reduce-a-number-to-zero/),我們可運用 gcc 內建函式實作,假設 int 寬度為 32 位元,程式碼列表如下:
```cpp=
int numberOfSteps (int num)
{
return num ? AAA + 31 - BBB : 0;
}
```
32 bits 內的 bit = 1 的數量等同要執行 subtract 的次數,因此可以很容易之道`AAA`的答案應為`__builtin_popcount(num)`,執行完`__builtin_popcount(num)`後我們還需要向右移動 1 個 bit,透過`__builtin_clz(num)`我們可以找到最左邊的 1 在哪個位置,找到後用
`31-__builtin_clz(num)`就可以知道應該向右 shift 幾次,所以 `BBB`為`__builtin_clz(num)`.
## 4. 64-bit GCD
考慮以下 64-bit GCD (greatest common divisor, 最大公因數) 求值函式:
```cpp=
#include <stdint.h>
uint64_t gcd64(uint64_t u, uint64_t v) {
if (!u || !v) return u | v;
while (v) {
uint64_t t = v;
v = u % v;
u = t;
}
return u;
}
```
改寫為以下等價實作:
```cpp=
#include <stdint.h>
uint64_t gcd64(uint64_t u, uint64_t v) {
if (!u || !v) return u | v;
int shift;
for (shift = 0; !((u | v) & 1); shift++) {
u /= 2, v /= 2;
}
while (!(u & 1))
u /= 2;
do {
while (!(v & 1))
v /= 2;
if (u < v) {
v -= u;
} else {
uint64_t t = u - v;
u = v;
v = t;
}
} while (XXX);
return YYY;
}
```
1. 5~7 行檢查 u、v 是否皆為偶數(或是講是否能一起除以2),因為`gcd(u,v) = 2*gcd(u/2,v/2)`
2. 8~10 行檢查 u 是否為偶數,如果 u 為偶數則 v 為奇數,所以`gcd(u,v) = gcd(u/2,v)`
3. 11~12 行檢查 v 是否為偶數,如果 v 為偶數則 u 為奇數,所以`gcd(u,v) = gcd(u,v/2)`
4. 然後剩下的其實就是輾轉相除法,並且會確保進入下一個迴圈的 v 是比較小的那個值,重複直到進入這次輾轉相除時的 u 和 v 相同,因此 `XXX = v`
5. 最後 return 要把 gcd 往左邊 shift 最開始除以2的次數,才能得到正確的值,固`YYY = u << shift`.
### 透過 __builtin_ctz 改寫
注意到下面的實驗中會將 quiz3 中題目的作法命名為 fast gcd,使用 __builtin_ctz 調整後的版本則稱為 faster gcd 以方便解釋。
前面的 `for (shift = 0; !((u | v) & 1); shift++)` 迴圈可以使用 __builtin_ctz 來改寫為如下:
```cpp=
#ifndef min
#define min(x, y) ((x) < (y) ? (x) : (y))
#endif
uint64_t faster_gcd64(uint64_t u, uint64_t v){
if (!u || !v)
return u | v;
int shift = min(__builtin_ctz(u), __builtin_ctz(v));
u >>= shift; v>>= shift;
while (!(u & 1))
u >>= 1;
do {
while (!(v & 1))
v >>= 1;
if (u < v) {
v -= u;
} else {
uint64_t t = u - v;
u = v;
v = t;
}
} while (v);
return u << shift;
}
```
## 5. bit array
在影像處理中,[bit array](https://en.wikipedia.org/wiki/Bit_array) (也稱 bitset) 廣泛使用,考慮以下程式碼
```cpp=
#include <stddef.h>
size_t naive(uint64_t *bitmap, size_t bitmapsize, uint32_t *out)
{
size_t pos = 0;
for (size_t k = 0; k < bitmapsize; ++k) {
uint64_t bitset = bitmap[k];
size_t p = k * 64;
for (int i = 0; i < 64; i++) {
if ((bitset >> i) & 0x1)
out[pos++] = p + i;
}
}
return pos;
}
```
可用 clz 改寫為效率更高且等價的程式碼:
```cpp=
#include <stddef.h>
size_t improved(uint64_t *bitmap, size_t bitmapsize, uint32_t *out)
{
size_t pos = 0;
uint64_t bitset;
for (size_t k = 0; k < bitmapsize; ++k) {
bitset = bitmap[k];
while (bitset != 0) {
uint64_t t = KKK;
int r = __builtin_ctzll(bitset);
out[pos++] = k * 64 + r;
bitset ^= t;
}
}
return pos;
}
```
看向原 Code 第 9 行,目的是要從最右邊的 bit 開始 check 是否為 1,下面改寫的版本把 for 改成 while,`bitset & -bitset`是把 `bitset` 與 `-bitset` 的 2 的補數做 & ,這樣即可得到最右邊 bit 數為 1 且其他 bits 皆為 0 的 bitmap,最後第 12 行是用來將最右邊 1 改為 0,這樣下次`__builtin_ctzll(bitset)`就不會找到錯誤的 bit .