Tag

2017年10月7日 星期六

論共享

其實這篇文章7月底就寫好了,只是一直都沒有寫完整貼出來,現在共享經濟已經慢慢進到衰退期,新聞也沒這麼多了,貼出來單純就是留個記錄XD

隨 obike 進駐台灣,讓台灣自 Uber, Airbnb 之後,再一次體會一個共享的商業模式,撇開 obike 違停造成的爭議不談,有一派的說法是,共享其實毫無新奇之處,飯店就是共享豪宅,圖書館是共享冷氣…等等。

我認為共享其實是個新概念,而不如上述所說,只是一個很潮的新名詞。

在過去其實是沒有共享,而是<公用>和<租用>的綜合,提供方是政府公家單位,亦或法人企業,就如上例中的圖書館、飯店,要服務大量使用者,清潔和維護都需要成本,因此會出現一個單一節點的單位為管理者。
與過去的公用及租用不同,現今的<共享>是運用科技,將管理的成本降到極低,同時允許一般民眾都能成為服務提供者,Uber, Airbnb 屬於此類,Uber 直接結合 GPS、刷卡付費、手機App 等科技工具,跳過管理計程車的規則,讓每個人都能成為計程車司機;Airbnb 則是允許任何有閒餘房子的人,上網登記提供給任何有短期需要的背包客。

兩者的重點都在於,透過網路大範圍的招募供應方和租用方,這和過去的公共提供,有單一節點的供應方截然不同。

就我這個定義來看,共享單車是否可稱為共享其實是存疑的,畢竟任何共享單車仍然有對應的管理單位,而非共享每個人擁有的腳踏車,我家裡有多的腳踏車,我並沒有辦法上網登錄,然後把它丟到路上讓大家使用,使用現行的腳踏車,一樣要向公司繳交押金、使用費用(對…其實就租金),這跟 Uber, Airbnb 所謂的共享,仍有一段差距。

目前共享在各地引起的爭議有兩種,第一如 Uber,在於直接挑戰各地政府對於出租車的規定;第二如共享單車,在於企業出租腳踏車大量佔有公眾資源(ex,違停佔用機車格、人行道),造成社會整體效能下降,引來政府管制。
共享單車的爭議其實不算太複雜,本來基於社會最大福祉,維護公有資源的使用量,政府就會對市場上的交易有所管制(我知道我寫最大福祉會有點爭議…不過我們姑且相信是這樣,才能討論下去),例如路邊停車格的設計,就是限制路邊(公共空間)的使用,以價制量,並對不守規矩的人拖吊,才不會變成馬路上都停滿車子;另外像是客運路線,政府也會介入,管制單一路線由哪些業者經營,以免熱門路線被大客車佔滿,偏遠路線卻無業者經營。

資源愈是珍稀,管制的範圍就會愈廣,由政府單一節點介入管制的機會也愈高;Obike 的爭議,充其量只是它把原先公眾免費的資源,使用到盡乎枯竭,引來政府介入罷了。

當然,這裡面牽扯的,又有更大程度的問題,像是政府規範的界限,政府究竟要無所不包還是儘量退縮?以 obike 的案例來看,這條界限是浮動的,取決於資源是否已經稀有到,需要政府介入分配,以免造成社會整體利益減損。
裡面牽扯的,包括我一直很好奇的,如日本的大手私鐵(ex. 阪急電鐵),它們在城市內建設地下化車站的時候,究竟是如何進行公眾相關的工作,例如土地取得、出口用地取得、交通維持等,這類具有稀有性和排他性的公眾資源使用。
我不認為一般的私人公司有這樣的權利進行這樣等級的資源分配,但若是由私鐵請求政府協助進行,豈不是政府直接圖利廠商?在市區內鐵道這種高度珍稀資源,難不成竟然是私鐵先搶先贏,建成之後完全排除了其他私鐵競爭的能力?想起來多少覺得有點怪怪的。

總之,針對共享經濟,我認為這是科技對社會又一衝擊,每每都在挑戰社會的接受度和容忍度,以及政府畫定管制界限的位置,除了完全禁止和完全開放之外,我們會需要更多的民眾討論,動態的進行實驗,我想是比較有效的解決方式。

2017年8月25日 星期五

使用rust closure實作fizzbuzz

之前用Rust 重寫Understanding Computation 裡面的ruby code,目前從 github 上來看,我的 Rust code 應該是僅次於原作者的 code,完成度最高的一個版本。
從去年五月,把大部分的 code 完成以來,唯一一個沒寫的章節:chapter 6 的 fizzbuzz,最近終於實作出來了\weee/。

本來我是用了比較直接的方法,也就是把closure 用function 來實作,使用generic 的方式來處理參數,例如在數字的部分,我們就要接受一個函式跟一個參數,這個函式要吃一個參數然後吐一個參數……。
例如我那時候實作的正整數的部分:
fn ZERO<F, T>(p: F, x: T) -> T where F: Fn(T) -> T { x }
fn ONE<F, T>(p: F, x: T) -> T where F: Fn(T) -> T { p(x) }
fn TWO<F, T>(p: F, x: T) -> T where F: Fn(T) -> T { p(p(x)) }
fn THREE<F, T>(p: F, x: T) -> T where F: Fn(T) -> T { p(p(p(x))) }
fn FIVE<F, T>(p: F, x: T) -> T where F: Fn(T) -> T { p(p(p(p(p(x))))) }

這個寫法的問題是啥?問題在於…我必須手動處理所有的型別,這在只有數字、布林的時候還容易處理,等到型別一複雜,這種函式宣告根本寫不出來,然後編譯器噴你一臉錯誤,最終完成的也只有number 跟 boolean,甚至連下一段的 is_zero 都實作不出來,程式碼我保留在 ch6-fizzbuzz 的分枝裡。

最近有天心血來潮,把我的 Rust code 實作成果貼到 rust forum:
https://users.rust-lang.org/t/computation-book-example-code-implemented-in-rust/12403
在討論串的下面有一位 jonh 回了我,他的辦法挺聰明的,實作的方式也比較符合這個 project 的要求,首先呢,我們不要處理這麼多型別的問題,把所有的型別都收到一個enum 之下:
pub enum Pol {
    C(Rc<Fn(Rp) -> Rp>),
    I(i32),
    B(bool),
}
pub type Rp = Rc<Pol>;
macro_rules! r {
    ($cl:expr) => {Rc::new(Pol::C(Rc::new($cl)))}
}

impl Pol {
    pub fn call(&self, x: Rp) -> Rp {
        match self {
            &Pol::C(ref c) => c(x),
            _ => panic!(),
        }
    }
}

型別 Rp 是用 rust 的 reference count pointer (Rc) 包裝這個 Pol 的型別,Pol::C 則是包裝一個 Rc 包裝的函式,該函式會吃一個Rp,吐一個Rp,等於是封裝了一個 lambda 函式。
另外我們利用自定義的 macro,讓產生這類封裝的 lambda 函式更容易,最後我們定義呼叫的 call 函式,它會把 Pol::C 裡的函式取出來,用 c 取用參數 x 執行。
這樣,就完成了函數的基本型態。
接著我們就能跟著這本書,一步步打造 fizzbuzz 的程式碼,例如上面提到的正整數的部分:
let zero  = r!(|_p| r!(|x| x));
let one   = r!(|p: Rp| r!(move |x| p.call(x)));
let two   = r!(|p: Rp| r!(move |x| p.call(p.call(x))));
let three = r!(|p: Rp| r!(move |x| p.call(p.call(p.call(x)))));
let five  = r!(|p: Rp| r!(move |x| p.call(p.call(p.call(p.call(p.call(x)))))));

這樣寫的問題是,我必須把所有的 closure 定義寫在 main 函式裡,因為 rust 不允許以 use 的方式,引入定義在別的檔案的 closure,以致最後 main.rs 高達 600 多行。
第二個問題是由於Rust 的所有權特性,在定義每個 closure 的時候,會需要不斷的 clone,例如 multiply 的 closure,需要用到 add 還有 zero,所以我們就要一路把 add 跟 zero clone 下去,寫到複雜一點的closure 例如 divide,需要使用 if, is_less_than, increment, subtract, zero,一個closure 的定義橫跨40 行,這寫法我覺得真的不行,不過一時之間真的找不到更好的寫法。
// multiply
// |m| { |n| { n(add(m))(zero) } }
let multiply = {
  let add = add.clone();
  let zero = zero.clone();
  r!(move |m: Rp| {
    let add = add.clone();
    let zero = zero.clone();
    r!(move |n: Rp| {
      n.call(add.call(m.clone())).call(zero.clone())
    })
  })
};
最後,我沒辦法把 Rp 這個函式印出來,像書裡面印出橫跨數頁,狀觀的lambda函式,這個問題也暫時無解。

最後的成果,完成的 fizzbuzz 所下所示:
let solution = {
  map.call(range.call(one.clone()).call(hundred.clone()))
   .call(r!(move |n:Rp| {
     _if.call(is_zero.call(module.call(n.clone()).call(fifteen.clone())))
        .call(fizzbuzz.clone())
        .call(
          _if.call(is_zero.call(module.call(n.clone()).call(three.clone())))
             .call(fizz.clone())
             .call(
               _if.call(is_zero.call(module.call(n.clone()).call(five.clone())))
                  .call(buzz.clone())
                  .call(to_digits.call(n.clone()))
             )
        )
   }))
};

