天天看點

c#進階學習--程式調優一、概述二、調優

一、概述

     程式調優是每個菜雞程式員慢慢變成大神的必修課,程式調優關乎到使用者體驗,伺服器的效率等諸多方面,我在此總結部分程式調優經驗供大家學習和參考.

二、調優

2.1 sql調優

     在程式的開發過程中免不了使用資料庫,并且在邏輯不是很複雜的程式中,程式速率的瓶頸往往是sql語句的執行,是以sql調優是首先要介紹的.

2.1.1 合理建立和利用索引

        關系型資料庫中索引相當于資料表的目錄,資料庫有了目錄的訓示才不用将所有的資料挨個進行排查,了解了這句話,索引的基本建立規則實際上就了解的差不多了,想象一下,資料表是一本書,你需要怎麼去建立他的目錄才能夠讓你最快的找出你想要的内容?那當然是根據你的查詢或者比對條件,你的索引與條件越符合,那麼查詢的速度就會越快(在查詢的條件中應該盡量避免使用函數).

      當建立了一個索引時,資料庫需要在進行修改的時候去維護它,是以索引可以增加查詢速度,但是會降低插入速度,并且索引也需要硬碟空間,資料量越多,索引所包含的東西越多,索引所占的空間就會直線上升,是以在建立索引之前需要考量它是否真的有必要.

    如果你的查詢是完全比對上的索引并且感覺到查詢速度依然不如意的話,那麼可能是因為資料庫的資訊碎片所導緻的,簡單來說就是資料庫的存儲資料因為長時間的增删改操作導緻資料被分到了不同的區域導緻查詢時間較長,遇到這種情況可以采用删除并重建索引的方式進行解決(是以當向資料庫中導入資料的時候應該先導入資料再建立索引,以防止碎片的産生).

2.1.2 sql語句的自身優化

      sql語句的好壞直接決定了查詢的時間和硬體的開銷,一個好的sql語句的條件應當盡量與索引相符,如果有多條件或者聯合查詢的時候,盡量先将資料篩選的足夠精确,然後再進行其他的操作。。。

優化還有很多方面具體可以參考:https://www.cnblogs.com/kkxwze/articles/10844569.html

