# 2023q1 Homework3 (fibdrv) contributed by < [cin-cout](https://github.com/cin-cout) > Github: [cin-cout/fibdrv](https://github.com/cin-cout/fibdrv) ## 開發環境 ```shell $ gcc --version gcc (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0 $ lscpu Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Byte Order: Little Endian Address sizes: 39 bits physical, 48 bits virtual CPU(s): 12 On-line CPU(s) list: 0-11 Thread(s) per core: 2 Core(s) per socket: 6 Socket(s): 1 NUMA node(s): 1 Vendor ID: GenuineIntel CPU family: 6 Model: 158 Model name: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz Stepping: 10 CPU MHz: 1201.821 CPU max MHz: 4500.0000 CPU min MHz: 800.0000 BogoMIPS: 5199.98 Virtualization: VT-x L1d cache: 192 KiB L1i cache: 192 KiB L2 cache: 1.5 MiB L3 cache: 12 MiB NUMA node0 CPU(s): 0-11 Vulnerability Itlb multihit: KVM: Mitigation: Split huge pages Vulnerability L1tf: Mitigation; PTE Inversion; VMX conditional cach e flushes, SMT vulnerable Vulnerability Mds: Mitigation; Clear CPU buffers; SMT vulnerable Vulnerability Meltdown: Mitigation; PTI Vulnerability Mmio stale data: Mitigation; Clear CPU buffers; SMT vulnerable Vulnerability Retbleed: Mitigation; IBRS Vulnerability Spec store bypass: Mitigation; Speculative Store Bypass disabled v ia prctl and seccomp Vulnerability Spectre v1: Mitigation; usercopy/swapgs barriers and __user pointer sanitization Vulnerability Spectre v2: Mitigation; IBRS, IBPB conditional, RSB filling , PBRSB-eIBRS Not affected Vulnerability Srbds: Mitigation; Microcode Vulnerability Tsx async abort: Not affected Flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtr r pge mca cmov pat pse36 clflush dts acpi mmx f xsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rd tscp lm constant_tsc art arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc cpuid aperf mperf pni pclmulqdq dtes64 monitor ds_cpl vmx e st tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid sse4_ 1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowpre fetch cpuid_fault epb invpcid_single pti ssbd i brs ibpb stibp tpr_shadow vnmi flexpriority ept vpid ept_ad fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid mpx rdseed adx smap clflusho pt intel_pt xsaveopt xsavec xgetbv1 xsaves dthe rm ida arat pln pts hwp hwp_notify hwp_act_wind ow hwp_epp md_clear flush_l1d arch_capabilities ``` ## 開發過程 ### 大數運算 #### bn API 大數資料結構: ```c struct bn { int cur_size; int max_size; unsigned long long *digits; }; ``` 1. bn_init ```c static struct bn *bn_init(int k) { if (k <= 0) { return NULL; } struct bn *new = kmalloc(sizeof(struct bn), GFP_KERNEL); if (!new) { return NULL; } new->digits = kmalloc(sizeof(long long) * k, GFP_KERNEL); if (!new->digits) { kfree(new); return NULL; } memset(new->digits, 0ULL, sizeof(long long) * k); new->max_size = k; new->cur_size = 1; ``` 2. bn_release ```c static void bn_release(struct bn* a){ kfree(a->digits); kfree(a); } ``` 3. bn_swap 參考 [sysprog21/bignum/bignum.c](https://github.com/sysprog21/bignum/blob/master/bignum.c) 中的作法。 ```c static void bn_swap(struct bn *a, struct bn *b) { if (!a || !b) { return; } struct bn tmp = *a; *a = *b; *b = tmp; } ``` 4. bn_add 判斷 carry 的方法參考[作業解說](https://hackmd.io/@sysprog/linux2023-fibdrv/%2F%40sysprog%2Flinux2023-fibdrv-b)。 ```c static void bn_add(struct bn* dst, struct bn* a, struct bn* b){ /*b->cur_size must bigger than a->cur_size*/ if(a->cur_size > b->cur_size){ bn_swap(a, b); } int carry = 0; for(int i=0; i<b->cur_size; i++){ if(i>=a->cur_size){ dst->digits[i] = b->digits[i] + carry; if(b->digits[i] + carry == 0ULL){carry = 1;} else{carry = 0;} } else{ dst->digits[i] = a->digits[i] + b->digits[i] + carry; if(a->digits[i] + carry > ~b->digits[i]){carry = 1;} else{carry = 0;} } } dst->cur_size = b->cur_size; if(carry){ dst->digits[b->cur_size] = carry; dst->cur_size++; } } ``` verse 2 原本 for 迴圈內的 else ,因為 digits 跟 a b 有可能會是同個位置,所以不能先給 digits 值再進行下一項的 carry 的判斷。 如果用 tmp 暫存 a 或 digits 的值,需要 64 bits的空間。 最後決定改為佔存 last_carry 需要空間較小。 ```c static void bn_add(struct bn *dst, struct bn *a, struct bn *b) { if (!dst || !a || !b) { return; } /*b->cur_size must bigger than a->cur_size*/ if (a->cur_size > b->cur_size) { bn_swap(a, b); } int carry = 0; for (int i = 0; i < b->cur_size; i++) { if (i >= a->cur_size) { dst->digits[i] = b->digits[i] + carry; if (b->digits[i] + carry == 0ULL) { carry = 1; } else { carry = 0; } } else { int last_carry = carry; if (a->digits[i] + carry > ~b->digits[i]) { carry = 1; } else { carry = 0; } dst->digits[i] = a->digits[i] + b->digits[i] + last_carry; } } dst->cur_size = b->cur_size; if (carry) { dst->digits[b->cur_size] = 1ULL; dst->cur_size++; } } ``` 5. bn_print 參考 [ray90514](https://github.com/ray90514/fibdrv/blob/master/client.c)。 ```c void print_bn(unsigned long long *digits, long long size) { if (!digits || size <= 0) { return; } unsigned __int128 div = 0; for (int i = 0; i < size; i++) { div = 0; digits[size] = 0; for (int j = size - 1; j >= i; j--) { div = div << 64 | digits[j]; digits[j + 1] = div / MAX_DIV; div = div % MAX_DIV; } digits[i] = div; size += digits[size] > 0; } printf("%llu", digits[size - 1]); for (int i = size - 2; i >= 0; i--) { printf("%019llu", digits[i]); } printf(".\n"); } ``` 接下來此段為 fast doubling 會使用的 API 6. bn_shift k 為要 shift 多少次,意即乘多少次2。但 fast doubling 只須 *2 所以通常 k 都等於 1。 OF_CHECK 為 2^63 目的為判斷 long long 最高位是否為 1 ,若為 1 下一項就須額外加 1。 ```c static void bn_shift(struct bn *dst, int k, struct bn *a) { int carry; for (int i = 0; i < k; i++) { carry = 0; for (int j = 0; j < a->cur_size; j++) { dst->digits[j] = a->digits[j] << 1; if (carry) { dst->digits[j]++; } if (a->digits[j] & OF_CHECK) { carry = 1; } else{ carry = 0; } } dst->cur_size = a->cur_size; if (carry) { dst->digits[a->cur_size] = 1ULL; dst->cur_size++; } } } ``` 7. bn_sub 原本有做一版使用先把 b 轉為二補數,在使用原本 bn_add 直接做相加,但後來考慮到二補數相加必須建立在加數與被加數相同 bit 數的情況,不適用於大數的資料結構,故改使用基本的直式相減。 ```c static void bn_sub(struct bn *dst, struct bn *a, struct bn *b) { if (a->cur_size < b->cur_size) { bn_swap(a, b); } int borrow = 0; unsigned long long mll = -1; for (int i = 0; i < a->cur_size; i++) { if (i >= b->cur_size) { dst->digits[i] = a->digits[i]; if (borrow) { if (a->digits[i] == 0) { borrow = 1; } else { borrow = 0; } dst->digits[i]--; } } else { if (a->digits[i] - borrow < b->digits[i] || (borrow == 1 && a->digits[i] == 0)) { dst->digits[i] = (mll - b->digits[i] - borrow) + a->digits[i] + 1; borrow = 1; } else { dst->digits[i] = a->digits[i] - b->digits[i] - borrow; borrow = 0; } } } for (int i = dst->max_size - 1; i >= 0; i--) { if (dst->digits[i]) { dst->cur_size = i + 1; break; } } } ``` 8. bn_mul 亦是使用直式乘法的邏輯,限制為因為將值寫入 dst 後,還需繼續使用 a b ,故不能像其他 API 一樣 dst 可以等於 a 或 b,另外 dst 初始值必須為 0。 因為 64 bits * 64 bits 結果最大可能為 128bits ,所以使用 unsigned __int128 res 紀錄乘法結果。做乘法時一定需要先做資料型態的轉換意即 (unsigned __int128),不然相乘結果會是 long long 型態,則無法求得 carry 。 ```c static void bn_mul(struct bn *dst, struct bn *a, struct bn *b) { /*a or b cannot equal to dst*/ unsigned __int128 res; unsigned long long carry; for (int i = 0; i < b->cur_size; i++) { res = 0; carry = 0ULL; for (int j = 0; j < a->cur_size; j++) { res = (unsigned __int128)a->digits[j] * (unsigned __int128)b->digits[i] + carry; carry = res >> 64; if (dst->digits[j + i] > (long long) ~res) { carry++; } dst->digits[j + i] += (long long)res; } if (carry) { dst->digits[a->cur_size + i] = carry; } } for (int i = dst->max_size - 1; i >= 0; i--) { if (dst->digits[i]) { dst->cur_size = i + 1; break; } } } ``` ### 實做 fib #### 迭代 先依照範例的迭代方法,只是使用不同的資料結構,來完成大數運算。 首先要考慮的事在進行迭代時,要知道怎麼定義 bn 的 size。 參考了兩個方法,分別來自於 [KYG-yaya573142](https://github.com/KYG-yaya573142/fibdrv/blob/master/bn_kernel.c) 和 [上課講義](https://hackmd.io/@sysprog/linux2023-fibdrv/%2F%40sysprog%2Flinux2023-fibdrv-b)。 [KYG-yaya573142](https://github.com/KYG-yaya573142/fibdrv/blob/master/bn_kernel.c): ```c void bn_fib(bn *dest, unsigned int n) { bn_resize(dest, 1); if (n < 2) { // Fib(0) = 0, Fib(1) = 1 dest->number[0] = n; return; } bn *a = bn_alloc(1); bn *b = bn_alloc(1); dest->number[0] = 1; for (unsigned int i = 1; i < n; i++) { bn_cpy(b, dest); bn_add(dest, a, dest); bn_swap(a, b); } bn_free(a); bn_free(b); } ``` 程式在 bn_add() 時會引導到 bn_do_add(): ```c static void bn_do_add(const bn *a, const bn *b, bn *c) { // max digits = max(sizeof(a) + sizeof(b)) + 1 int d = MAX(bn_msb(a), bn_msb(b)) + 1; d = DIV_ROUNDUP(d, 32) + !d; bn_resize(c, d); // round up, min size = 1 unsigned long long int carry = 0; for (int i = 0; i < c->size; i++) { unsigned int tmp1 = (i < a->size) ? a->number[i] : 0; unsigned int tmp2 = (i < b->size) ? b->number[i] : 0; carry += (unsigned long long int) tmp1 + tmp2; c->number[i] = carry; carry >>= 32; } if (!c->number[c->size - 1] && c->size > 1) bn_resize(c, c->size - 1); } ``` [KYG-yaya573142](https://github.com/KYG-yaya573142/fibdrv/blob/master/bn_kernel.c) 在決定 bn 的 size 是由不斷動態調整(一直 resize bn 的 size )來決定,所以需要一直 realloc。 [上課講義](https://hackmd.io/@sysprog/linux2023-fibdrv/%2F%40sysprog%2Flinux2023-fibdrv-b): ```c #define BUFFSIZE (8 * sizeof(int) * LENGTH / 3 + 2) #define LOGPHI (0.20898764025) #define LOGSQRT5 (0.34948500216) int main() { int offset = 100; /* TODO: try test something bigger than the limit */ int fd = open(FIB_DEV, O_RDWR); if (fd < 0) { perror("Failed to open character device"); exit(1); } float digits; int size; for (int i = 0; i <= offset; i++) { // calculate how many digits are needed for fib(i) // digits needed for fib(n) = n*LOG(Phi) - (LOG √5) digits = i * LOGPHI - LOGSQRT5; float buf_size = floor(digits / 9); size = (int) buf_size; char *buf = malloc(sizeof(char) * (BUFFSIZE * size)); lseek(fd, i, SEEK_SET); long long time1 = read(fd, buf, 0); long long time2 = read(fd, buf, 1); printf("%d %lld %s\n", i, time1, buf); free(buf); } ``` 透過Fibonnaci Number 有一個近似值的公式 0.4472135955∗1.61803398875n 利用這個公式我們可以近似出所需的空間。 如此一來一開始就可以分配好空間,不需要不斷 realloc bn 的 size。 因為 linux kernel 為了加速運算,所以不能進行浮點數運算,在 kernel 也無法使用 math.h 函式庫。[上課講義](https://hackmd.io/@sysprog/linux2023-fibdrv/%2F%40sysprog%2Flinux2023-fibdrv-b) 提供了不用浮點數的版本 (−181400625+n×108475312)/10^10 + 1 粗估為 (-181+n*109)/10^4 + 1 最終迭代版本的 fib 為下: 詳細 API 請見上方。 ```c static struct bn *fib_sequence(long long k) { if (k < 0) { return NULL; } int ll_size = (-181 + k * 109) / 1000 + 1; struct bn *a = bn_init(ll_size); struct bn *b = bn_init(ll_size); b->digits[0] = 1ULL; for (int i = 2; i <= k; i++) { bn_add(a, a, b); bn_swap(a, b); } if (k == 0) { bn_swap(a, b); } bn_release(a); return b; } ``` #### Result 成功計算到 f(500)。 ``` -Reading from /dev/fibonacci at offset 498, returned the sequence 53254932961459429406936070704742495854129188261636423939579059478176515507039697978099330699648074089624. -Reading from /dev/fibonacci at offset 499, returned the sequence 86168291600238450732788312165664788095941068326060883324529903470149056115823592713458328176574447204501. -Reading from /dev/fibonacci at offset 500, returned the sequence 139423224561697880139724382870407283950070256587697307264108962948325571622863290691557658876222521294125. -Reading from /dev/fibonacci at offset 500, returned the sequence 139423224561697880139724382870407283950070256587697307264108962948325571622863290691557658876222521294125. -Reading from /dev/fibonacci at offset 499, returned the sequence 86168291600238450732788312165664788095941068326060883324529903470149056115823592713458328176574447204501. -Reading from /dev/fibonacci at offset 498, returned the sequence 53254932961459429406936070704742495854129188261636423939579059478176515507039697978099330699648074089624. ``` #### Fast Doubling 參考 [Calculating Fibonacci Numbers by Fast Doubling](https://chunminchang.github.io/blog/post/calculating-fibonacci-numbers-by-fast-doubling) 寫的非常完整,也有非常完整的程式碼,但需要花功夫對大數運算方面做修改。 邏輯與文章中最後一段的實作相似,使用迭代的方式。需要注意的是 因為數列 input 值是使用 long long 紀錄。所以 mask 須改為 long long 的型態。此外 bn_mul 的限制為因為將值寫入 dst 後,還需繼續使用 a b ,故不能像其他 API 一樣 dst 可以等於 a 或 b,另外 dst 初始值必須為 0。 所以在放入參數時,須考慮限制。 ```c static struct bn *fib_fast_doubling(long long k) { unsigned int h = 0; for (long long i = k; i; h++, i >>= 1) ; int ll_size = (-181 + k * 109) / 1000 + 1; struct bn *a = bn_init(ll_size); struct bn *b = bn_init(ll_size); struct bn *c = bn_init(ll_size); struct bn *tmp = bn_init(ll_size); struct bn *d = bn_init(ll_size); b->digits[0] = 1ULL; for (long long mask = 1 << (h - 1); mask; mask >>= 1) { // c = a * (2 * b - a);188 // d = a * a + b * b189 memset(c->digits, 0ULL, sizeof(long long) * ll_size); bn_shift(tmp, 1, b); bn_sub(tmp, tmp, a); bn_mul(c, a, tmp); memset(d->digits, 0ULL, sizeof(long long) * ll_size); memset(tmp->digits, 0ULL, sizeof(long long) * ll_size); bn_mul(d, a, a); bn_mul(tmp, b, b); bn_add(d, d, tmp); if (mask & k) { bn_add(b, c, d); bn_swap(a, d); } else { bn_swap(a, c); bn_swap(b, d); } } bn_release(b); bn_release(d); bn_release(tmp); bn_release(c); return a; } ``` #### Result 與迭代法相同,亦可成功計算到 f(500)。 ``` -Reading from /dev/fibonacci at offset 498, returned the sequence 53254932961459429406936070704742495854129188261636423939579059478176515507039697978099330699648074089624. -Reading from /dev/fibonacci at offset 499, returned the sequence 86168291600238450732788312165664788095941068326060883324529903470149056115823592713458328176574447204501. -Reading from /dev/fibonacci at offset 500, returned the sequence 139423224561697880139724382870407283950070256587697307264108962948325571622863290691557658876222521294125. -Reading from /dev/fibonacci at offset 500, returned the sequence 139423224561697880139724382870407283950070256587697307264108962948325571622863290691557658876222521294125. -Reading from /dev/fibonacci at offset 499, returned the sequence 86168291600238450732788312165664788095941068326060883324529903470149056115823592713458328176574447204501. -Reading from /dev/fibonacci at offset 498, returned the sequence 53254932961459429406936070704742495854129188261636423939579059478176515507039697978099330699648074089624. ``` ### 效能分析 #### 前置設定 為了避免會影響的效能測量的因素,需要做前置設定,前置設定參考 [上課講義](https://hackmd.io/@sysprog/linux2023-fibdrv/%2F%40sysprog%2Flinux2023-fibdrv-c) 和 [KYWeng](https://hackmd.io/@KYWeng/rkGdultSU#%E9%87%8F%E6%B8%AC%E6%99%82%E9%96%93%E7%9A%84%E6%96%B9%E5%BC%8F),若後者有指令無法執行,則以上課講義為主。 因為每次開機都需要重新設定一次,仿照 [KYWeng](https://hackmd.io/@KYWeng/rkGdultSU#%E9%87%8F%E6%B8%AC%E6%99%82%E9%96%93%E7%9A%84%E6%96%B9%E5%BC%8F) ,自己寫了一個 [measure.sh](https://github.com/cin-cout/fibdrv/blob/master/scripts/measure.sh),作用為:前置設定->執行程式並畫圖->恢復預設值。 ```bash #!/bin/bash CPUID=5 ORIG_ASLR=`cat /proc/sys/kernel/randomize_va_space` ORIG_GOV=`cat /sys/devices/system/cpu/cpu$CPUID/cpufreq/scaling_governor` ORIG_TURBO=`cat /sys/devices/system/cpu/intel_pstate/no_turbo` ORIG_SMT=`cat /sys/devices/system/cpu/smt/control` sudo sh -c "echo 0 > /proc/sys/kernel/randomize_va_space" sudo sh -c "echo performance > /sys/devices/system/cpu/cpu$CPUID/cpufreq/scaling_governor" sudo sh -c "echo 1 > /sys/devices/system/cpu/intel_pstate/no_turbo" sudo sh -c "echo off > /sys/devices/system/cpu/smt/control" python3 ./scripts/driver.py sudo sh -c "echo $ORIG_ASLR > /proc/sys/kernel/randomize_va_space" sudo sh -c "echo $ORIG_GOV > /sys/devices/system/cpu/cpu$CPUID/cpufreq/scaling_governor" sudo sh -c "echo $ORIG_TURBO > /sys/devices/system/cpu/intel_pstate/no_turbo" sudo sh -c "echo $ORIG_SMT > /sys/devices/system/cpu/smt/control" ``` 要特別注意,[KYWeng](https://hackmd.io/@KYWeng/rkGdultSU#%E9%87%8F%E6%B8%AC%E6%99%82%E9%96%93%E7%9A%84%E6%96%B9%E5%BC%8F) 的例子中 CPUID 是使用 7,但是每台電腦 CPU 數量都不同,我的電腦只有 6 個 CPU 所以使用 5 。 若要確認 CPU 數量,可執行: ```bash $ nproc ``` 必須先跑完前置設定執行後的值才是可以設定的量。這是因為在前面的操作中,關閉了超線程和 CPU Turbo Boost,導致可用 CPU 的數量減少。nproc 命令顯示的是可用 CPU 的邏輯數量,而不是實際的物理 CPU 數量。在某些情況下,這兩個數量是不同的。 執行時因為路徑的關係,必須在 ./scripts 外以下方式執行: ```bash $ ./scripts/measure.sh ``` #### 測量時間效能 參考[上課講義](https://hackmd.io/@sysprog/linux2023-fibdrv/%2F%40sysprog%2Flinux2023-fibdrv-c)的建議,使用原本沒有使用到的 write function 作為時間測量,用 size 決定使用何種 method。 ```c static ssize_t fib_write(struct file *file, const char *buf, size_t size, loff_t *offset) { ktime_t kt; struct bn *a; switch (size) { case 0: kt = ktime_get(); a = fib_sequence(*offset); kt = ktime_sub(ktime_get(), kt); break; case 1: kt = ktime_get(); a = fib_fast_doubling(*offset); kt = ktime_sub(ktime_get(), kt); break; } bn_release(a); return (ssize_t) ktime_to_ns(kt); } ``` client 方面把得到的時間寫檔。 ```c /*fprint iterative*/ for (int i = 0; i <= offset; i++) { lseek(fd, i, SEEK_SET); sz = write(fd, write_buf, 0); fprintf(fptr_i, "%d %llu\n", i, sz); } /*fprint fast doubling*/ for (int i = 0; i <= offset; i++) { lseek(fd, i, SEEK_SET); sz = write(fd, write_buf, 1); fprintf(fptr_f, "%d %llu\n", i, sz); } ``` 也有實做利用 gnuplot 來畫圖,但後來考慮到要做統計以及刪除異常值,直接用 python script 的形式會比起使用 c 來的快速許多,畫圖就直接使用 python 了。 ```gnu set title "perf" set xlabel "nth fibonacci" set ylabel "time (ns)" set terminal png font " Times_New_Roman,12 " set output "time.png" set key left plot\ "iterative.txt" using 1:2 with linespoints linewidth 2 title "iterative", \ "fast_doubling.txt" using 1:2 with linespoints linewidth 2 title "fast doubling" ``` #### 統計方法 使用 python script 的方法是基於 [colinyoyo26](https://github.com/colinyoyo26/fibdrv/blob/master/scripts/driver.py) 的想法上,配合自己儲存資料的型態去做更改。作用為將執行100次的結果去除 95% 區間之外的值再做平均。make for_py 是前置的一些編譯。 ```python import subprocess import numpy as np import matplotlib.pyplot as plt runs = 100 def outlier_filter(data, threshold = 2): z = np.abs((data - data.mean()) / data.std()) return data[z < threshold] def data_processing(data): n_f = data[0].shape[1] final = np.zeros(n_f) for n in range(n_f): final[n] = outlier_filter(data[:,0,n]).mean() return final if __name__ == "__main__": Yi = [] Yf = [] subprocess.run('make for_py', shell=True) for i in range(runs): subprocess.run('sudo taskset -c 5 ./client > /dev/null', shell=True) output_i = np.loadtxt('./scripts/iterative.txt', dtype='int').T output_f = np.loadtxt('./scripts/fast_doubling.txt', dtype='int').T Yi.append(np.delete(output_i, 0, 0)) Yf.append(np.delete(output_f, 0, 0)) X = output_i[0] Yi = np.array(Yi) Yi = data_processing(Yi) Yf = np.array(Yf) Yf = data_processing(Yf) fig, ax = plt.subplots(1, 1, sharey = True) ax.set_title('perf', fontsize = 16) ax.set_xlabel(r'$n_{th}$ fibonacci', fontsize = 16) ax.set_ylabel('time (ns)', fontsize = 16) ax.plot(X, Yi, marker = '+', markersize = 3, label = 'iterative') ax.plot(X, Yf, marker = '*', markersize = 3, label = 'fast_doubling') ax.legend(loc = 'upper left') plt.savefig('time_5000.png') subprocess.run('sudo rmmod fibdrv || true >/dev/null', shell=True) ``` #### Result [fast doubling]((https://chunminchang.github.io/blog/post/calculating-fibonacci-numbers-by-fast-doubling)) 雖然也要迭代,但是次數遠比每項迭代來的少許多。而基本的迭代法會隨著 n 線性成長。 前500項比較,與預期結果相同。 ![](https://i.imgur.com/43HgPtm.png) 前5000項比較,與預期結果相同。 ![](https://i.imgur.com/EL917hh.png) ### 額外參考資料 [Character devices](https://hackmd.io/@combo-tw/ryRp--nQS) [fseek() ](http://tw.gitbook.net/c_standard_library/c_function_fseek.html) [費式數列計算機](https://codepen.io/tales00/pen/jKroMJ)