執行起來慢的要死,fizzbuzz 1-100 費時 51s ,如果真的用 rust 寫,根本不用1 ms 好不好。
當然了,最終能用 rust 把這篇奇文給實作出來,還是覺得滿有趣的,中途也曾出現過,因為一個括號括錯地方,瞬間讓 multiply 變成 power 3*5 = 243,WTF!我至今還參不透,究竟為什麼括號括錯就會讓 multiply 瞬間升一級變 power OAO
我的程式碼都收到master branch 下面,可以參考 github連結,體會一下 functional programming 的奧妙之處XD
這篇文其實根本是「重新發明輪子的極致」,不止是演算法,我們要把整個整數系統、真偽值什麼的,都重新打造一遍,有一種我們先來種顆樹,長出來之後砍下來變木材,作成工具台之後開始打造輪子,感情我不是在寫 fizzbuzz,而是在玩 minecraft 呀(X。

2017年7月29日 星期六

設定 ssh讓人生簡單一些

ssh 是工作上的重要工具之一,平時要連進其他電腦、傳送檔案、甚至是上 ptt 都可以用 ssh 達成,這篇文整理一些 ssh 的設定,可以讓 ssh 的使用更簡單一些。

第一個是免打密碼的設定,這個如果有用 github 的話一定很清楚,簡單來說我們可以把電腦的公開金鑰放一份到遠端,登入的時候 ssh 就會不問密碼自動驗證。
步驟如下:
在家目錄下的 .ssh 目錄中,鍵入 ssh-keygen 指令,預設是使用 rsa,如果覺得 rsa 不夠安全可能會被心算解開(誤,可以用 -t dsa | ecdsa | ed25519 | rsa 選擇要用哪種公開金鑰加密法,用 -b 來選擇生成的金鑰長度(不過我下了 ssh-keygen -b 16384 然後它金鑰生不出來…)。
金鑰生成後,會詢問檔案要存在哪,預設就是 .ssh 資料夾;另外會問 pass phrase,我們都用上 public key 就是不想打密碼,除非有安全性考量否則留空即可。
Enter file in which to save the key (/home/garbage/.ssh/id_rsa): /tmp/id_rsa
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
會產生兩個檔案,id_[algorithm] id_[algorithm].pub,從檔名看 pub 自然是公開金鑰了。
接著將 .pub 檔案的內容,複製到伺服器上 .ssh/authorized_keys 裡面,如此一來 ssh 就能免密碼登入了

詳細請參考
https://blog.gtwang.org/linux/linux-ssh-public-key-authentication/

第二個是設定 ssh config,這有點像 ssh 專用的 /etc/hosts,可以幫常連的機器設定別名,甚至是連線時要用什麼動作,以下來看看:
首先,假設我們要連線一台電腦,remotemachine.com 或者是 ip,帳號名稱是 yolo,開的 port 為 9453,要連線要打入這樣的指令:
ssh yolo@remotemachine.com -p 9453
當然如果照這篇文章安裝了 fzf ,某種程度上能大幅緩解連一台機器要打很多字的問題--連機器的指令不會變,用fzf 找出來就是了,但每次都用 fzf 來找還是會花一些時間,這時可以改用 ssh config
在家目錄的 ~/.ssh/config 檔案中,加上以下內容:
Host MyMachine
 HostName remotemachine.com
 User yolo
 Port 9453
ssh MyMachine 就會直接連上 yolo@remotemachine.com -p 9453
這些規則不是寫死的,ssh 的設定優先次序是命令列、.ssh/config、/etc/ssh/ssh_config,設定後照樣可以用 another@MyMachine 來使用其他使用者登入,或者用 -p 來改變連線的埠。
設定檔就是許多 Host 為開頭的區塊,內容為針對該 Host 的設定,上面展示的就是設定預設主機、使用者跟埠,還有許多其他設定可用,像是認證檔的位置、選擇加密方式,詳細可以看 man ssh_config

下面是我查到 ssh config 的契機,平常工作的地方開了一個新的工作室,但平常測試的主機A放在工作室A,兩個工作室的網段不一樣,沒辦法透過 ip 直接連線;為了連線所以在 router 上面鑽一個洞,開一個 ip 會直接進到工作室A的另一台主機B,到主機B就進到內網,可以直連測試主機A。
如果要打指令,大概會長得像下面這樣,等於是透過machineB ,開一個 pseudo-tty,再執行 ssh進到machineA:
ssh -t userB@machineB.ip -p 9453 ssh userA@machineA.ip
要簡單一點找到這個 stackoverflow,關鍵字是ssh proxy,在 .ssh/config 裡面加上這些設定
Host machineB
  Hostname machineB.ip
  User userB

Host machineA
  User userA
  ProxyCommand ssh -q machineB nc -q0 machineA.ip 22
ProxyCommand 指定要連到這台 Host 時要下的指令為何,這裡會用 ssh quiet mode ,再執行 netcat 接入machineA,這樣只要用 ssh machineA 就能完全上述工作啦
當然這樣每次連線都要打兩次密碼,不想打密碼,照上面在機器上放入自己的公鑰即可。

2017年7月3日 星期一

使用 git-svn 和 svn 遠端協同開發

最近因為跟人協作,共同開了一個版本控制資料夾。
對方使用的版本控制是 svn ,而我則是用 git,重新學 svn 實在太麻煩了,有沒有一個好的解決方案呢?經過強者我同學 AZ 大神跟 qcl 大神的推薦,決定使用 git-svn 來解決。

git-svn 是 git 提供的一個…橋接工具?可以在遠端保持 svn 的狀態,本地則用 git 的管理,享受 git 那些branch 開很大開不用錢,git stash之類,種種我們再熟悉不過的使用方式;另外用了 git 也不用每次都跟遠端目錄同步,可以在自己家裡亂搞,最後有網路時再一次同步。
畢竟在 git 出世前,svn 才是世界上版本控制的霸主,有不少早期知名的 project ,例如LLVM, apache software;用了 git svn ,不需重新熟習 svn 也能用 git 參與這些 project 的開發。

第一步,在拉下 svn repository 的時候,直接使用 git svn 的指令,所有跟 svn 相關的指令都是 git svn xxx:
git svn clone http://SERVER/svn/trunk/ TARGET_DIR
這樣就會把整個svn給抓下來,它同等於執行 git svn init 跟 git svn fetch。
要注意的是因為 git 設計的邏輯就是「所有的機器裡面都有完全一樣的內容」,所以 git svn clone 的時候,它會把遠端的內容逐個載下來,如果遠端 svn 很大的話,這個動作可能會花上非常久的時間。
抓下來的 repository會產生一個叫 git-svn 的 remote ,這個 remote 只有用 git svn 的時候會動到;要注意一點,因為 svn 只能維持一條線性的歷史,同時也沒辦法修改歷史,所以在使用 git-svn 的repository 裡面,不要和其他的 git 遠端同時使用,保持所有使用者都用一個 svn 遠端,git svn 設計上也假定你只有一個遠端。

再來我們就能做些修改,一樣就是git add, git commit,這時提交的內容只會在本地中,可以用:
git svn dcommit
把內容送到 svn 遠端去。
git 在推送到遠端 svn 的時候,會把一個一個 commit 取出,並提交到 svn,然後最重要的,它會依照svn 的提交結果,重新在 git repository 裡面 commit 這些結果,整個 dcommit 的結果,最終效果更像是 git rebase,這跟一般的 git push 完全不同。
commit aea3964417e62759dadf9e1769d927623e0f5a1b
Author: yodalee <garbage@mail.com>
Date:   Fri Jun 30 16:31:30 2017 +0800

    add debug message to every function call

commit 2531676d1d1b81f898e9965c0e46f28e92e02c82
Author: yoda <yoda@59464745-af19-4556-b8ec-ef3a2794439b>
Date:   Fri Jun 30 08:00:23 2017 +0000

    fix description in sensor function

    git-svn-id: http://SERVER/svn/trunk@2345 59464745-af19-4556-b8ec-ef3a2794439b
上面的 git log ,包含一個已經推到遠端的 commit 跟一個還未推送的 commit,推送到 svn 上的 commit 會出現 git-svn-id 的遠端資訊,同時它的作者資訊跟雜湊值也會變化,這也是為何不建議同一個 repository 中同時使用 git跟svn的遠端,git-svn 修改雜湊值會讓 git 遠端天下大亂。
就算要有 SVN 跟 git 兩個遠端也要先向svn dcommit ,得到最終雜湊值後,再推送到 git 遠端上。

svn 身為版本控制,也是允許其他人共同協作,只要有協作就會有衝突要解決,如果發生衝突,svn dcommit 會無法推送到遠端。
為了解決該問題,可以執行 git svn rebase ,它和 git pull 很像,首先它會用 git svn fetch ,把 svn 遠端上的內容拉下來,沿著 git-svn 往前長,之後再用 git rebase ,把現在 git HEAD 指向的目標,rebase 到 git-svn 上。
如果沒有更新的內容,在 git svn rebase 時會看到:
Current branch master is up to date.
此時就能放心進行 git svn dcommit

這裡會牽涉到一些 git 跟 svn 設計不同的地方,在 git 裡面,假設 remote/master 跟本地的 master 有所不同,在 push master 的時候即會發生衝突,git 會要求你解決衝突後才能 push。
svn 在這點上,只有檔案有所衝突的時候才會要求,所以當遠端修改 A 檔案,本地修改 B 檔案,在 dcommit 的時候是完全沒有問題--只是遠端專案會進到一個 A, B 檔案都修改過,而本地檔案卻沒看到 A 檔被修改的狀態。
直接引用 git 文件的話:「如果做出的修改無法相容但沒有產生衝突,則可能造成一些很難確診的難題。」所以,誠心建議還是在 dcommit 前都 svn rebase 一下,確保跟遠端保持隨時同步。

其實有了 dcommit 跟 rebase,大概也就差不多了,有關 svn branch 的部分我就不太想看了,畢竟 git branch 比較強大;唯一要注意的,大概就是要送到 svn 伺服器之前,儘量用 rebase ,把 git 的各 branch 收整成一條線性,再進行 dcommit 。另外有個小技巧是,git svn dcommit/rebase 在操作的時候不允許任何 uncommit 的內容,所以在 svn 操作的前後,可以利用 git stash push/pop ,把未commit 的內容塞進stash,svn 操作結束後再取出來。

參考資料:
Git 與 Subversion

2017年6月17日 星期六

Nand2Tetris 教學投影片與講解影片

故事是這樣子的,5 月的時候我把 Nand2Tetris 這門課給修完了。

剛好,我們同好們辦的 Code& Beer 需要題目,想說都修過了就來講(ㄔㄨㄢˊ)解(ㄐㄧㄠˋ)個 Nand2Tetris 吧,花了點時間整理了投影片,也要感謝在Yahoo 台灣大殺四方驚動萬教每月豪領100K的人生溫拿勝利組強者我同學 qcl 大神,幫我準備場地跟辦活動:

投影片slide share 連結:
https://www.slideshare.net/youtang5/introduction-to-nand2-tetris

錄影使用的是 Facebook 直播,為了把影片載下來花了點功夫,後來找到答案如下:
播放影片之後,按右鍵有一個「顯示影片網址」,將網址的 www 改成 m ,使用 mobile 模式看影片,這時就可以直接右鍵另存影片了:
http://www.green-umbrella.biz/2016/03/how-to-download-facebook-live-stream-videos-into-mp4-files/

當日後面接著 typescript 的活動,這是用 ffmpeg 剪輯過才上傳 Youtube 的:

講了一個半小時,很多地方還是講不清楚,畢竟本來的課程是兩門 coursera,各 6 週,每週的影片時間都是 2 小時以上,這已經是超濃縮版本了,如果大家有興趣還是可以自己修修看,會學到東西的。
不過我想投影片跟錄影完,也算是個總結,我跟 Nand2Tetris 的故事就到這裡了吧,畢竟這也是基礎課程,熟悉了就要往其他更困難的地方前進了。

2017年6月14日 星期三

國學常識大補帖

故事是這樣子的,大概在去年9月的時候,有一位非常喜歡批評人的教授批評大家都沒國際觀,還弄了一個<國際觀檢測網>,那時我把它們的題目都抓下來,寫了個國際觀大補帖,文章在此:
http://yodalee.blogspot.tw/2016/09/global.html

最近同一位非常喜歡生氣又總是對著那些沒有錯的人生氣的教授又生氣了,稍微瀏覽的一下相關的頁面之後,竟然發現除了<國際觀檢測網>之外,還有另外一個<國學常識檢測網>,網址在此:
http://doc.boyo.org.tw/sinology/

同樣進去有十題,看了看題目覺得哇塞這真是太狂阿!有文學有歷史有地理,把這些全部都學起來,競爭力肯定更加8.7 dB,這麼珍貴的題目不出個大補帖全部背下來怎麼可以!台灣年輕人都不學國學是國家重大危機呀!(雖然題目從簡答題變成2選1選擇題,難度大幅下降OAO)
受到傳說中在金門島上大殺四方的鍾誠教授的感召,我決定也來堅守<一個python政策>,也就是「世界上只有一個Python,Python 2 是Python 3 傳統不可分割的分枝,Python 3是目前 Python 唯一正統實作」。什麼你說 Python 3 比Python 2 晚發佈?哎呀晚成立都取代早成立的,這種事很正常啦。

從 python 2 轉換到 python 3不算太難,之前轉換時有個很大的障礙是,處理 html 的套件 lxml 還沒搬到 python 3上,這次發現 lxml 也轉換完成,剩下一些要調的就是 urllib,把 request 獨立就行了,同樣的 code 在 python 3 的實作大概像這樣:
import urllib.request

req = urllib.request.Request(TARGET)
req.add_header("Pragma", "no-cache")
response = urllib.request.build_opener().open(req)

另外就是一些 dict 介面上的變化,還有因為選擇題的關係,在取出的 tag 裡面還有 tag ,因此把 text 換成 text_content,小修一下就能動了。
同樣是開起來一直跑一直跑,出來的檔案有 1200 多行,560 題

原始碼:https://github.com/yodalee/globalizaion
大補帖:https://github.com/yodalee/globalizaion/blob/master/sinology

同樣的,我是不樂見有人真的把這個拿來背啦(同樣…應該不會有人這麼蠢吧…應該啦……)

發佈了這本國學常識大補帖之後,跟國際觀大補帖一樣,我同樣收到來自四面八方熱切的使用心得,以下僅節錄幾則:
* 去年學校的畢業典禮,禮堂不知道為什麼很熱,其他同學都忍不住出去乘涼,幸好我手邊有國學常識大補帖可以當扇子,於是只有我一個人聽到台上的演講,我現在覺得我超厲害足以打爆那些沒聽到的同學。
* 之前我的電腦中了 Wannacry,所有檔案都被加密,只有電腦裡的國學常識大補帖無法被加密,還自動幫我破解了RSA-2048,回復所有的檔案呢
* 自從讀了國學常識大補帖,我現在看到AES 256加密的一條明文跟一條密文,手指滑過就能直接把它的 key 寫出來,手指識字不算什麼什麼,靠著國學常識大補帖,我還練成了「手指識key」
* 之前我超級不喜歡吃香菜,自從讀了國學常識大補帖,就算是香菜蛋糕也能輕鬆下嚥,每位不喜歡吃香菜的人都該讀這本。
* 本來我迷上了<動物朋友>無法自拔,後來經過朋友轉介得知了國學常識大補帖,發現國學遠比動畫博大精深,終於戒了毒癮,たーのしー

算了我不嘴砲了,該工作了

2017年6月12日 星期一

Minecraft 計時器教學影片

最近突然想來錄影一下,很久之前蓋好的 Minecraft 時鐘的解說,來講解裡面核心元件計時器的設計。
那時是第一次在這裡發 minecraft 文:使用差動雙投擲計時器應用於時鐘製作之研製

首先在錄影程式上就遇到問題了,本來我用的是 Google Hangout 來錄影,錄了兩部 word 的教學影片;但在 Minecraft 或遊戲這樣高 frame rate 的應用上就不行了,lag 到天荒地老QQ,試了另外兩款 Kazam 跟 gtk-recordmydesktop ,同樣都無法解決錄影 lag 的問題。
最後選用的錄影程式是 obs-studio,最大的賣點是開源,無論 windows 或 Linux 下都能流暢使用,我是使用 Archlinux 包好的package obs-studio,當然要的話也可以從 source 自己編:
https://github.com/jp9000/obs-studio

另外不知道是不是 Minecraft 的問題,在Windows 下用”錄影視窗”錄 minecraft 的內容,都會錄不到影像,一定要用全螢幕去錄;Linux 上就沒這個問題。

成品如下:中、英文各錄一版,不過我好奇沒有宣傳的話,真的有人會看英文版嗎OwO

中文版解說:


英文版解說:

2017年5月27日 星期六

使用 doxygen 產生程式文件

這應該是個過時的題目,相關的文件已經滿天飛了,不過最近程式寫得肥了,又是和其他人的合作項目,總不能老是這樣打開 Office Word 寫文件(掩面),然後程式跟文件老是不同步,還是把註解改好用 doxygen 產生文件省事些,順帶得就寫個文章記錄一下。
doxygen 是一套文件產生程式,會自動 parse 原始碼、標頭檔等,搭配設定的模版,自動產生不同種類的文件,如 manpage, html , latex…。
要用 doxygen 第一步是先安裝 doxygen,這在 unix 下配上套件管理員應該跟喝水一樣簡單,略過不提。

doxygen -g <config-file>
產生設定檔,預設的名字會是 Doxyfile

下一步要編輯設定檔,我比較過預設設定,大概要改下面這些地方:

  • PROJECT_NAME:當然要改成自己的名字
  • OUTPUT_DIRECTORY:我設定在 doc 資料夾,並且把 doc 資料夾加到 .git 中
  • OUTPUT_LANGUAGE:設定語言
  • EXTRACT_ALL:設成 YES,這樣才會解析所有的檔案,否則必須要用 \file 或 @file 定義過的檔案才會被解析,否則它只會解析 .h 檔
  • INPUT:用空白分隔的輸入檔案或資料夾,我是 src 資料夾
  • FILE_PATTERNS:設定要分析的檔案,這裡我只保留 .c 跟 .h
  • EXCLUDE:把不需要的資料夾剔掉,因為我有一個測試的 test 資料夾,所以把它加上去
  • GENERATE_*:設定要輸出的格式,我只選擇輸出 html ,設定 GENERATE_HTML 是 YES

為了在程式中加上更多資訊,可以在程式碼裡面為函式寫註解,我是使用下列的型式:
/*!
 *
 */
另外搭配以下幾個註解命令:
\brief 可以提供一行描述,簡短敘述這個函式的功能
\see 關鍵字,可以產生連結跳轉去其他的內容
\param 參數描述,有符合原始碼內的參數名稱,在排版上會自動上色並縮排
\return 對回傳值的描述
在 \brief 之後空一行,其它的內容則會歸為長敘述中,如果沒空行的話這些內容都會被濃縮成一行歸在 brief 裡面,所以,一個完整的函式註解如下:
/*!
 *
 * \brief dest = src xor dest
 *
 * do xor on src buffer and dest buffer, store result in dest buffer
 * \param src, dest buffer to xor
 * \param len buffer size both buffer should have enough space for len
 * \return none
 *
 */
int32_t xor2(
  uint8_t *src,
  uint8_t *dest,
  uint32_t len)

接著就可以下
doxygen config_file
產生文件,文件會在 doc/html 中
話說新一代的程式語言好像都自帶文件產生器,如 golang 的 godoc,rust 也有 rustdoc,也許 doxygen 這樣的東西,未來也用不太到了?

Doxygen Manual:
http://www.stack.nl/~dimitri/doxygen/manual/index.html

2017年5月16日 星期二

如何調整 virtualbox 虛擬硬碟檔案的大小

故事是這樣子的,一直以來我都是用 Archlinux 作為我的作業系統,因為在碩士班要跑的模擬用的是 windows 版的ADS,做投影片要用到 M$ Office,於是就裝了一個 Virtualbox ,裡面跑 Win 7。
最近wannacry 勒索軟體肆虐,我才驚覺原來我 virtual box 裡的 win 7 已經超級久沒有更新了,結果是卡到 win 7 的一個 update bug,一更新它就卡在check update 上出不來了,後面搜尋了一段時間,先手動裝了幾個 update 才開始更新,這部分用<windows 7 check update stuck>當關鍵字還不少搜尋結果。
因為累積的一籮筐的更新,要下載的檔案大小超過1 GB,然後…嗯…我的 virtualbox 劃給 windows 的磁碟就滿了(._.),幸好我用的是vdi格式可以動態調整磁碟大小,之前就有調整過一次,這次做個筆記方便下次查找。

https://forums.virtualbox.org/viewtopic.php?f=35&t=50661
開頭就有說了,一定要是 vdi 的虛擬硬碟檔案,首先要先備份一下 vdi 檔,以免調整用量的時候出錯,把整個虛擬磁碟毀了,備份完之後,使用下面的指令,vdi 檔的路徑建議用絕對路徑,size 的單位是 MB,我多劃 5 GB 的空間給它,總共是 40 GB。
vboxmanage modifyhd /media/datadisk/win7.vdi --resize 40960
要注意這個指令似乎只能調大不能調小,所以不要不小心劃太多。

https://www.lifewire.com/how-to-open-disk-management-2626080
vdi 檔案加大之後,只是從 guest 那邊看到的磁碟變大,windows 內還沒有對應的調整,這裡要使用 windows 的 disk management:選控制台->系統及安全性->系統管理工具下有一個建立及格式化硬碟分割,可以把它想成 windows 版的 gparted。
打開之後的畫面大概像這樣,這是已經擴大分割之後的畫面,剛擴大完應該會在 C碟的後面看到另一個未使用的分區。
這時只需要在 C 碟上右鍵,選擇延伸磁碟區,把新加的空間劃給它即可,如此一來就完成了擴大磁碟的工作了。

話說 35 GB 都不夠用,win 7真的肥肥。
更新又這麼久,真的是珍惜生命,遠離 windows。

2017年5月1日 星期一

NAND2Tetris Part2

三月時看到coursera 上,Nand2Teris 第二部分終於開課了,同樣給它選修下去;最近剛把最後的 project 寫完,完成這門課程。
課程網址:
https://www.coursera.org/learn/build-a-computer
另外,這門結束之後,5/8 立刻有開下一個 session,有興趣的大家趕快 Enroll ,一起把電腦給看透透吧。

Nand2Tetris 2 的影片長度明顯比 season 1 長得多,光第一週的影片,要介紹Virtual Machine 概念,又要講解整個 memory layout, pointer access 的概念,第一週影片長達200 分鐘,還不算上 week 0 複習 Nand2Tetris 1 Machine Language 的部分。
另外 part 2 畢竟進到軟體的部分,作業都很吃軟體能力,像是實作 VMTranslator,編譯器,寫高階語言,也不像第一部分,有提供手爆作業給不會寫程式的人,如果沒有基本程式能力選修起來會有些難度。
作業我本來想延續上一個 part 1 都用rust 寫作業,後來…還是偷懶改用Python 寫了(yay,我魯我廢我不會寫 rust QQQQ。

讓我們帶過七週的課程內容:

第一週:

介紹整體 Nand2Tetris stack machine 的架構,整個 Nand2Tetris 都是執行在這種stack machine 上面,之所以用 stack machine 應該是因為相較 register machine,stack machine 還是好做一些,Memory 設定為:
0-4: SP, LCL, ARG, THIS, THAT
5-12: temp
13-15: general register
16-255: static variable
256: stack
2048: heap (以下暫時不用管)
16384: Screen
24576: Keyboard

在 part 1 的最後,我們可以把 assembly 像是 D=A 轉成機械碼 1101...,VM stack machine 程式碼比 assembly 再高階一些,我們可以直接寫stack 操作,例如 push ARG 1,把 Argument 1 推到 stack 的頂端,這樣高階的程式碼讓我們可以更簡單的實作複雜的行為。

作業是VMTranslator,也就是把剛才那些 push ARG 1 轉成一連串的 D=A, D=M 的assembly,寫作業時發現這份文件非常好用:
http://www.dragonwins.com/domains/getteched/csm/CSCI410/references/hack.htm

稍微卡的地方:
pop local 2 這條指令,因為我要先把 local 2 的位址算出來,這樣就需要 D, M, A 三個register (@2, D=A, @LCL, A=M, D=A+D),然後要取SP 的位址也需要三個 register (@SP, A=M, D=M),這樣就沒有地方可以存 pop 出來的東西了
解法是要用到 13-15 的 general purpose register,算出 local 2 位址之後,先塞進 @R13 裡面,取出stack top 放到D 之後就能直接用 @R13, A=M, M=D 將資料塞進 local 2 裡了。

對應課程:我其實不確定,編譯器一般不會是編譯成虛擬機 bytecode ,都是直接變成 bitcode;如果說是stack machine 的觀念的話,那就是虛擬機器了。

第二週:
VM 裡的 branching, function call
branch 很單純,一個影片就講完了;大部分的內容都在講 Function Call,這部分最複雜的也就是 Function call 跟 Return 的calling convention,作業也就實作這兩個功能:
Function Call 大致的內容如下:LCL (local) 對應一般機器上的 base pointer,SP 則是 stack pointer:

呼叫函式的部分:
Push argument
儲存return address, LCL, ARG, THIS, THAT 到 stack 上面
將 ARG 移動到stack 上存參數的位置
將 LCL 到stack 頂端
Jump
寫入Return label

函式本體的部分:
執行Push stack,留給 local 變數空間
函式本體

Return 部分:
將 LCL 儲存到 R13 (general purpose register) 中
將 Return address (LCL - 5) 儲存在 R14 中
複製 stack 頂端的 return value 到 ARG,要注意沒有參數的話,ARG 指向的位置就是 LCL-5 Return address,所以上面要先把 Return address 備份,不然沒有參數的話會被 return value 蓋掉。
將 SP 移到 ARG 的位置 (pop stack)
恢復 LCL, ARG, THIS, THAT
跳到 R14 的 return address

如此就完成了函式的呼叫與返回,有一點要注意的事,講義的部分沒有講很清楚:這週的作業有要寫bootstrap 的部分,他只寫要做到 SP=256; Call Sys.init,其實在Call Sys.init 的部分,也應該要把bootstrap 的 LCL, ARG, THIS, THAT 存到 stack 中,雖然 Sys.init 永遠不會 return,但在實作上還是要求這麼做。
另外我在寫的過程中,如上所述,函式return 時試著去調換 return value 寫入ARG 跟取出return address 的部分,結果就把 return address 蓋掉導致 \explosion/ 了,正好驗證那句:「你以為你會寫程式,假的!叫你用assembly 寫一個Fibonacci 都寫不出來」

對應課程:這周的對應課程我不甚肯定,calling convention 也許是作業系統,也許是編譯器的 code generation 吧,實際可能分散在下面幾堂課裡:
計算機結構、作業系統、計算機組織與組合語言、系統程式。

第三週:
Jack programming language

就是介紹 Jack 的設計,syntax, data struct 之類的怎麼設計的,因為Jack 比起什麼C++/Java 簡單非常非常多,說真的只要學過任何一種 programming language 的,這周應該都可以跳過去不用看。
對應課程:計算機程式。


第四週:

Jack language 的 parser,內容介紹 tokenizer 還有 parse 的基本原理,作業是實做 tokenizer 跟 parser,把 jack program parse 成一堆 token 然後寫到 xml 檔中,要產生 token 的xml ,還有parser 處理過,加上結構節點像是 WhileExpression 的 xml。
tokenizer 我一樣用 python 實作,regular expression 文件裡面有一節就是如何用 re 的 scanner 來寫一個 tokenizer,整個 jack language 的規則大概就是這樣,寫起來輕鬆寫意:
https://docs.python.org/3.2/library/re.html#writing-a-tokenizer
jackkeyword = ['class', 'constructor', 'method', 'function', 'int',
  'boolean', 'char', 'void', 'var', 'static', 'field', 'let', 'do',
  'if', 'else', 'while', 'return', 'true', 'false', 'null', 'this']
tokenSpec = [
  ('comment', r'//[^\n]*|/\*(\*(?!\/)|[^*])*\*/'),
  ('integerConstant', r'\d+'),
  ('symbol', r'[+\-*\/\&\|\<\>\=\~\(\)\{\}\[\]\.\,\;]'),
  ('identifier',  r'[A-Za-z_][A-Za-z_0-9]*'),
  ('stringConstant', r'\"([^"]*)\"'),
  ('newline', r'\n'),
  ('space', r'[ \t]+'),
]

parser 部分我寫了個class ,對各種狀況寫函式來處理,寫得超級醜,文法檢查和輸出的部分都混在一起了,整個 parser 都是用土砲打造而成,正符合下面這句話:
「我一直以為我會寫程式…假的!連個 jack compiler 都寫不出來。」
那個 code 實在是太醜了,決定還是不要公開…,但相較在網路上找到一些人的實作,連文法檢查都沒有,我是覺得我寫得還是很有誠意啦XD
https://github.com/kmanzana/nand2tetris/tree/master/projects/10

對應課程:無疑問的就是編譯器的上半學期


第五週:
Code Generation

上週實作的編譯器只能輸出 xml ,這周要直接把 jack 轉成 VM code,影片就是一步步教你,當遇到運算符號要怎麼處理,呼叫函式要怎麼處理。它的class 沒有 inheritance 所以…嗯,也不用處理 virtual table 的問題,其實滿簡單的。
我覺得收獲最大的,是學到怎麼處理物件 method 的call,其實也就是把指向物件的記憶體位址塞進函式的第一個參數,然後在進到 method 的時候先把這個參數塞進 THIS,這樣就能用 THIS[n] 來取用物件的 data member 了。
這裡也會知道,上面實作時遇到的 THIS, THAT,其實分別對應物件跟陣列,要用物件的 data member 就用 THIS[n] 去取,陣列的內容則用 THAT[n],這樣只需要把記憶體位址存到 THIS, THAT 裡面,就能取用物件和陣列了。

下面是一些實作的筆記:
Jack language 裡面沒有 header/linker 的概念,編譯的時候是以一個 class 為單位在編譯,所以如果裡面出現呼叫其他class 的函式,例如 do obj.foo(),我們只能把它們當成「合法」的程式來產生 code,就算沒有這個 class 或是函式還是可以編譯
但沒有最後一步將所有 vm file link成一個檔案,而是模擬器個別 load 所有的 vm 檔,自然不會像 C/C++ 的 linker ,在最後階段回報 undefined reference to class name/function name 的錯誤。

因為parser 已經把整個程式碼變成 AST了,code generate的部分,只需要把對應的函式寫好,大抵就會動了;沒實作的地方,我發現與其寫 pass 不如直接 raise NotImplementedError,這樣一編譯遇到沒實作的地方,程式就直接 crash 並指出是哪個地方沒有實作;而無論是 parser 還是 code generation, expr->term->factor 這部分的變化是最多元的,這部分處理完,各個 statement 的部分都可以輕鬆對付。

然後…它的模擬程式其實有點兩光,如果選用 program flow 模擬的話會非常慢,依它的建議要把它選成 No Animation,可是這個模式無法編輯記憶體的內容;正確步驟是:load 程式,編譯記憶體,選成 No Animation 執行。
另外,No animation 下,所有對記憶體的操作,都要按下停止模擬才會反映出來,不知道這點的我一直以為我 code generate 哪裡寫錯了lol。

這次作業會需要 call 到一些 library 提供的函式,請參考官方的文件:
http://www.nand2tetris.org/projects/09/Jack%20OS%20API.pdf
例如,要產生字串的話,要用 String.new, String.appendChar 兩個方法,大致如下:
push constant num_of_character
call String.new 1 // 1 argument
for char in string:
  push constant ord(char)
  call String.appendChar 2 //first is string, second is character
最後的 compiler 總共 1000 行左右…覺得實作超糟,都用了python 怎麼還會寫得這麼肥?

對應課程:編譯器的下半學期


第六週:

作業系統,或者稱 Library 或是 runtime 應該比較恰當,總之就是 jack 中,會用到一些 Keyboard, Screen, Math 之類的函式,在之前都是用預先編譯好的模組來執行,現在則要自己用 jack 實作這些功能。這次的影片也高達 230 分,到最後一個 module 連教授都忍不住大叫:Yeah, the last module~~

理論上是用 jack 這樣的高階語言來寫,以為有高階語言就沒事了嗎,錯!
jack 他X 的有夠難寫,少了一個 do, return 什麼的程式就炸給你看(沒有return 它的函式就會一路執行到下一個函式 lol),因為它沒有 operator precedance 所以該加括號的地方也要加,宣告也無法和賦值寫在一起,我大概照他設計的內容,寫完Math 跟 Memory 之後才比較抓到該怎麼寫。
作業寫起來都滿有挑戰性的,像是 Memory.jack 就要用 linked-list 來實作的記憶體管理(用這破破的語言XD)。

作業不止是在考驗寫 jack ,同時也在驗證上一次寫的 compiler ,測試過程中我找到好幾個 compiler 相關的錯誤,像是函式的參數沒處理好,不該傳入 this 的時候傳入 this,讓我在Output 文字的地方 de 了很久的 bug。
這種錯真的超級麻煩,你不一定知道是高階語言寫錯還是compiler 錯…,有時要追著模擬器一步步執行,才能知道是哪裡錯了。這次真的是體會到:好的編譯器帶你上天堂,壞的編譯器帶你住套房。

對應課程:作業系統、系統程式
有一些些的演算法概念(例如乘法要怎麼實作才會快),但份量極少所以不列上好了。

第七週:
這週只是提點一些 Nand2Tetris 可以加強的部分,像是:
在硬體中實作乘法除法來加速,這樣就不需要 library 的函式支援。
在硬體中實作位元運算(左移右移)
如何自己實作模擬器的 built-in chip

另外也公佈可能會有的 Nand2Tetris part 3,如何真的在 FPGA 上實作這台電腦

--

末語:
大推這門課,課程中 Shimon 講課的速度都滿慢的(我都開 1.3-1.4 倍速在聽課),不會太難聽懂。
第二部分相較之下實在太難,覺得還是稍微有碰過程式再來學會比較好,第一部分就沒這問題(也許可以說:電機系的不用修第一部分,資工系的不用修第二部分XDD)修完整部 Nand2Tetris ,對數位電路到組成電腦都有一些概念,第二部分更是讓你看透程式到底怎麼編譯、執行,這點可能即使是資工本科生都不一定全盤了解。
同時,修這門課對我來說也有一點點復健的作用,從簡單的東西入手給自己一點信心:原來,我也是會寫程式,寫得出一些些可以用的東西呢。

僅引用課程講義最後一節的文字:
In CS, God gave us Nand, everything else was done by humans.
神給了我們反及閘,於是我們打造一台電腦。
另外還有影片中教授引用的這段:
We shall not cease from exploration, and the end of all our exploring will be to arrive where we started and know the place for the first time." - T. S. Eliot
作為這門課的總結。

2017年4月25日 星期二

Effective Modern C++: 42 Specific Ways to Improve Your Use of C++11 and C++14

中譯書名是「Effective Modern C++ 中文版:提昇C++11與C++14技術的42個具體作法」

包括前兩本 effective C++ 跟 more effective C++,其實這已經是作者第三本關於 C++ 的書了,我真的滿好奇作者哪來對 C++ 這樣無比熱情,因為我已經很久很久沒有寫 C++ 了,有關底層的東西大部分還是用C,雖然有時候遇到沒有STL 支援的資料結構還是會哀一下,其他程式大多用Python 來寫;寫最多 C++ 的地方大概是 leetcode XD,想練演算法又想貪圖 C++ STL 提供的好處;大型 C++ 的譔寫經驗好像也沒多少,所以說我到底為什麼要看這本書呢……(思

扯遠了,先拉回來。

總而言之,C++ 在 11 跟 14 加入了許多新的語法和功能,這本 Effective Modern C++ 包含42個C++ 的使用項目,分佈在下面7個主題當中:
  • Deducing Types:C++ 的template 是如何推導型別 
  • auto:何時該用 auto ,何時又會出錯 
  • Moving to Modern C++:C++ 11,14 引入的新的寫法 = deleted, = override, 自動產生的物件方法
  • Smart Pointers:推薦使用smart pointer 代替 raw pointer ,以及 smart pointer 背後的實作 
  • Rvalue References, Move Semantics, and Perfect Forwarding:C++ 右值的 move, forward 的使用。 
  • Lambda Expressions:如何正確使用 C++ lambda 語法 
  • The Concurrency API:C++ 提供的 thread 跟 future 要在哪些情況才能正確使用 
  • Miscellaneous:其他兩個,介紹 emplace 何時要用 copy-by-value 
我是覺得到了 concurrency API 就已經看不太懂,平常大概也不會用到這樣,不然請大家回想一下你上次用 C++ 寫非同步的程式是什麼時候XD,但前面幾章就還看得滿有感覺的。

整體來說,C++ 從開始發跡到 C++11, 14,零零總總經歷各種不同的設計,無論是文法上或執行效率的考量,新增了很多全新的建議寫法或者關鍵字,每個關鍵字的背後可能都是一段故事,就例如存在已久的 typename :
http://feihu.me/blog/2014/the-origin-and-usage-of-typename/

書裡每個項目都包含從C++98或 C++11 開始到C++14最新的設計範例,作者還會告訴你為什麼C++11和C++14要這樣設計這些標準,哪些用法會在造成不預期的結果,或者直接了當的編譯錯誤,我認為,學習 C++ 而不去了解這些故事,等於是錯過一段精彩的設計思辯,非常可惜。

所以,還是回到那些話:學習C/C++的問題並不是學不會什麼生猛功能,而是要在各種實作的方法中學習:「哪些方法是比較<適合>的方法,避免哪些有問題的寫法,比較好的方法和概念是什麼?以及為何如此」

還在感嘆自己不懂C++ 11 跟C++ 14?趕快到書店買本 effective modern C++ 吧

不過告訴你一個悲慘的事實,今年C++又要推出更新的C++17了,希望今年Scott Meyers 也能再出本新書(More effective modern C++ (誤))幫大家指點迷津。

末語只能說:人生很難,可是C++,更難

2017年4月8日 星期六

Makefile header 檔的相依性檢查

Makefile 使用 make depend 進行相依性檢查
在寫Makefile 的時候,一般的規則是這樣的:
Target: Dependency
  Command (Makefile文件是寫 recipe,不過我這邊就寫command)
如果是C 程式,通常也會把 header 檔寫在 dependency 內,否則header 檔改了結果程式沒有重新編譯就奇怪了,如果每次改了header 都要 make -B 也不是辦法。
project 長大的時候,手填 header dependency 變得愈來愈不可行,特別是Makefile 大多是寫 wildcard match,例如這樣把object files 都填入變數 OBJS 之後,直接用代換規則將 .c 編譯成 .o:
SRCS = file1.c file2.c
OBJS = $(SRCS: .c=.o)

target: $(OBJS)
  $(CC) $(LDFLAGS) -o $@ $^

%.o: %.c
  $(CC) -c $(CFLAGS) -o $@ $<
在這裡翻譯了這篇文章,裡面給出一個針對大型專案完整的解決方法,重要的是它有把為何這麼寫的理由講出來:
http://make.mad-scientist.net/papers/advanced-auto-dependency-generation/

最簡單的方法,是在Makefile 裡面加上一個depend 的目標,在這個目標利用一些分析工具,例如makedepend建立 dependency,但這樣等於是把 make depend 跟真正的編譯分開了,缺點有兩個,一是需要使用者下make depend 才會重建 dependency,第二是如果有子資料夾也要依序下 make depend,我們需要更好的方法。

首先我們可以利用 makefile 的include 功能,直接 include 一個含有 dependency 的makefile ,把所有source file 設為 include file 的dependency,這樣只要有source 檔更新,make 時就會自動更新dependency到最新,完全不用使用者介入;但這樣也有缺點,只要有檔案更新就要更新的dependency,我們可以做得更好。

首先是 gnu make 推薦的方法:
https://www.gnu.org/software/make/manual/html_node/Automatic-Prerequisites.html
對每一個原始碼檔,產生一個.d 的相依性資訊
%.d: %.c
  @set -e; \
  rm -f $@; \
  $(CC) -M $(CPPFLAGS) $< > $@.Td; \
  sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.Td > $@; \
  rm -f $@.Td
先開 set -e 設定只要後面的command 出問題就直接結束,然後把 name.d 檔案刪掉;接著使用gcc 的 -M 參數(或者用 -MM ,如果不想要產生的dependency 包含system library的header),會讀入 name.c 之後,產生如下的dependency,寫到 name.d.Td 檔案中。
name.o: name.c aaa.h
之後用sed將上想 gcc -M 產生的內容,加上name.d 這個target:
name.o name.d: name.c aaa.h
對每個.c 檔都產生過 .d 檔之後,就能直接include所有.d 檔案了。
include $(sources: .c=.d)

因為 .d 的dependency 包括對應的 .c 檔,這樣只要.c 檔更新了,makefile 也會重build .d 檔,保持相依資訊在最新,同時只更新需要的 .d ,不會浪費build 的時間。
這個做法有幾個問題,首先是re-invocation 的問題,使用include的問題在於Makefile 本身的設計,因為include 可能帶入新的變數定義,目標等等,一但include 的目標有更新,它就會強制重跑一次本體的Makefile (參考這一篇:http://make.mad-scientist.net/constructed-include-files/ ),這在大型的專案上會帶來可觀的成本;更嚴重的是,如果我們把 .h 檔給刪除或更名,在編譯的時候會出現錯誤:
make: *** No rule to make target 'name.h', needed by 'name.d'. Stop.
這是因為name.d 相依於 .h ,.h 已經不存在而且又沒有產生 .h 的rule,Makefile 就會回傳錯誤;雖然改 .h 的狀況不常見,一但遇到就只能手動刪掉所有 .d 檔重新產生。

為了解決上述兩個問題,首先是Makefile 每次都會重新make 的問題,問題是這樣:如果有個source 更新了要重新編譯,那我們知不知道新的dependency 也沒差--反正它鐵定要重編譯的,該做的是為下一次的編譯產生新的 dependency 資訊;所以我們可以把產生 .d 檔這步,移到編譯 .c 檔的時候,大略如下:
OBJS = $(SRCS: .c=.o)
%.o : %.c
  # 產生 .d 檔的位置
  $(CC) -o $@ $<
include $(SRCS:.c=.d)
第二個問題比較棘手,這次用的招式是:Makefile 中如果有目標但沒有command 或dependency,這個目標又不是個已存在的檔案,那Makefile 無論command有沒有執行,會預設這個目標已經「被更新到最新」,注意它是有更新的,所以相依於這個目標的目標,也會依序更新下去。
http://www.gnu.org/software/make/manual/html_node/Force-Targets.html
例如下面這個例子,因為FORCE 每次執行都會被更新,所以clean 也一定會更新而執行
clean: FORCE
  rm $(objects)
FORCE:
所以這裡用的技巧就是,把那團相依的.h 檔變成無dependency/command 的目標,產生.Td之後,下面我是分成多行的結果,實際上可以寫得緊密一點,所做的工作包括:用 sed 依序移除comment;移除 : 前的target;移除行尾接下一行的反斜線;移除空白行;把行尾變成 : ,這樣本來的dependency 通通變成目標了。
  cp $*.Td $*.d; \
  sed -e 's/#.*//' \
  -e 's/^[^:]*: *//' \
  -e 's/ *\\$$//' \
  -e '/^$$/ d' \
  -e 's/$$/ :/' < $*.Td >> $*.d; \
  rm -f $*.Td
最後的問題是,如果我們移除 .d 檔, .c .h 檔又沒有更新則Makefile 也不會重新產生 .d 檔,因為 .d 檔並不是編譯時相依的target;但 .d 檔又不能是個真的target ,否則又有老問題:include 的時候它會產生 .d 檔,然後整個Makefile 會重跑一次;這裡的解法是把 .d 加到 .o 的dependency ,然後加一個空的target,這樣一來,.d 檔如果存在,因為commands 是空的所以什麼事都不做;如果 .d 被刪除,在make 時 .o 會觸發 .d 的更新,在空的target 更新之後, .o 也跟著更新的時候,一併產生全新的 .d 檔。
%.o: %.c %.d
  command
%.d:
最後是一些針對 dependency file 的處理,完整的Makefile 會長這個樣子:
DEPDIR = .depend
DEPFLAGS = -MT $@ -MMD -MP -MF $(DEPDIR)/$*.Td
POSTCOMPILE = mv -f $(DEPDIR)/$*.Td $(DEPDIR)/$*.d

%.o : %.c $(DEPDIR)/%.d
  $(CC) $(DEPFLAGS) $(CFLAGS) -c $<
  $(POSTCOMPILE)

$(DEPDIR)/%.d: ;
.PRECIOUS: $(DEPDIR)/%.d
include $(patsubst %,$(DEPDIR)/%.d,$(basename $(SRCS)))
第一段定義depend file 的存取地點,然後定義 DEPFLAGS,利用編譯時插入 DEPFLAGS 把編譯跟產生 dependency 一併做完;相關的 flags 都是 -M 開頭,可參考:
https://gcc.gnu.org/onlinedocs/gcc-5.2.0/gcc/Preprocessor-Options.html
  • -MT 定義產生出來的目標名稱,預設是 source 檔的suffix 換成 .o 後綴,並去掉前綴的任何路徑,例如 -MT www 則生成的相依資訊就變成 www: name.h,所以這個選項不一定要加。
  • -MM or -M 要求產生 dependency 資訊
  • -MP 直接幫忙對 header file 產生空的 dependency target (顯然gcc 開發者有注意到 .h 並非目標產生的問題),上面提到那一大串 sed ,如果是用 gcc 加上這個選項就不需要了
  • -MF 設定輸出到的檔案,相當於 redirect >
POSTCOMPILE 則把暫時的dependency file .Td 改名為.d,如果沒有要對 .Td 做什麼,拿掉這行,指定 -MF 直接寫到 .d 檔也是不錯的選擇。

第二段在.o 的dependency 加上 .d,command 編譯 .o 檔,同時gcc 產生.d 檔。
第三段寫入空的 .d 檔目標,以便處理 .d 檔被刪掉的狀況,.precious 讓 .d 檔在Makefile 當掉或被砍掉的時候不會被刪掉;最後 include 產生出來的 .d 檔。

大概的解法就是這樣,其實,如果project 很小的話,根本就不用這麼大費周章,像下面這個解法,直接把所有SRC送給gcc 產生dependency ,寫到 .depend 檔案,然後每次都include .depend,對 99.9%的project 來說應該都足夠了:
http://stackoverflow.com/questions/2394609/makefile-header-dependencies

2017年3月21日 星期二

用Python ctypes 建立與C的介面

故事是這樣子的,最近小弟接觸一項工作,主要是開發一套C 的API,實作大程式底層的介面,以前只有改過別人的介面,這次自己從頭到尾把介面建起來,git repository 提交100多個commit,說實在蠻有成就感的。
寫Project的過程中也發現自己對測試的經驗實在不夠,本來想說該把unit test set 建起來然後做個 regression test,結果unit test 寫一寫最後都變成behavior test 了,啊啊啊啊我根本不會寫測試啊,測試好難QQQQ

寫測試時也發現一個問題,用C 寫測試實在有點痛苦,陸續看了幾個例如 CMocka 的testing framework,實作起來還是挺麻煩的;有些要測試的功能,例如檔案parser,都需要另外找library來支援,而C 的library 卻未必合用,要放入project 的Makefile 系統也很麻煩;C 需要編譯也讓有彈性的測試較難達成。
這時我們的救星:Python 又出現了,是否能利用python上面豐富的模組跟套件,還有簡單易用的 testing framework,可以彈性修改的直譯執行來幫助測試呢?

一開始我看了一下Python C/C++ extension,這是將C的程式包成函式庫,並對它增加一層 Python 呼叫的介面,變成 Python 可以 import 的module。
但是這個方法有點太殺雞用牛刀了,這是要用在python 極需性能的部分,可以寫C 進行加速,而不是單純想 call C function 來測試。
比較好的解法是利用python 的ctypes,可以直接載入編譯好的 shared library,直接操作C 函式。引用參考資料<程式設計遇上小提琴>的話:與其做出pyd來給python使用這種多此一舉的事情,東西就在那裡,dll就在那裡,為何不能直接使用呢?
所以我們的救星:ctypes 登場了。

這篇先來談談 ctypes 的大致用法,下一篇我們來說明一下在 project 中利用到它們的地方。

首先是要引用的對象,使用ctypes 前要先把要引用的project 編成單一的動態函式庫,下面以 libcoffee.so 為例,如此一來,我們可以用下列的方式,將 .so 檔載入python 中:
filepath = os.path.dirname(os.path.abspath(__file__))
libname = "libcoffee.so"
libcoffee = ctypes.cdll.LoadLibrary(os.path.join(filepath, libname))
ctypes 有數種不同的載入方式,差別在於不同的 call convention,cdll 用的是cdecl,windll 跟oledll 則是 stdcall。載入之後就可以直接call 了,有沒有這麼猛(yay)

參數處理:

ctypes 中定義了幾乎所有 C 中出現的基本型別,請自行參考內容表格,None 則會直接對應 C 的NULL:
https://docs.python.org/2/library/ctypes.html#fundamental-data-types
所有產生出來的值都是可變的,可以透過 .value 去修改。
例外是利用Python string 來初始化 c_char_p(),再用 value 修改其值,原本的Python string 也不會被修改,這是因為Python string 本身就是不可修改的。
如果要初始化一塊記憶體,丟進C 函式中修改的話,上面的 c_char_p 就不能使用,要改用creat_string_buffer 來產生一塊記憶體,可以代入字串做初始化,並指定預留的大小,爾後可以用 .value 來取用NULL terminated string,.raw 來取用 memory block。
如果要傳入 reference ,除了 create_string_buffer 產生的資料型態本身就是 pointer 之外,可以用 byref() 函式將資料轉成 pointer。
使用 ctypes 呼叫C 函式,如果參數處理不好,會導致 Python 直接crash,所以這點上要格外小心。

我們可以為函式加一層 argtypes 的保護,任何不是正確參數的型態想進入都會被擋下,例如:
libcoffee.foo.argtypes = [c_int]
# 之前這樣會過,C function 也很高興的call 下去
libcoffee.foo(ctypes.c_float(0))
# 設定之後就會出現錯誤
ctypes.ArgumentError: argument 1: <type 'exceptions.TypeError'>: wrong type
這部分採 strict type check ,甚至比C 本身嚴格,如果設定argtypes 為:
POINTER(c_int)
那用 c_char_p 這種在C 中可以轉型過去的型態也無法被接受。

restype 則是定義 C 函式的 return type,因為我的函式都回傳預設的 c_int ,所以這部分不需要特別設定。比較令我驚豔的是,ctypes 另外有 errcheck 的屬性,這部分同restype 屬性,只是在檢查上比較建議使用errcheck。
errcheck 可以設定為 Python callable (可呼叫物件,無論class 或function ),這個callable 會接受以下參數:
callable(result, func, args)
result 會是從C 函式傳回來的結果;func 為 callable 函式本身;args 則是本來呼叫C 函式時代進去的參數,在這個callable 中我們就能進行進一步的檢查,在必要的時候發出例外事件。
def errcheck(result, func, args):
 if result != 0:
   raise Exception #最好自己定義 exception 別都用預設的
 return 0
libcoffee.errcheck = errcheck

衍生型別:

ctypes 另外提供四種衍生型別 Structure, Union, Array, Pointer 來對應C 的struct, union, array, pointer
每個繼承 Structure 跟Union 的 subclass 都要定義 _filed_,型態為 2-tuples 的 list,2-tuple 定義 field name 跟 field type,型態當然要是 ctypes 的基本型別或是衍生的 Structure, Union, Pointer 。
Struct 的align 跟byte order 請參考:
https://docs.python.org/2/library/ctypes.html#structure-union-alignment-and-byte-order

Array 就簡單多了,直接某個 ctypes 型態加上 * n 就是Array 型態,然後就能如class 般直接初始化:
TenInt = ctypes.c_int * 10
arr = TenInt()

Pointer 就如上面所說,利用 pointer() 將 ctypes 型別直接變成 pointer,它實際上是先呼叫 POINTER(c_int) 產生一個型別,然後代入參數值。
爾後可以用 .contents 來取用內容的副本(注意是副本,每次呼叫 .contents 的回傳值都不一樣)和 C 一樣,pointer 可以用 [n] slice,並且修改 [n] 的內容即會修改原本指向的內容,取用 slice 的時候也要注意out of range 的問題,任何會把C 炸掉的錯誤,通常也都會在執行時把 python 虛擬機炸了。
同樣惡名昭彰的Null pointer dereference:
null_ptr = POINTER(c_int)()
產生NULL pointer,對它 index 也會導致 Python crash。

Callback

ctypes 也可以產生一個 callback function,這裡一樣有兩個函式:CFUNCTYPE 跟 WINFUNCTYPE,分別對應 cdecl 跟 stdcall;以第一個參數為 callback 的return type,其餘參數為 callback 參數。
這部分我這裡沒有用到先跳過,不過下面的連結有示範怎麼用ctypes 寫一個可被 qsort 接受的 callback 函式,真的 sort 一個buffer 給你看:
https://docs.python.org/2/library/ctypes.html#callback-functions

實際案例

上面我們把ctypes 的文件整個看過了,現在我們來看看實際的使用案例。
這裡示範三個 C function,示範用ctypes 接上它們,前情提要一下project 的狀況,因為寫project 的時候消耗了太多咖啡了,所以project 的範例名稱就稱為 coffee,我們會實作下面這幾個函式的介面:
// 由int pointer回傳一個隨機的數字
int coffee_genNum(int *randnum, int numtype);
// 在buf 中填充API version string
int coffee_getAPIVersion (char *buf)
// 對src1, src2 作些處理之後,結果塞回到dest 裡面
int coffee_processBuf (char *src1, char *src2, char *dest, int len)
針對我要測試的 c header,就對它寫一個 class 把該包的函式都包進去,init 的部分先將 shared library 載入:
import ctypes
import os.path

class Coffee(object):
  """libcoffee function wrapper"""

  def __init__(self):
    filepath = os.path.dirname(os.path.abspath(__file__))
    libname = "libcoffeeapi.so"
    self.lib = ctypes.cdll.LoadLibrary(os.path.join(filepath, libname))
所有 header 檔裡面的自訂型別,都能很容易直接寫成 ctypes Structure,舉個例本來有個叫counter 的 Union,比對一下C 跟Python 版本:
C 版本:
typedef union Counter {
  unsigned char counter[16];
  unsigned int counter32[4];
} Counter;
Python ctypes 版本:
class Counter(Union):
  _fileds_ = [
    ("counter", c_uint8 * 16),
    ("counter32", c_uint32 * 4)]
針對 C 裡面的函式,我們把它們寫成各別的 Python 函式對應,同時為了保險起見,每個函式都設定 argstype,這樣在參數錯誤時就會直接丟出 exception;又因為函式都依照回傳非零為錯誤的規則,所以可以對它們設定 errcheck 函式,在return 非零時也會拋出例外事件。
這裡的作法是在class __init__ 裡面把該設定的都寫成一個dict,這樣有必要修改的時候只要改這裡就好了:
def errcheck(result, func, args):
    if result != 0:
        raise Exception
    return 0

# in __init__ function
argstable = [
  (self.lib.coffee_genNum,          [POINTER(c_int), c_int]),
  (self.lib.coffee_getAPIVersion,   [c_char_p]),
  (self.lib.coffee_processBuf,      [c_char_p, c_char_p, c_char_p, c_int])]

  for (foo, larg) in argstable:
    foo.argtypes = larg
    foo.errcheck = errcheck

然後就是實作各函式了,這部分就是苦力,想要測的函式都拉出來,介面的參數我就用Python 的物件,在內部轉成ctypes 的物件然後往下呼叫,所以如getNum 的轉化型式就會長這個樣子,pointer 的參數,可以使用 ctypes 的byref 。
# int coffee_genNum(int *randnum, int numtype);
def genNum(self, numtype):
  _arg = c_int(numtype)
  _ret = c_int(0)
  self.lib.coffee_genNum(byref(_ret), _arg)
  return _ret.value
getAPIVersion 是類似的,這次我們用 create_string_buffer 產生一個 char pointer,然後直接代入函式,就可以用value 取值了。
# int coffee_getAPIVersion (char *buf)
def getAPIVersion(self):
  buf = create_string_buffer(16)
  self.lib.coffee_getAPIVersion(buf)
  return buf.value
最後這個的概念其實是一樣的,我把它寫進來只是要火力展示XD,上面提到的processBuf,實際上可以把它們跟Python unittest 結合在一起,利用python os.urandom來產生完全隨機的string buffer,答案也可以從python 的函式中產生,再用unittest 的assertEqual 來比較buffer 內容:
l = 4096
src1 = create_string_buffer(os.urandom(l-1))
src2 = create_string_buffer(os.urandom(l-1))
dest = create_string_buffer(l)
ans  = generateAns(src1, src2)
self.lib.coffee_processBuf(src1, src2, dest, l)
self.assertEqual(dest.raw, ans)
一個本來在C 裡面要花一堆本事產生的測試函式就完成啦owo
如能將這個class 加以完善,等於要測哪些函式都能拉出來測試,搭配python unittest 更能顯得火力強大。

後記:
趁這個機會把ctype的文件整個看過一遍,覺得Python 的 ctypes 真的滿完整的,完全可以把 C 函式用 ctypes 開一個完整的 Python 介面,然後動態的用 Python 執行,真的是生命苦短,請用python。

參考資料:

https://docs.python.org/2/library/ctypes.html
http://blog.ez2learn.com/2009/03/21/python-evolution-ctypes/

2017年3月18日 星期六

使用"旁通道攻擊"攻擊腳踏車

Bob 和Alice 兩位都是T大的學生,Alice 暗戀Bob 已久,發現Bob 下課在校園間移動,都騎著他那台紅色小腳踏車。Alice 於是心生一計:如果她偷走Bob 的腳踏車,Bob 下課找不到腳踏車的時候,她就能騎著腳踏車出現在Bob 身邊,好心載他一程,以拉近兩人的關係
問題是Bob 的腳踏車用了密碼鎖,想要破解或試出密碼,或找到大剪剪斷塑膠的部分都不是那麼容易的事,那到底該如何解開密碼鎖,偷走腳踏車呢?

這時Alice 靈機一動,想起上週在聽密碼學導論時,教授提到有關旁通道攻擊(Side Channel Attack, SCA)的內容,為何不能利用旁通道攻擊來破解密碼鎖呢?
所謂旁通道攻擊,就是不正攻防禦系統中最強的部分,像是用暴力法直接試密碼鎖10,000 種不同的組合,或者拿大剪直接斷塑膠的部分,前者要試很久,後者有點太明目張膽了;反之旁通道攻擊從每次加解密時流出來的資訊,推敲出金鑰全部或部分,從1996年Kocher那篇 Timing attacks on implementations of Diffie-Hellman發表至今,無論從電源消耗、近場輻射,甚至聲音,旁通道攻擊攻破無數數學上完全的密碼系統,那麼腳踏車呢?

前情提要到此:
理論還滿簡單的,密碼鎖上有4個0-9的數字,上鎖之後要把數字撥亂,其他人才猜不到真正的密碼;可是,撥亂這件事-或稱亂數產生器-卻不一定有被認真對待。
Bob 上鎖時,可能大手一轉就以為鎖好了,很可能4個數字轉盤裡面有2到3個都是一起轉的,或者因為Bob 是右撇子,都用右手向右撥,也因此,只要持續記錄Bob 每次鎖車時轉盤上的數字,就可以推測出密碼的數值
這是Alice 努力不懈每天記下Bob 停腳踏車後密碼鎖的數值:
1752 4852 5941 5652 4869 4830 5941 4841
其實結果還滿明顯的,轉的數值真的跟習慣有關,甚至多次轉出一樣的數字,我光用看的都能看出來ABCD 四個數字間的關係,AB 差4 (B-A),CD差7 (D-C),BC 比較看不出來,但算眾數的話是差8,如果有更多筆資料就能更精準的看出關係,這可能是我習慣上會四個一起轉之後,CD兩位再轉一些,導致BC差值通常不固定。
由上猜測,腳踏車的密碼可能是 0429,以及四個數字一起轉的其他結果:1530, 2641……9318,很不巧,0429真的是Bob 的腳踏車鎖密碼。

試過其他解法,例如假設真正的密碼是ABCD,觀察到的數值是A’B’C’D’,個別數字的距離:X-X’ 應該會相當接近才對,我寫了個小程式去算 0000-9999 取 X-X’ 的標準差,然後算所有測試結果的標準差總合,愈小的愈可能是答案。
後來發現這招沒用,例如密碼0000 被轉成 6688,這樣比起正確的密碼0000,0011這個錯誤解在這個算法下就會勝過0000。

無論如何,記錄了8組數據就能把10,000組密碼組合縮減到10 組,旁通道攻擊果然有用啊。
可惜後來Bob 發現腳踏車不見了,就跑去借公共腳踏車(NT)Ubike,功敗垂成不禁讓Alice 感嘆:偷得走心上人的腳踏車,卻偷不走他的心。

反思:
* 人類其實不是一個很好的亂數產生器,就如上面的例子,轉盤轉的數字看似亂數,其實隱藏了內部規則,就像現在請人在1到100中選一個數,大家通常認為人腦選的數字是亂的,其實受限一些人腦的想法,我想結果應該也不是<真的>亂數;另外,依照人的習慣,轉出來的數字,通常也不會跟原有密碼重複,由這也會流出一些線索。
* 如果要比較安全的腳踏車鎖,請使用鑰匙鎖,密碼鎖上鎖都該用真的亂數產生器產生密碼,或者每次轉密碼不能偷懶,一格一個好好的亂轉。
其實,每次上鎖都把它轉成 0000 也是不錯防範方式XD
* 其實我不確定有沒有更好的攻擊方法,只能從數字的規則中推敲出密碼間的關係,但資料量一多關係就很明顯了,這也是統計的效應:資料的數量才是重點,就算裡面垃圾很多,透過大量資料也能篩出有意義的資訊,某種程度上來說,統計或Big Data 也是從垃圾裡淘出黃金,算是現代的回收業吧XD。

後記:
其實幾個月前,我鑰匙型的車鎖不見了,換了新的密碼鎖,多轉了幾次之後,想到這個點子。本篇文中的例子,都是這一個星期來,每次停腳踏車時就記錄一次,累積一個星期的資料就猜出密碼了。當然文中提到的密碼也是真的,只是我現在已經換了一個,所以大家要是看到我的小紅,就別用文中的密碼去試了。
也要提醒大家,如果看到有人在看你腳踏車密碼鎖,而且過幾天又看到他,很可能他就是要對你的腳踏車實施旁通道攻擊,腦袋中一定響個警鈴:「噹噹,你的腳踏車正遭受攻擊」,一定要記得換個密碼。

不過話說回來,我猜直接搬走腳踏車比這個方法容易多了。

2017年3月9日 星期四

使用 git patch 來搬移工作內容

前幾天在改一個專案,因為筆電的設備不夠強大,只能到桌電上開發,兩邊都是開發機,也就沒有用remote 的方式來同步專案。今天,要把那時候的commit 搬回筆電,只好使用git 的patch 移動工作內容,這裡記錄一下整體工作流程和解決衝突的方法。

首先是生成 patch,git 本身有兩種 patch 的功能,一種是使用常見的diff,一種則是 git 專屬的patch system。
常見的 diff 其實也就是git diff 生成的 patch,內容就是:這幾行刪掉,這幾行加上去,用 git diff > commit1.patch 就能輕鬆生成。git patch system 則是用 git format-patch 來產生,它提供比 diff 更豐富的資訊,他的使用有幾種方式:
git format-patch <commit>  從某一個 commit 開始往後生成patch
git format-patch -n <commit> 從某一個 commit 開始從前先成 n 個patch

使用format-patch的好處是,它可以一次對大量的commit 各產生一個patch,之後就能用git 把這些commit 訊息原封不重放到另一個 repository 裡面;我自己的習慣,是開發一個 features 就開一個新的branch,這樣在生成 patch的時候只要使用format-patch master,就會把這條分枝的 commit 都做成patch,git 會自動在檔案前綴 0001, 0002,這樣用 wildcard patch 時就會自動排序好。
你說如果 patch 的數量超過10000 個怎麼辦……好問題,我從來沒試過這麼多patch,搞不好我到目前為止的 commit 都沒這麼多呢,一般project 超過 10000 個commit ,結果要用patch system 來搬動工作內容也是滿悲劇的啦
細看 patch 的內容,開頭是這個commit 的概略訊息,修改的檔案小結,後面就是同樣的diff 內容。
From commit-hash Mon Sep 17 00:00:00 2001
From: author <email>
Date: Mon, 6 Mar 2017 14:07:42 +0800
Subject: [PATCH] fix getDates function

---
database.py  | 9 +++++----
viewer.py      | 5 +++++
2 files changed, 10 insertions(+), 4 deletions(-)

現在我們來使用 patch ,把這些patch檔拿到要apply 的repository裡
針對常見的diff,其實用shell 的patch 指令就能apply 上去,但它出問題的風險比較高,也不能把 commit 訊息帶上,所以我都不用這招。對format-patch產生的 patch,我們就能用 git apply 的指令來用這個 patch,首先先用
git apply --check patch
來檢查 patch 能不能無縫補上,只要打下去沒噴訊息就是正常。

接著就是用 git apply patch 把patch 補上,然後記得自己commit 檔案。
這樣還是太慢,我們可以用
git am *.patch
把所有的 patch 一口氣送上,運氣好的話,會看到一整排的 Applying: xxxxx,所有的commit 立即無縫接軌。

如果運氣不好,patch 有衝突的話,apply 就會什麼都不做。
衝突其實很常見,只要你產生patch 跟apply patch 的地方有些許不同就會發生,因為patch 裡只記載了刪掉哪些內容、加上哪些內容,一旦要刪掉的內容不同,apply就會判斷為衝突。這跟merge 的狀況不同,merge 的時候雙方有一個 base作為基準點,可以顯示 <<<<<< ====== >>>>>> 的差異比較,apply 的資訊就少很多。
在 apply patch 的時候,也可以使用 -3 來使用3方衍合,但若你的repository 中沒有patch 的祖先,這個apply 一樣會失效。
衝突時am 會跳出類似這樣的訊息:
Applying: <commit message>
error: xxxxxxxxxxxxxxxxxxx
Patch failed at 0001 <commit message>
The copy of the patch that failed is found in: .git/rebase-apply/patch
When you have resolved this problem, run "git am --continue".
If you prefer to skip this patch, run "git am --skip" instead.
To restore the original branch and stop patching, run "git am --abort".

這裡我們只能手動解決,在apply 的指令加上:
git apply --reject patch
這樣會補上那些沒有問題的patch, 然後把無法補上的地方寫入 .rej 檔案中。
下一步我們要用編輯器,打開原始檔跟 .rej 檔,把檔案編輯成應該變成的樣子,把該加的檔案變化都git add add 之後,使用 git am --continue 完成commit。
後悔了,直接用 git am --abort 停掉 am 即可。

以上是git patch system 的簡介,我的心得是,能用git remote 就用git remote,merge 起來資訊豐富很多,解決衝突也有git-mergetool 如vimdiff 可以用,省得在那邊 format-patch 然後apply 起來機機歪歪,每個commit 都要手動解衝突是會死人的。

參考資料:
https://git-scm.com/book/en/v2/Distributed-Git-Maintaining-a-Project
http://aknow-work.blogspot.tw/2013/08/patch-conflict.html

2017年2月24日 星期五

使用dbench 進行硬碟效能測試

最近遇到需要大量進行儲存系統讀寫的要求,因為包含了samba 硬碟,查了一下發現了dbench 這個測試程式,就試用了一下,它支援本機的測試,也支援samba, iscsi跟nfs 測試:

本測試需安裝 dbench 進行測試,在Linux 主機上使用下列指令取得dbench。
git clone https://github.com/sahlberg/dbench

先安裝要測試網路硬碟所需的library:
sudo apt-get install libiscsi-dev
sudo apt-get install smbclient
sudo apt-get install libsmbclient-dev
sudo apt-get install samba-dev
sudo apt-get install libnfs-dev
在dbench 中使用下列指令編譯,configure 要加的參數來自這個gist,才能找到samba client library:
https://gist.github.com/Labisana/6d94b7db13b08be586ce
./autogen.sh
./configure CFLAGS="-I/usr/include/samba-4.0/"
make
make install

完成編譯dbench,在測試機上,可以使用dbench來進行測試,簡單的如:
./dbench --loadfile=loadfiles/client.txt -t <TIME> <THREAD>
或是想要測試遠端的samba server:
./dbench -B smb --smb-share=//<IP>/<DIR> --smb-user=<USER>%<PASS> --loadfile=loadfiles/smb_1.txt -t <TIME> <THREAD>

各欄位說明如下:
<IP> <DIR>:samba之IP位址及資料夾名稱。
<DIR> <USER> <PASS>:samba使用者之帳號與密碼。
<TIME>:測試時間
<THREAD>:使用多少程序進行測試。
如果是nfs 或iscsi 的話,應該會需要其他的參數以設定登入,不過手邊沒有nfs 或iscsi 可以測試,只好先跳過。

loadfile 是dbench 的測試檔案,裡面可以描述想要dbench 執行的讀寫動作,例如開檔、寫檔等等,如果寫得好,它應該可以重現一般使用者真實的讀寫狀況,不過我都直接用它在loadfiles 資料夾中預設的檔案,如上面的smb_1.txt。
自己試過在超過50 個thread 的時候,samba很容易出現寫入錯誤,所以保守一點就用50 個thread 就是了,如果是本機測試的話,就不用這麼保守;不過話說回來一般本機上也不會有這麼多人一起用你的電腦就是了。
執行之後,dbench 就會印出測試的報表:
Operation                Count    AvgLat    MaxLat
--------------------------------------------------
Flush                      849   117.384   207.657
Close                     9000     0.003     0.117
LockX                       40     0.006     0.019
Rename                     520     0.078     3.887
ReadX                    19240     0.007     1.723
WriteX                    6039     0.056    11.209
Unlink                    2480     0.058     3.291
UnlockX                     40     0.003     0.005
FIND_FIRST                4300     0.035     0.162
SET_FILE_INFORMATION       990     0.090    10.150
QUERY_FILE_INFORMATION    1940     0.002     0.017
QUERY_PATH_INFORMATION   11350     0.013     8.822
QUERY_FS_INFORMATION      2040     0.004     0.048
NTCreateX                12260     0.020     8.160

Throughput 42.255 MB/sec  10 clients  10 procs  max_latency=207.669 ms

參考資料:
http://kongll.github.io/2015/04/24/dbench/
https://dbench.samba.org/doc/dbench.1.html

2017年2月22日 星期三

用vim 巨集整理文件格式

曾經有一次,從外面匯入一個project 的程式碼,林林總總大概10 幾個C 的source跟header,每個檔案幾十行到幾百行不等。
打開一看,關掉,哎呀我的眼睛業障重呀
唔…是沒這麼誇張,但裡面充斥著行末空白、排版有點糟糕,然後有些tab 跟空白混用,研究了一下,可以用vim 把這些程式都整理整理。

在刪掉trailing space 的部分用vim-better-whitespace:
https://github.com/ntpeters/vim-better-whitespace
這樣就能在文件中使用StripWhitespace 指令刪掉所有行末空白。
排版用vim 本身的排版功能,一般文件可以用 = 就會排好了,不過如果是python 的話就沒辦法,它不像C 有明確的分號跟大括號來表示縮排結束,所以這招對python 是沒用的,用了只會一直不斷的縮排下去。
把tab 換掉的部分,我基本上是space 派的(戰,這也可以用vim 的取代功能很快做完,不過我們要用ge 來suppress error ,以免文件中不存在tab 的狀況停掉巨集執行。

我們利用vim 的巨集功能,把上面幾個結果串在一塊,選一個喜歡的英文字母(這裡用y,因為我最喜歡y了),依序輸入下面的指令,打完一行就按一下enter,#後面的表示註解:

q
y #將巨集存在 y 暫存區
:StripWhitespace  #清除trailing space
:%s/\t/ /ge  #全文件取代tab 為雙空白
=G  #全文件重新排版
:w  #記得存檔,神明保佑(X
:n  #編輯下一份文件
q
然後關鍵的一步來了,我們的巨集在執行完就會跳到vim 暫存區的下一個檔案,現在,我們在project 目錄裡面,可以用 vim *.h *.c 一次打開所有程式檔案到暫存區。
接著只要 100@y,執行這個巨集100 次(好吧如果你檔案更多就選個更大的數字),巨集的執行會在 :n 沒有下一個檔案的時候停止,這樣就能把所有檔案的格式都整理得漂漂亮亮了。

2017年2月19日 星期日

gmail 碎紙機

電子郵件是個讓人又愛又恨的東西,基於工作的理由你實在不能不用它,各網站服務也都是用電子郵件帳號註冊跟認證,可是,一但加入會員就會不斷收到宣傳信,像是什麼<XX購物網>一直倒宣傳信過來,啊我沒錢不然你給我錢我買你的東西這樣可以嗎?然後像什麼求職廣告、防詐騙提醒,封鎖要是鎖掉了真正重要的信件又很麻煩。工作上討論一次CC群組,每個人回信信就一直湧進來,多到要爆炸……嗯爆炸是不至於,畢竟我用的gmail 空間很大。

可是呢,每天刪信刪到手軟也很煩,突發奇想就寫了一個chrome extension,原始碼在這裡,可以載下來之後用<載入未封裝之擴充功能>來安裝:
https://github.com/yodalee/shred-my-gmail

我不但要刪掉信,在刪的同時還要把他給攪掉,用下面這個 extension boilerplate:
https://github.com/KartikTalwar/gmail-chrome-extension-boilerplate
搭配最新的 gmail.js
http://github.com/kartiktalwar/gmail.js

首先是在文件中插入一個 audio tag,並連結到我extension 中的mp3 檔
var a = document.createElement('audio');
a.id = "sound";
a.src = chrome.extension.getURL('shredder.mp3');
(document.head || document.documentElement).appendChild(a);
然後在main.js 裡面,載入gmail.js 之後,用gmail.js 的observe
gmail.observe.on("delete", playShredSound);
就能監看刪除事件,並用getElementById 取得audio element 並播放,就能做出信被攪掉的效果,聲音的來源是這裡:
https://www.youtube.com/watch?v=ECstQgQZWXc
下載下來之後用audacity 把切削的部分取出來。

雖然信其實只是一樣進到垃圾桶裡而已,不過把垃圾信攪一攪就有一種爽感:「X的你們這些王八蛋垃圾信,看我把你們攪成碎片~~~」;我本來想說如果把信永久刪除的話,就放個燒掉的音效好了,最後覺得燒掉的聲音太難表現還是算了。

總之寫了一個自己爽的專案,感謝各位看倌貢獻的時間,文章最後附上示範影片連結:

2017年2月17日 星期五

柔珠、遊覽車與自由主義

過年從家裡拿了兩罐Biore 的男性洗面乳,用時仔細一看它標榜含有<黑白微粒柔珠>,好極了,這不是會通過汙水處理系統,已經公告明年禁用的產品嗎?當下決定立即停用,看怎麼處理掉它們。
會知道柔珠產品的危害,首先是來自於臉書上看到美國政府宣佈禁用的英文新聞,爾後台灣政府也旋即宣佈禁用,相關新聞看下來自然也對相關產品的議題有初步的了解。

柔珠議題其實是個小縮影:禁用柔珠的產品,也等於是政府介入自由市場,規定某些交易(交易含有柔珠的產品)不能發生,它的目的是為了防止柔珠產品進入大洋,透過食物鏈危害生態,甚而回頭危害人類。
商家應該有權利販售他們生產的商品,政府憑什麼介入?柔珠對生態造成的破壞很大,但反映到一般人眼中其實很小,政府是基於全民的健康,限制了某些交易的自由。如果把政府的角色拿掉,想透過市場力量把產品淘汰掉,社會必須先累積足夠的環保共識,由有識之士透過逐次的宣導讓大家拒用柔珠產品,真照完全自由主義的做法,別說柔珠,就是DDT、CFC 的禁用,大概也是耗日費時。
這也是組成政府的目的,提高某些規範與保障的效率。

套用到最近的遊覽車司機、一例一休跟Uber 案,政府的監管其實並不是那麼沒必要的,這其實也是人民、社會和政府間的一種妥協,我們授權給政府,讓它為我們分配資源、維持法律、督導業者、取締不法。
因為柔珠對生態造成的破壞,因為過勞<偶爾>發生的遊覽車車禍造成的死傷、因為計程車規格不統一而讓乘客吃的虧,放大一點看,政府之所以取締酒駕,不也是要防止酒駕的成本轉嫁到全民身上?
自由主義是一種理想,它假定市場是萬能的,就像個清新脫俗的美少女,政府介入都像鹹豬手一樣噁心,我認為這並非事實,也許政府管理並非永遠正確,甚至有些愚蠢,但它絕非無效。
有時,迷信自由主義過了頭,好像看不見的手能解決任何問題,當政府真的就該介入的時候,就轉而訴諸政府的管制是無效的,一切的原因都是消費者蠢,只能說也太過天真了。

在臉書這個世界最大的個人媒體,有機會看到很多為自由主義宣傳的文章,像是臉書專頁<真暴民的時事筆記>,對於遊覽車的新聞有著如下評論:「真正的進步,是懂得為自己負責的消費者帶領出來的。有什麼樣的需求,就有什麼樣的供給,光把法規修成歐規,騙得了自己也騙不了人。」
好像很合理?問題是我們到底要的是一個<我們是超自由社會好棒棒>的大旗,大家繼續等著無良業者被淘汰;還是由政府訂下一定的規則,讓大多數人遵守並依規定懲處不符規定的玩家?就如這幾天各國網友分享的,在各國規定之下,無論是雙司機、強制八小時工時等等法規限制,雖然提高了消費者負擔,卻也很有效的保障司機作為勞工的權益,也保護乘客的安全。

政府介入是假的進步,還是讓社會往更好方向前進的必須?

我們也可以照樣造句:
「真正的環保,是懂得環境保護的消費者帶領出來的,有什麼樣的需求,就有什麼樣的供給,修正法律禁止柔珠,騙得了自己也騙不了人」
「真正的治安,是懂得為自我規範的公民們帶領出來的。有什麼樣的人民,就有什麼樣的社會,光是立法懲罰,騙得了自己也騙不了人。」
都不知道在說什麼鬼話了…「把道德立成法律」由政府執行也許很噁心,但不立法律讓大家把道德當成法律,下場也不會好到哪裡去,說得更明白一點,現在會有過勞的問題,不正是政府放任業者依照成本和利潤壓榨司機的結果,自由經濟學真棒?

這就是自由主義最大的罩門:不是每個人都是全知全能,甚至我們必須承認,所有人的能力都不夠,才因而組成政府。
正因為勞資兩方是不公平的,因此我們組成政府,訂定勞基法來限制一些人壓榨其他人
正因為每個人追求最大利益會忽視如環保的外部性,因此我們組成政府來替我們取締不法,訂定環保法規。
正因為獨佔帶來不公平競爭,因此我們組成政府,對抗獨佔業者。
正因為市場存在資訊不對稱,因此我們組成政府禁止內線交易和炒作股票。

我們可以假想一個勞工跟資方站在平等立場的世界,不會有貧窮急需用錢的勞工,家裡不會有嗷嗷待哺的小孩,可以不急不徐的拒絕不合理的工作環境,向資方磨出一個合理的工資。在消費者跟司機全知的世界,消費者會自動拒絕過勞司機的旅行社,旅行社拉低條件會立即因為請不到人而倒閉,勞基法在這裡一無是處,但這不是現實。

這種錯誤很容易犯,迷信某些東西能把所有問題一掃而空,就跟迷信宗教一樣,我在開始接觸 Linux 的時候,也曾經成為<教徒>,覺得政府單位為什麼還要交微軟稅?作業系統全部用Linux 取代,Office 全部用Libreoffice 取代不就好了?見識多了,才會發覺事情不會這麼簡單。
舉著理想的大旗,很好,通常理想指向一個值得實現的目標;但請不要忘記社會的現實,只看著目標是會被路上的坑洞絆倒的。

相關資料:
為何加班拚不到經濟卻會傷害無辜?
http://www.ettoday.net/news/20170216/867311.htm

2017年2月14日 星期二

在Archlinux 上安裝mariadb筆記 (Install mariadb on Archlinux)

最近在我的Archlinux 上面安裝MySQL,結果撞了一堆牆,在這裡筆記一下過程,希望有機會的話能幫到其他使用者。
步驟就是照著wiki 所講,一步一步往下做:
https://wiki.archlinux.org/index.php/MySQL

首先因為Archlinux 已經改用mariadb ,MySQL 移去AUR,不過我試過,用yaourt -S mysql 也會裝mariadb ,一個由不得你的概念。
sudo pacman -S mariadb

使用mysql_install_db 設定好/var/lib/mysql
mysql_install_db --user=mysql --basedir=/usr --datadir=/var/lib/mysql

用systemd 啟動mariadb service
systemctl start mariadb
最後可以用mysql_secure_installation來進行安全設定

我在start mariadb 這步遇上問題,service 總是起不來,出現類似這樣的訊息,要不就是start mariadb 直接hang住:
Job for mariadb.service failed because the control process exited with error code.
See "systemctl status mariadb.service" and "journalctl -xe" for details."
使用systemctl status mariadb.service 之後,它會列出哪行指令出了錯,我記得沒錯的話是 /usr/sbin/mysqld,總之它開不了 /var/lib/mysql 中的某些檔案。
後來發現原因是,在安裝mariadb 的時候,理論上它要加上mysql 這個使用者,但原因不明的沒有加上去,因此我們要手動幫它補上:
groupadd -g 89 mysql
useradd -u 89 -g mysql -d /var/lib/mysql -s /bin/false mysql
chown mysql:mysql /var/lib/mysql

然後重跑上面的mysql_install_db,就能順利的把mariadb 跑起來了

2017年1月19日 星期四

使用webpack打包React.js 專案

在上一篇我們終於在server 上把React.js 跑起來之後,就能繼續寫下去,結果很快的我們就遇到另一個問題,那就是怎麼所有code 都擠在一塊了(yay,
我們上一篇解法的問題,在於我們template render 這個網頁之後,相關的檔案全塞在用<script>引入的javascript 檔案裡,然後這個文件就引不入其他文件了,而React 提倡的模組化該是能把各元件分別歸檔才是。
幸好,我們還是有解法的,利用webpack 讓我們把檔案分開,然後用webpack 打包成單一的 javascript 檔案,據說這種寫法就是<入坑>,因為一用了webpack 就脫不了身,之後都會綁死在webpack 上面。
網路上有些相關的文件,就來一步步跟著做:
https://rhadow.github.io/2015/04/02/webpack-workflow/

首先先準備我們的front-end/main.jsx,因為使用到react跟react-dom,因此在檔案開頭用require 引入,完成的main.jsx 像這樣:
'use strict'

var React = require('react')
var ReactDOM = require('react-dom')

var hello = React.createClass({
  render: function() {
    return (<h2>Hello World!</h2>);
  }
});

ReactDOM.render(
  React.createElement(hello, null),
  document.getElementById('content')
);
接著使用npm 安裝webpack :
npm install --save-dev webpack

並編輯webpack 的設定檔webpack.config.js:
var path = require('path');

var config = {
  entry: [path.resolve(__dirname, './front-end/main.jsx')],
  output: {
    path: path.resolve(__dirname, './static'),
    filename: 'bundle.js'
  },
};

module.exports = config;
這時候使用webpack 來打包,會出現錯誤訊息:
ERROR in ./front-end/main.jsx
Module parse failed: /home/yodalee/website/message-downloader/front-end/main.jsx Unexpected token (8:12)
You may need an appropriate loader to handle this file type.

明顯它看不懂react 的jsx 語法,我們需要設定webpack.config.js 遇到 jsx 檔案就使用babel-loader,還要排除本地的node_modules 裡面的檔案,與output 同層加上:
module: {
  loaders: [{
    test: /\.jsx?$/,
    loader: 'babel-loader',
    exclude: path.join(__dirname, 'node_modules')
  }]
},
並編輯babel的設定檔 .babelrc:
{
  "presets": ["react"],
}
並且使用npm 安裝babel-loader和preset react:
npm install --save-dev babel-loader babel-core babel-preset-react react react-dom

這樣就能用webpack 幫忙打包所有檔案了,有了這個我們就能…嗯…把bower 給刪掉啦(・∀・)。

現在bundle.js 產生在static 裡面,我們可以把template/view.html 清得乾乾淨淨只剩下這樣就會動了:
<!DOCTYPE html>
<html>
  <head lang="en">
    <meta charset="UTF-8">
    <title>View Test</title>
  </head>
  <body>
    <div id="content"></div>
    <script src="/static/bundle.js"></script>
  </body>
</html>
有沒有覺得超級神奇,我也是這麼覺得的。
老實說你要說我一知半解我也認了,前端真的是各種古怪離奇,我看網路上那堆stack overflow的答案怎麼每個都不太一樣,然後試著做不能動的居多(yay,這篇要不是強者我同學人生勝利組一哥冠霖大神的指導,根本要試超久才試得出來。

下面整理一些相關的錯誤訊息:
Unexpected token
well 它看不懂 jsx 的檔案,所以需要用babel-loader 來處理 jsx 檔

Module not found: Error: Cannot resolve module 'react'
表示它在node_modules 裡面找不到require 的module,使用npm 安裝即可

ERROR in ./front-end/main.jsx
Module build failed: Error: Couldn't find preset "react" relative to directory "/home/yodalee/website/message-downloader"
這個是指 babel-loader 沒有相關的……套件?像上面裝了babel-preset-react 就解掉了。

本文感謝強者我同學人生勝利組一哥冠霖大神的指導

2017年1月14日 星期六

使用bower安裝react 前端環境

最近寫message-viewer ,想在bottle.py 執行的server 上面跑React.js,於是就小找了一下,基本上排除了使用 bottle-react 這種懶人套件,我想要的就是能直接寫,同時react jsx 也能在我的管控之下的設定。
後來找到這篇文章,照著它的步驟、跟留言的回覆做就成功了,在這邊整理一下:
https://realpython.com/blog/python/the-ultimate-flask-front-end/

這裡就不介紹React.js 的運作原理了,筆者到目前也還在學,總之我們就跟裡面的一樣,先寫個 view.html,裡面沒什麼,就是用React 寫一個Hello World,直接使用cdnjs 提供的library:https://cdnjs.com/libraries/react/
<!DOCTYPE html>
<html>
  <head lang="en">
    <meta charset="UTF-8">
    <title>View Test</title>
  </head>
  <body>
    <div id="content"></div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.38/browser.min.js"></script>

    <script type="text/babel">
var hello = React.createClass({
  render: function() {
    return (<h2>Hello World!</h2>);
  }
});

ReactDOM.render(
  React.createElement(hello, null),
  document.getElementById('content')
);
    </script>
  </body>
</html>

接著我們使用任何一種server 的template engine ,我這裡用的是Jinja2就能把網頁跑起來,因為script 來自CDN,所以不必特別設定就能直接使用,打開來應該會出現h1 的Hello World。
app.route('/view', 'GET', MessageViewHandler)
@route('/view')
def MessageViewHandler():
    template = JINJA_ENVIRONMENT.get_template('view.html')
    return template.render()

下一步我們要在自己的電腦上面裝上React,我們使用的是管理前端的管理程式bower,像是bootstrap, React 什麼的都可以裝,

因為我是用archlinux ,本身就提供了bower 套件,所以可以用 pacman 裝bower;非archlinux 的發行版就要用npm 裝bower:
$ npm install -g bower
使用-g 是設定global ,因為在其他project 中八成也會用到bower,但另外,為了服務載了你的project 卻沒有bower 的使用者,我們也需要設定一個npm 的文件:
$ npm init
$ npm install --save-dev bower

有了bower之後,在project 中初始一個bower:
$ bower init
設定直接接受預設設定即可,它會產生一個 bower.json 檔案,我們另外要指定bower 安裝檔案的路徑為static,這要編輯 .bowerrc 並加入下列內容:
{
  "directory": "./static/bower_components"
}
並使用bower 安裝套件,可以用 bower install <package_name> --save 或是在bower.json 中加入套件名之後,再呼叫 bower install;我們這裡用第二種方法,在bower.json 的dependency 下面加上:
"dependencies": {
  "bootstrap": "^3.3.6",
  "react": "^15.1.0",
  "babel": "^5.8.38"
}
並執行 bower install,就能安裝好所需的套件,這時project 中的檔案應該差不多是這樣:
.bowerrc
.gitignore
bower.json
package.json
static/bower_components
template/

現在可以把上面的cdnjs 換成本地資料夾的static link:
<script src="/static/bower_components/react/react.min.js"></script>
<script src="/static/bower_components/react/react-dom.min.js"></script>
<script src="/static/bower_components/babel/browser.min.js"></script>
同時在server 要加上static handler來處理所有對static 的連結:
@route('/static/<path:path>')
def callback(path):
    return static_file(path, root='static')

這樣應該就能在python server 上面寫react.js 的網頁前端了,把剛剛的view.html 打開來跑跑看吧。

2017年1月7日 星期六

多益 900+

去年12/18 ,參加了去年最後一場多益聽讀考試,1/6 公佈成績結果是930,聽力490閱讀440。
之所以要考這場多益,原因是五月退伍後有小小求職過,發現手邊已經沒有適合的英文能力證照,2011 年暑假考的GRE 在今年過期了,當時的成績也不太好,V 60 % / Q 90 % / W 3.5,而且GRE 本身就不是一個廣泛使用的求職英文標準,寫出來大家都不太鳥。
為了及時填補英文能力證明的火力空隙,暑假做了練習題,評估過自己英文實力後,決定報考12/18的多益測驗。

自己的背景當然比較優一點,在學校已經習慣念英文課本跟paper ,聽英文演講也沒有太大問題,口說跟寫作當然…嗯…沒事,就加加減減過得去啦
報考之後下半年就展開練習的生活,大概從8月底持續到12月,除了中間有事出差或太忙,每週末就到圖書館作一回練習題,看看都錯什麼題目。這裡要感謝之前實驗室的地下室長(X 莘予大大借我練習題庫:<New TOEIC 新多益閱讀題庫解析>跟<New TOEIC 新多益聽力題庫解析>,兩本題庫總共2000題,這套據說難度跟真實考試比起來稍難一些,我自己是覺得差不多,全部刷完對我的幫助不小。
另外也有在手機上下載背單字用的App AnkiDroid,單字卡是選用GRE的7500 張單字卡,平時零碎的時間就我把手機拿出來記點單字,我想多少有點幫助(雖然到現在還是沒記完)。
平常練習的時候聽力跟閱讀都是錯10題左右,也就是各450 分,那時候才會定下 >900這個目標,雖然只要超過860就有金色證書,但身為一個整數控,當然是要取個整數900,幸好這次有順利達成目標不用二試。
// 不過我要是有強者我同學:呂神郝神的 -3 dB 大概就 990 了吧QAQ

分科來看,老實說聽力的結果比我想像的好很多,平時練習聽力最怕多益的英、澳口音,跟聽習慣的美、加口音不同,英、澳的口音比較糊成一團,特別聽力 Part 2聽短句問題然後最適當的回答,問題短短的糊成一團就過去了,根本沒聽懂在問什麼,有些 5W1H 的發音還不太一樣,直接聽錯在問什麼;這次測驗事後論壇也有不少人評論有個男生講話特別的糊,好像含著魯蛋在講話,能考到490我也覺得運氣真的很好。
閱讀則是有點偏難(? ,平常練習都可以寫完還剩20分鐘左右,考試可能是太緊張或者太謹慎,剛進到最後一題雙閱讀題,竟然就宣布剩10分鐘,當下有些緊張,最後一題也沒好好讀,成績果然沒有太好看。

小碎念一下,我一直很疑惑多益為什麼不把滿分定在1000 分,拿個990讓人很想進位呀OwO,而且1000 四位數亮出來就是硬比三位數來得有氣勢呀;另外因為身旁實在太多人都990了,害我以為930其實不是個很好的成績 lol不過真要說起來,多益還是比較偏門,像強者我學長丞淵大神在TOEFL 大殺四方,那才是聽說讀寫俱佳的真強者owo。

曾經聽人說過,<這些檢定什麼的都是外顯的,並不能決定你>,仔細想想,要是真有些實際功績,例如在國外念過書或者在外商工作大殺四方,甚或如柏任學長直接在會議會場擔任口譯,丞淵學長在英文辯論場上如戰群雄,他們也許根本不需要這些證書,也能證明自己的英文能力,會考這張證書,也許正是沒有實戰能力的表徵吧?
Anyway,多益順利考過了,評估起來如果再練一兩個月,把閱讀速度跟單字量再提高一些,再考一次閱讀應該能拿到更好的成績,不過目前既然有過900就不打算再考了,接下來就要專心準備七月二戰JLPT N1 了(你看…又是沒實戰能力所以要考證書…QAQ)。

Related Posts Plugin for WordPress, Blogger...