2016年8月9日 星期二

PHP Laravel MVC架構概念

整理目前上課以及實作過程摸索的內容


2016年8月1日 星期一

傳統硬碟的硬碟重整是否有效益?

最近在看youtube上哈佛的cs50課程,裡面講到了傳統硬碟的運作方式,這讓我對硬碟重整是否有效益產生好奇心,稍微思考了一下,也把概念寫成這篇文章做記錄

這個問題的效益評量指標可以從硬碟壽命和時間成本兩個面向探討,為簡化問題,我們把傳統硬碟壽命簡單以讀取頭移動距離來代表,並以單一文件的讀取為例子

假設一個硬碟壽命是可以移動100km的距離
當一個文件儲存的位置分散的時候,假設每讀取一次文件讀取頭移頭至各個位置取得數據的總移動距離是1m,花費0.1秒

接下來做了一次硬碟重整,把該文件各部份數據移動到相近的區域,這個動作花費了讀取頭500m的移動距離完成,以及2小時的時間(通常在不影響使用的時候作業,故不列入考慮)

重整後每次讀取該文件的總移動距離減少為0.6m,花費0.07秒(時間除了移動,還有讀取的動作以及所在位置處於外圈或內圈等的其他因素,所以不會是等比例,此處先不討論)

那麼這樣是有效益的嗎?

a.從硬碟壽命的角度來看:

重整花了500m的距離,每次讀取減少了0.4m
500m / 0.4m = 1250,當對這個文件的讀取超過1250次,節省的移動就超過了重整的移動成本,那麼重整就是有效益的,反之則否

b.從時間成本的角度來看:

每次讀取減少了0.3秒,假設個人時間價值是500 nt / hour,每秒價值0.139nt,每次讀取節省了0.0417nt時間成本,假設一顆硬碟價格是2500nt,壽命100km,等當40m / 1nt,500m /40m = 12.5nt , 12.5nt / 0.0417nt = 299.8,當對這個文件的讀取超過299.8次,就是有效益,反之則否

c.因為重整同時縮短移動距離與讀取時間,所以應該綜合考量

重整後每次移動節省 0.4m + 1.67m(由0.0417nt換算距離) = 2.07m

500m / 2.07m = 241.55,只要重整後讀取該文件有超過241.55次就是有效益

結論:

這個問題沒有絕對的答案,會依情況而有不同,真的要有一個統整性的參考答案,可能就是要從統計的角度衡量,若能有足夠多的樣本數據可以統計,我們就可以依統計數據評估整體而言硬碟重整普遍是正效益或是負效益

以上只是將問題簡單化探討這個問題,現實運作有很多難以量化的變因,這篇文章想表達的是要有對事物的本質的好奇心以及研究的精神,那麼就能從錯誤中學習並持續進步。

程式效率優化範例

以下是一個針對使用者輸入內容做驗證的function,分別有兩個寫法
我們來比較一下差異

======================================
寫法一:相對較差的方式

function checkForm() {
    if(document.formJoin.m_username.value ==""){
        alert("請填寫帳號!");
        document.formJoin.m_username.focus();
        return false;
    }else{
        uid=document.formJoin.m_username.value;
        if(uid.length<5 || uid.length>12){
            alert("您的帳號長度只能5至12字元");
            return false;
        }
        if(!(uid.charAt(0)>='a'  && uid.charAt(0)<='z')){
            alert("帳號第一個字元只能是小寫英文");
            return false;
        }
        for(idx=0; idx<uid.length;idx++){
            if(uid.charAt(idx)>='A' && uid.charAt(idx)<='Z'){
                alert("帳號不可有大寫字元");
                document.formJoin.m_username.focus();
                return false;
            }
            if(!((uid.charAt(idx) >='a'  && uid.charAt(idx)<='z')|| (uid.charAt(idx) >='0'  && uid.charAt(idx)<='9') || uid.charAt(idx)=="_")){
                alert("帳號只能英文或數字及_!");
                document.formJoin.m_username.focus();
                return false;
            }
            if(uid.charAt(idx)=="_" && uid.charAt(idx-1)=="_"){
                alert("符號不能相連");
                document.formJoin.m_username.focus();
                return false;
            }
        }
    }
    if(!(check_passwd(document.formJoin.m_passwd.value,document.formJoin.m_passwdrecheck.value))){
        document.formJoin.m_passwd.focus();
        return false;
    }
}

