帶你玩轉Visual Studio——性能分析與優化

找到性能瓶頸

二八法則適合不少事物:最重要的只佔其中一小部分,約20%,其他80%的儘管是多數,倒是次要的。在程序代碼中也是同樣,決定應用性能的就那20%的代碼(甚至更少)。所以優化實踐中,咱們將精力集中優化那20%最耗時的代碼上,這那20%的代碼就是程序的性能瓶頸,主要針對這部分代碼進行優化。html

常見優化方法:

這部分我就不寫,直接參見《性能調優攻略》,由於我沒有自信能寫出比這更好的。ios

若是不想這麼深刻地瞭解,看看《C++程序常見的性能調優方式》這篇文章也是不錯的。算法

應用案例

咱們以一個應用案例來說解,以致於不會那麼乏味難懂。shell

咱們知道能被1和它自己整除的整數叫質數,假設1到任意整數N的和爲Sn(Sn=1+2+3+…+n)。如今要求10000100000之間全部質數和Sn編程

可能你會以爲這問題不是So Easy嗎!都不用腦殼想,咣噹一下就把代碼寫完了,代碼以下:windows

#include <iostream>
#include <windows.h>

// 定義64位整形
typedef __int64 int64_t;

// 獲取系統的當前時間,單位微秒(us)
int64_t GetSysTimeMicros()
{
    // 從1601年1月1日0:0:0:000到1970年1月1日0:0:0:000的時間(單位100ns)
#define EPOCHFILETIME   (116444736000000000UL)
    FILETIME ft;
    LARGE_INTEGER li;
    int64_t tt = 0;
    GetSystemTimeAsFileTime(&ft);
    li.LowPart = ft.dwLowDateTime;
    li.HighPart = ft.dwHighDateTime;
    // 從1970年1月1日0:0:0:000到如今的微秒數(UTC時間)
    tt = (li.QuadPart - EPOCHFILETIME) / 10;
    return tt;
}

// 計算1到n之間全部整數的和
int64_t CalculateSum(int n)
{
    if (n < 0)
    {
        return -1;
    }

    int64_t sum = 0;
    for (int i = 0; i < n; i++)
    {
        sum += i;
    }
    return sum;
}

// 判斷整數n是否爲質數
bool IsPrime(int n)
{
    if (n < 2)
    {
        return false;
    }

    for (int i = 2; i < n; i++)
    {
        if (n %i == 0)
        {
            return false;
        }
    }

    return true;
}

 

void main()
{
    int64_t startTime = GetSysTimeMicros();
    int count = 0;
    int64_t sum = 0;
    for (int i = 10000; i <= 100000; i++)
    {
        if (IsPrime(i))
        {
            sum = CalculateSum(i);
            std::cout << sum << "\t";
            count++;
            if (count % 10 == 0)
            {
                std::cout << std::endl;
            }
        }
    }
    int64_t usedTime = GetSysTimeMicros() - startTime;
    int second = usedTime / 1000000;
    int64_t temp = usedTime % 1000000;
    int millise = temp / 1000;
    int micros = temp % 1000;
    std::cout << "執行時間:" << second << "s " << millise << "' " << micros << "''" << std::endl;
}

而後運行。多線程

我想這確定不是你要的結果(太慢了),若是你以爲還滿意,那下面的就能夠不用看了。併發

VS的性能分析工具

性能分析工具的選擇

打開一個性能分析的會話:Debug->Start Diagnotic Tools Without Debugging(或按Alt+F2)VS2013Analysis菜單中。 app

CPU Usage

檢測CPU的性能,主要用於發現影響CPU瓶頸(消耗大量CPU資源)的代碼。函數

GPU Usage

檢測GPU的性能,經常使用於圖形引擎的應用(DirectX程序),主要用於判斷是CPU仍是GPU的瓶頸。

Memory Usage

檢測應用程序的內存,發現內存。

Performance Wizard

性能(監測)嚮導,綜合檢測程序的性能瓶頸。這個比較經常使用,下面再逐一說明。

性能(監測)嚮導

1.指定性能分析方法;

CPU Sampling(CPU採樣) 