2.1.3 盡量減少與資料庫的連接配接和互動次數

      在程式與資料庫的互動過程是影響程式效率中很重要的一個環節,每次通路資料庫都需要大量的開銷,是以在通路資料庫較為頻繁的程式中盡量保持資料庫的連接配接,建立連接配接池而不是用完就将資料庫的連接配接銷毀,這樣可以在一定程度上保證程式的效率,避免資源的浪費(c#語言中DbConnection類會自動維護資料庫的連接配接,是以隻要不刻意銷毀繼承DbConnection類所建立出的對象就能夠達到資料庫的連接配接重複使用的效果)。

     就算程式與資料庫始終建立連接配接,程式通路資料庫依然會占用時間和資源,是以需要盡量減少資料庫的通路次數,适當利用緩存來增加程式的響應速度,在某些情況也可以将多條執行的語句合并成一句來進行統一執行。

2.1.4 合理分庫分表

分表:

    衆所周知,關系型資料庫的資料表中的資料如果變得很大的話就會變得很難維護,但是一般資料的增删改查操作大多數都是對近期的資料進行處理,是以可以根據業務來進行分表處理,将不同時間段的資料放入到不同的表中以減少每張資料表中的資料量,加快語句執行速度,并減少維護成本。

分庫:

   資料庫的的功能是存儲資料,資料需要用來做運算和統計分析也需要用來進行展示,是以,我們可以根據資料的不同使用功能來進行資料庫的拆分,使得運算和統計互不影響。

具體分庫分表可以參照的思路:https://www.cnblogs.com/butterfly100/p/9034281.html

2.2堆,棧,裝箱

    代碼在編寫的時候應該盡量考慮哪些代碼需要消耗系統資源和時間,并盡量考慮簡化這些代碼,使得程式的運作效率更上一層樓。

   對于c#的代碼來說操作可以分為以下幾類型:

   1.建立對象

   2.對象指派

   3.邏輯處理

   4.銷毀對象

  其中邏輯處理和對象的銷毀資料代碼邏輯優化,在下一步接收,這一步主要介紹對象的建立和指派的優化。

  首先,建立任何一個對象的實體或者對象的指針都是需要消耗時間和系統資源的。

  在了解如何優化之前,我們首先需要了解堆棧的概念與特點。

  首先我們要了解在建立一個對象時,對象是放在哪裡的,對于c#來說,建立的對象有兩種,一種是值類型的對象,一種 是引用類型的對象,那麼顧名思義,我們通路值類型的對象時通路的是對象的内容,而通路引用類型的對象時通路的是對象的指針 ,然後通過指針去通路内容.

值類型中的資料和引用類型的指針是會被放入棧中,而引用類型的内容會被放入 到堆中,也就是說系統在正常情況下通路的都是棧,如果是值類型那麼就會直接得到 資料,如果是引用類型那麼就會得到指針,然後再通過指針通路内容,這樣說來,棧中的資料讀取速度會明顯快于堆中資料讀取速度的.是以我們得到第一條優化規則

--聲明,調用堆中的值類型資料的速度會快于引用類型資料的速度(引用類型的資料總是放在堆中,但是值類型的資料如果是直接調用資料的話就是放在棧中,如果通過引用類型進行調用的話就是放在堆中)

了解了這些之後我們還應該了解到引用類型的資料和值類型的資料在指派上的差別,

引用類型的資料在指派時會直接在棧中開辟一份空間用來存儲棧中的資料,當修改資料時,會直接修棧中所存儲的資料,

而引用類型的指派分為兩種,一種是将指針直接指向已有的堆中,另外一種是在堆中開辟新的空間存放資料,再用指針指向它.

當一個方法正常傳參時,值類型的資料會直接拷貝一份到新的方法中,是以在修改方法中值類型的資料時原資料不會發生改變,而當一個引用類型的資料作為方法的參數時拷貝過去的是指針,是以如果修改指針所指的堆中的資料則會影響到原有的資料.(這裡可以很明确的知道方法傳參會在棧中新開辟空間,而不會影響到堆(不适用ref或者out等關鍵字,且string除外))

--(string雖然是引用類型的資料,但是它會将值而不是指針作為參數來傳遞.是以,當有過長的string需要傳遞時最好先将其進行封裝)

,是以

--無論堆中的資料量有多大,引用類型和值類型的傳參效率是差不多的.

我們現在再看一下循環建立的效率.我們看一下下面幾段代碼

代碼段1:

int j = 0;
            for (int i = 0; i < 10;i++ )
            {
                int k = 3;
                j = j + k;
            }
           

代碼段2:

int j = 0;
            int k = 3;
            for (int i = 0; i < 10;i++ )
            {
                j = j + k;
            }
           

代碼段2的效率是要明顯高于代碼段1的效率的,因為代碼段1在不斷重複建立和銷毀棧中的k.而代碼段2則建立一次,就不需要進行任何的其他操作,這個是正常的邏輯,代碼也應該像2這樣寫,但是作為開發者的我們還應該看到一些其他的東西,

代碼段一中的k随着for的執行完畢會被銷毀,而代碼段2中的k則在方法的生命周期内都會一直存在,雖然加快了運算速度和效率但是依然存在浪費記憶體的情況,并且存儲于棧中的資料無法手動消除,是以在某些極端的情況下可以考慮代碼段1的編寫方式.

在代碼的編寫過程中,經常會用到StringBuilder類,很多人隻知道這樣會增加系統性能,但是不知道為什麼,這是因為string字元串是存放在堆中,并且長度是固定的,是以string每做一次拼接都會在堆中建立一個string對象,然後改變指針的指向,那麼這個過程是需要消耗系統性能的,是以我們在處理這類拼接的字元串的工作時,用StringBuilder類作為輔助來減少系統開銷.

我們再看一下下面的一段代碼

,

int i = 5;
            object o=i;
            int j=(int)o;
           

這裡兩個變量,i是值類型,o是引用類型,那麼以上的代碼發生的實際情況是:運作是程式會先在棧上建立一個i=5的對象,然後在堆上建立一個包含值(5)的對象,o的值是對該對象的一個引用,最後再在棧上建立一個j=5的對象,這個過程就是裝箱和拆箱,裝箱和拆箱的操作會降低性能,一次裝箱拆箱的操作的開銷或許微乎其微,但是執行千百次這樣的操作不僅會增大程式本身的開銷,還會建立衆多的對象,增加伺服器負擔.

2.3 垃圾回收與代碼邏輯優化

首先c#與java一樣,堆中的垃圾有自動回收機制,但是至于什麼時候回收就要看天意了,而正是因為這種不确定性導緻程式可能在運作的時候占用的記憶體無法得到快速的釋放,進而影響裝置性能,嚴重的還會導緻記憶體溢出的錯誤,是以在編寫代碼的時候發現堆中存放的資料占用空間較多時可以手動清理掉堆中的資料(像datatable用完了之後可以調用clear方法,來清空堆中的資料以保證記憶體的充足)并且在适當的時候還可以手動調用GC來進行對沒有指針指向的堆中的資料進行垃圾的清理工作..

其次,很多我們所用到的進階文法的底層機制其實都是低級文法,隻不過在文法上進行了封裝,是以雖然lambda表達式或者linq之類能夠帶給我們很多友善,但是并不能增加系統運作效率,,是以不能迷信這些進階文法,很多時候自己去親力親為,掌握自己代碼的時間複雜度以及空間複雜度,并在自己的代碼基礎上進行優化反而是更好的選擇!