function check_passwd(pw1,pw2) {
    if(pw1==""){
        alert("密碼不可為空白");
        document.formJoin.m_passwd.focus();
        return false;
    }
    for(var idx=0;idx<pw1.length;idx++){
        if(pw1.charAt(idx)==" " || pw1.charAt(idx)=="\""){
            alert("密碼不可有空白或雙引號\!n");
            document.formJoin.m_passwd.focus();
            return false;
        }
        if(pw1.length<5 || pw1.length>10){
            alert("密碼介於5到10個字元!\n");
            document.formJoin.m_passwd.focus();
            return false;
        }
        if(pw1!=pw2){
            alert("密碼兩次輸入不一樣");
            document.formJoin.m_passwd.focus();
            return false;
        }
    }
    return true;
}


======================================
寫法二:比較好的方式,和寫法一的差異以註解說明

function checkForm() {
    //將查詢結果儲存到變數中,避免多餘的查詢
    var inpM_username = document.formJoin.m_username;
    if (inpM_username.value == '') {
        alert('請輸入帳號!');
        inpM_username.focus();
        return false;
    } else {
        //將id字串長度計算結果儲存到變數中,避免重複計算,尤其是在for迴圈判斷結束點時
        var uid = inpM_username.value,
            uidLength = uid.length;
        if (uidLength < 5 || uidLength > 12) {
            alert('帳號長度需介於5~12個字元');
            inpM_username.focus();
            return false;
        } else {
            //字元的比較會自動轉為ascii碼比大小
            //以local變數做為for迴圈的累加運算變數,而不是global變數(未使用var或let宣告的變數就是global變數)
            for (var i = 0; i < uidLength; i++) {
                //將取得特定索引位置字元的結果儲存到變數,避免重複運算
                var uidIndexWord = uid.charAt(i);
                if (uidIndexWord >= 'A' && uidIndexWord <= 'Z') {
                    alert('帳號不可以含有大寫字元');
                    inpM_username.focus();
                    return false;
                }
                if (!((uidIndexWord >= 'a' && uidIndexWord <= 'z') || (uidIndexWord >= '0' && uidIndexWord <= '9') || uidIndexWord == '_')) {
                    alert('帳號只能數字,英文字母及「_」');
                    inpM_username.focus();
                    return false;
                }
                if (uidIndexWord == "_" && uid.charAt(i - 1) == "_") {
                    alert('「_」符號不可以相連');
                    inpM_username.focus();
                    return false;
                }
            }
        }
    }
    //將查詢結果儲存到變數中,避免多餘的查詢
    var inpM_passwd = document.formJoin.m_passwd,
        pw1 = inpM_passwd.value,
        inpM_passwdrecheck = document.formJoin.m_passwdrecheck,
        pw2 = inpM_passwdrecheck.value;
    if (!checkPassword(pw1, pw2)) {
        inpM_username.focus();
        return false;
    }
}
function checkPassword(pw1, pw2) {
    //以下順序代表著效率的優化

    //先簡單確認是否為空白
    if (pw1 == '') {
        alert('密碼不可以空白');
        return false;
    }
    //先儲存長度再執行for迴圈避免重複計算長度
    var pwLength = pw1.length;
    //既然已取得長度,就先比長度是否符合限制,因為速度快,且寫在for外面也是避免無意義的重複
    if (pwLength < 5 || pwLength > 12) {
        alert('密碼需介於5~12個字元');
        return false;
    }
    //再來執行for迴圈 word by word比對,只要一個不符合即return結束,也是很快
    for (var i = 0; i < pwLength; i++) {
        var pwIndexWord = pw1.charAt(i);
        if (pwIndexWord == ' ' || pwIndexWord == '\"') {
            alert('密碼不可以包含空格或雙引號');
            return false;
        }
    }
    //最後才是字串的比對,寫在for外面避免無意義的重複比對
    if (pw1 != pw2) {
        alert('密碼確認不一致,請修正');
        return false;
    }
    return true;
}

寫法二不一定是最好的方式,也一定有更好的寫法,但這篇文章想表達的是養成思考是否有更佳的實現方式的習慣,就會一直進步這個觀念。