進行採樣統計,以低開銷水平監視佔用大量CPU的應用程序。這個對於計算量大的程序可大大節省監控時間。 

Instrumentation(檢測)

 徹底統計,測量函數調用計數和用時

.NET memory allocation(.NET 內存分配) 

 跟蹤託管內存分配。這個好像只有託管代碼(C#)纔可用,通常以C++代碼好像不行。

Resource contention data(併發) 

檢測等待其餘線程的線程,多用於多線程的併發。

2.選擇要檢測的模塊或應用程序;

3.啓動分析程序進行監測。

 

性能分析報告

程序分析完成以後會生成一個分析報告,這就是咱們須要的結果。

 

視圖類型

有幾個不一樣的視圖可供咱們切換,下面加粗的部分是我的以爲比較方便和經常使用的視圖。
Summary(概要):整個報告概要說明
Call Tree(調用樹):以樹形表格的方式展開函數之間的關係。
Module(模塊):分析調用的不一樣的程序模塊,如不一樣的DLLlib模塊的耗時
Caller/Callee(調用與被調用):以數值顯示的調用與被調用的關係
Functions(函數統計):以數值顯示的各個函數的執行時間和執行次數統計值
Marks(標記):
Processers(進程):
Function Detials(函數詳情):以圖表的方式形象地顯示:調用函數-當前函數-被調用子函數之間的關係和時間比例。 

 

調用樹

函數詳情

 

函數統計

 

專用術語 

若是是第一次看這報告,你還不必定能看懂。你須要先了解一些專用術語(你能夠對照着Call Tree視圖和Functions視圖去理解)
Num of Calls:(函數)調用次數
Elapsed Inclusive Time:已用非獨佔時間
Elapsed Exclusive Time:已用獨佔時間
Avg Elapsed Inclusive Time:平均已用非獨佔時間
Avg Elapsed Exclusive Time:平均已用獨佔時間
Module Name:模塊名稱,通常爲可執行文件(.exe)、動態庫(.dll)、靜態庫(.lib)的名稱。 

也許看完你還迷糊,只要理解什麼是獨佔與非獨佔你就都明白了。 

什麼是獨佔與非獨佔 

非獨佔樣本數是指的包括了子函數執行時間的總執行時間
獨佔樣本數是不包括子函數執行時間的函數體執行時間,函數執行自己花費的時間,不包括子(函數)樹執行的時間。 

解決應用案例問題 

咱們已經大體瞭解了VS2015性能分析工具的使用方法。如今迴歸本質,解決上面說起的應用案例的問題。 

1、咱們選擇Function Detials視圖,從根函數開始依據百分比最大的項選擇,直到選擇PrintPrimeSum,這時能夠看到以下圖: 

找出性能瓶頸1

咱們能夠看到IO佔了50%(49.4%+9.7%)的時間,因此IO是最大的性能瓶頸。其實,有必定編程經驗的人應該都能明白,在控制檯輸出信息是很耗時的。咱們只是須要結果,不必定非要在控制中所有輸出(這樣還不便查看),咱們能夠將結果保存到文件,這樣也比輸出到控制檯快。

注:上圖所示的時間,應該是非獨佔時間的百分比。

知道了瓶頸,就改進行代碼優化吧:

void main()
{
    int64_t startTime = GetSysTimeMicros();
    std::ofstream outfile;
    outfile.open("D:\\Test\\PrimeSum.dat", std::ios::out | std::ios::app);
    int count = 0;
    int64_t sum = 0;
    for (int i = 10000; i <= 100000; i++)
    {
        if (IsPrime(i))
        {
            sum = CalculateSum(i);
            outfile << sum << "\t";
            count++;
            if (count % 10 == 0)
            {
                outfile << std::endl;
            }
        }
    }
    outfile.close();
    int64_t usedTime = GetSysTimeMicros() - startTime;
    int second = usedTime / 1000000;
    int64_t temp = usedTime % 1000000;
    int millise = temp / 1000;
    int micros = temp % 1000;
    std::cout << "執行時間:" << second << "s " << millise << "' " << micros << "''" << std::endl;
}

再次執行,發現時間一下減少到

效果很明顯!

 2、但這還不夠,繼續檢查別的問題,對新代碼再次用性能分析工具檢測一下。 

找出性能瓶頸2

咱們發現IsPrime函數佔用了62%的時間,這應該是一個瓶頸,咱們能不能對其進行算法的優化?仔細想一想,上面求質數的方法實際上是最笨的方法,稍微對其進行優化一下:

// 判斷整數n是否爲質數
bool IsPrime(int n)
{
    if (n < 2)
    {
        return false;
    }

    if (n == 2)
    {
        return true;
    }

    //把2的倍數剔除掉
    if (n%2 == 0)
    {
        return false;
    }

    // 其實不能被小於n的根如下的數整除,就是一個質數
    for (int i = 3; i*i <= n; i += 2)
    {
        if (n % i == 0)
        {
            return false;
        }
    }

    return true;
}

再次執行,發現時間一下減少到:

 幾乎減了一半的時間。

3、這仍是有點慢,再看看還能不能進行優化。對新代碼再次用性能分析工具檢測一下。 

CalculateSum函數佔了88.5%的時間,這絕對是影響目前程序性能的主要因素。對其進行。仔細想一想,求1N的和其實就是求123 … N的等差數列的和。優化代碼以下:

// 計算1到n之間全部整數的和
int64_t CalculateSum(int n)
{
    if (n < 0)
    {
        return -1;
    }

    //(n * (1 + n)) / 2
    return ( n * (1 + n) ) >> 1;
}

再次執行,發現時間一下減少到:

一秒中以內,基本上能夠知足要求子。

總結

程序性能調優,就是數上面這樣一點點地改進的過程,直到知足應用的要求。上面只用了一個視圖的一種統計指標(各函數所用時間佔總時間的百分比),就解決了問題。對於大型的複雜應用程序,咱們能夠結果多種視圖的多種統計指標進行綜合判斷,找出程序性能的瓶頸!

 

代碼:

#include <iostream>
#include <windows.h>

// 定義64位整形
typedef __int64 int64_t;

// 獲取系統的當前時間,單位微秒(us)
int64_t GetSysTimeMicros()
{
    // 從1601年1月1日0:0:0:000到1970年1月1日0:0:0:000的時間(單位100ns)
#define EPOCHFILETIME   (116444736000000000UL)
    FILETIME ft;
    LARGE_INTEGER li;
    int64_t tt = 0;
    GetSystemTimeAsFileTime(&ft);
    li.LowPart = ft.dwLowDateTime;
    li.HighPart = ft.dwHighDateTime;
    // 從1970年1月1日0:0:0:000到如今的微秒數(UTC時間)
    tt = (li.QuadPart - EPOCHFILETIME) / 10;
    return tt;
}

// 計算1到n之間全部整數的和
int64_t CalculateSum(int n)
{
    if (n < 0)
    {
        return -1;
    }

    int64_t sum = 0;
    for (int i = 0; i < n; i++)
    {
        sum += i;
    }
    return sum;
}

// 判斷整數n是否爲質數
bool IsPrime(int n)
{
    if (n < 2)
    {
        return false;
    }

    for (int i = 2; i < n; i++)
    {
        if (n %i == 0)
        {
            return false;
        }
    }

    return true;
}


void PrintPrimeSum()
{
    int64_t startTime = GetSysTimeMicros();
    int count = 0;
    int64_t sum = 0;
    for (int i = 10000; i <= 100000; i++)
    {
        if (IsPrime(i))
        {
            sum = CalculateSum(i);
            std::cout << sum << "\t";
            count++;
            if (count % 10 == 0)
            {
                std::cout << std::endl;
            }
        }
    }
    int64_t usedTime = GetSysTimeMicros() - startTime;
    int second = usedTime / 1000000;
    int64_t temp = usedTime % 1000000;
    int millise = temp / 1000;
    int micros = temp % 1000;
    std::cout << "執行時間:" << second << "s " << millise << "' " << micros << "''" << std::endl;
}
相關文章
相關標籤/搜索