Android:通過systrace進行性能分析


一、Systrace 簡介

  Systrace 允許您在系統級別(如SurfaceFlinger、WindowManagerService等Framework部分關鍵模塊、服務、View系統等)收集和檢查設備上運行的所有進程的計時信息。 它將來自Android內核的數據(例如CPU調度程序,磁盤活動和應用程序線程)組合起來,以生成HTML報告。

二、抓取Systrace 的方法:

方法一:使用Android Device Monitor行抓取 Systrace
  1. 啟動 Android Device Monitor 工具,因為Android studio 3.1后認為monitor用的很少,便去掉了菜單欄啟動按鈕,所以只能通過命令運行該工具了。
    工具位於android-sdk目錄中,例如我的本地SDK目錄為“C:\Users\drago\AppData\Local\Android\Sdk” ,然后在tools目錄中的monitor.bat即為啟動腳本,雙擊運行即可。


    可以通過自己寫一個 DDMS.bat 放在桌面作為快捷啟動:
    @echo off
    rem  color 0A :設置cmd背景顏色和字體顏色
    color 0A
    rem title:設置cmd標題
    title Start Android Studio Mointor 
    echo 請按任意鍵打開 Android Studio Mointor .....
    pause>nul
    rem “D:\AndroidSDK\tools\monitor.bat”是AndroidStudio配置SDK文件夾下monitor.bat的完整路徑
    call D:\Android\Sdk\tools\monitor.bat
  2. Monitor的界面如下:
  3. 點擊Capture按鈕並配置需要抓取信息:

    Destination File :制定生成的trace.html文件的地址
    Trace duration:抓取時間,通常設置5秒,並在5秒內重現問題,時間太短會導致問題重現時沒有被抓到,時間太長會導致JavaHeap不夠而無法保存。因此在能抓到問題點的情況下,時間越小越好。
    Trace Buffer Size:存儲Systrace的size,同樣太小會導致信息丟失,太長會導致Java Heap不夠而無法保存。如果檢測結果有異常,請調整Buffer Size的大小試試。
    Enable Application Traces from:檢測的應用,默認選擇none,這里需選擇自己需要檢測的應用。
    Commonly Used Tag:常用標簽,這部分TAG全部使能。只關注顯示情況的話 ,只選取Graphics和View System即可。
    Advanced Options:高級選項。如果設備root了,可以看到更多的TAG,如eMMC commands、Synchonization、Kernel Workqueues。

    注:假如抓取過多次trace,為避免數據丟失,請及時清除緩存中的內容,清理地方在 Android Device Monitor的右下角,如下圖所示:

  4. 配置完成(如抓取camera相關trace)點擊確定后,開始操作手機,在時間到了后會自動生成報表(trace.html)文件。

方法二:使用命令行抓取 Systrace

  1. 下載並安裝Android SDK Tools(C:\Users\drago\AppData\Local\Android\Sdk\platform-tools\systrace\systrace.py),安裝Python

    注:Python是沒有自帶訪問windows系統API的庫的,需要下載。庫的名稱叫pywin32,可以從網上直接下載:

      https://github.com/mhammond/pywin32/releases (下載適合你的Python版本)

  2. 連接手機,打開開發者選項中的USB Debug選項,使用命令行抓取 Systrace,語法如下:
    python systrace.py [options] [categories]

     例1: 調用systrace10秒鍾內記錄設備進程,包括圖形進程,並生成一個名為mynewtraceHTML報告:

       python systrace.py --time=10 -o mynewtrace.html gfx 

     例2: 檢測UI性能:

       python systrace.py view --time=10

     可通過 python systrace.py -l 顯示所有支持的選項。

     如果不指定任何類別或選項,systrace將生成包含所有可用類別的報告,並使用默認設置。 可用的類別取決於您使用的連接設備。

 參數分為兩個部分options和category:

 options可取值:

options 解釋
-o <FILE> 指定trace數據文件的輸出路徑,如果不指定就是當前目錄的trace.html
-t N, –time=N 執行時間,默認5s。絕對不要把時間設的太短導致你操作沒完Trace就跑完了,這樣會出現Did not finish 的標簽,分析數據就基本無效了
-b N, –buf-size=N buffer大小(單位kB),用於限制trace總大小,默認無上限
-k <KFUNCS>,–ktrace=<KFUNCS> 追蹤kernel函數,用逗號分隔
-a <APP_NAME>,–app=<APP_NAME> 這個選項可以開啟指定包名App中自定義Trace Label的Trace功能。也就是說,如果你在代碼中使用了Trace.beginSection("tag"), Trace.endSection;默認情況下,你的這些代碼是不會生效的,因此,這個選項一定要開啟
–from-file=<FROM_FILE> 從文件中創建互動的systrace
-e <DEVICE_SERIAL>,–serial=<DEVICE_SERIAL> 指定設備,在特定連接設備上進行跟蹤,由設備序列號標識 。
-l, –list-categories 這個用來列出你分析的那個手機系統支持的Trace模塊,一般來說,高版本的支持的模塊更多

 category可取值:

category 解釋
gfx Graphic系統的相關信息,包括SerfaceFlinger,VSYNC消息,Texture,RenderThread等;分析卡頓非常依賴這個。
input Input
view View繪制系統的相關信息,比如onMeasure,onLayout等。。
webview WebView
wm Window Manager
am ActivityManager調用的相關信息;用來分析Activity的啟動過程比較有效。
sm Sync Manager
audio Audio
video Video
camera Camera
hal Hardware Modules
app Application
res Resource Loading
dalvik 虛擬機相關信息,比如GC停頓等。
rs RenderScript
bionic Bionic C Library
power Power Management
sched CPU調度的信息,非常重要;你能看到CPU在每個時間段在運行什么線程;線程調度情況,比如鎖信息。
binder_driver Binder驅動的相關信息,如果你懷疑是Binder IPC的問題,不妨打開這個。
core_services SystemServer中系統核心Service的相關信息,分析特定問題用。
irq IRQ Events
freq CPU Frequency
idle CPU Idle
disk Disk I/O
mmc eMMC commands
load CPU Load
sync Synchronization
workq Kernel Workqueues
memreclaim Kernel Memory Reclaim
regulators Voltage and Current Regulators

  [options] 是一些命令參數,[category] 是你感興趣的系統模塊,比如view代表view系統(包含繪制流程),am代表ActivityManager(包含Activity創建過程等);分析不同問題的時候,可以選擇不同你感興趣的模塊。需要重復的是,盡可能縮小需要Trace的模塊,其一是數據量小易與分析;其二,雖然systrace本身開銷很小,但是縮小需要Trace的模塊也能減少運行時開銷。比如你分析卡頓的時候,power, webview 就幾乎是無用的。

方法三:離線抓取 Systrace

  ① 輸入指令:adb root && adb remount
  ② 輸入以下指令開始后台抓取systrace,此時可以斷開usb連接線去復現問題:
adb shell "atrace -z -b 40000 gfx input view wm am camera hal res dalvik rs sched freq idle disk mmc -t 15 > /data/local/tmp/trace_output &"

  參數說明:

-a appname enable app-level tracing for a comma separated list of cmdlines

-b N use a trace buffer size of N KB

-t N trace for N seconds [defualt 5]

-z compress the trace dump

--list_categories list the available tracing categories

The time and buffer size should be long enough to finished the systrace collecting.

  ③ 復現問題后,重新連接usb線輸入如下指令,確認atrace進程是否結束抓取並退出:

adb shell ps -A | grep atrace

  ④ 抓取完成后,取出生成的trace文件,並轉換成html格式:

adb pull /data/local/tmp/trace_output systrace.py --from-file trace_output -o output.html

  然后就可以用谷歌瀏覽器打開分析了~

 

三、trace.html文件分析:

使用Google Chrome(其他瀏覽器很可能打不開)將這個文件打開進行分析,界面如下:
在進程的上面有一條很細的進度條,包含了該線程的狀態:
  灰色
: 睡眠。
  藍色: 可以運行(它可以運行,但還未被調度運行)。
  綠色: 正在運行(調度程序認為它正在運行)。

  紅色
: 不間斷的睡眠(通常發生在內核鎖上), 指出I / O負載,對於性能問題的調試非常有用
  橙色: 由於I / O負載導致的不間斷睡眠。
  要查看不間斷睡眠的原因(可從sched_blocked_reason跟蹤點獲取),請選擇紅色不間斷睡眠切片。

按鍵操作 作用
w             放大,[+shift]速度更快
s             縮小,[+shift]速度更快
a             左移,[+shift]速度更快
d             右移,[+shift]速度更快

f             放大當前選定區域
m             標記當前選定區域
v             高亮VSync
g             切換是否顯示60hz的網格線
0             恢復trace到初始態,這里是數字0而非字母o

h             切換是否顯示詳情
/             搜索關鍵字
enter      顯示搜索結果,可通過← →定位搜索結果
`             顯示/隱藏腳本控制台
?             顯示幫助功能

 

 (1)分析View性能


  然后通過w放大,找F(即Frames)和F之間的間隔時間,如果間隔時間超過16ms的都是有問題的,16ms其實對應的就是60fps(1/60≈16ms),因為人眼與大腦之間的協作無法感知超過60fps的畫面更新。Android系統每隔16ms發出VSYNC信號,觸發對UI進行渲染,那么整個過程如果保證在16ms以內就能達到一個流暢的畫面。那么如果操作超過了16ms就會發生下面的情況,如果系統發生的VSYNC信號,而此時無法進行渲染,還在做別的操作,那么就會導致丟幀的現象,(察覺到APP卡頓的時候,可以看看logcat控制台,會有drop frames類似的警告)。其中F圓圈中綠色表示在16.6毫秒內呈現的幀,花費16.6毫秒以上渲染的幀用黃色紅色框圈表示。

  

  放大后可看到時間1420~1500ms中,兩個F之間間隔70ms左右,明顯是超過16ms的,然后繼續放大可以看到具體的任務內容和占用時長:

  此時再點擊“F”圖標會在下欄提示相關內容如下:

  提示是listview在recycling/rebinding的時效率低,接着點擊Alerts:

  點擊各項也會相應展開,並給出性能分析結果和優化建議:

  如果要查看工具在 trace中發現的每個 Alert以及設備觸發 Alert的次數,請單擊窗口最右側的 Alerts選項卡,如下圖所示:
  

  如果在 UI Thread上做太多的工作,需要找出哪些方法消耗了太多的 CPU時間。一種方法是添加跟蹤標記到您認為會導致這些瓶頸的方法,以查看這些函數調用是否顯示在 systrace中。 如果您不確定哪些方法可能會在UI線程上造成瓶頸,請使用 Android Studio的內置 CPU分析器,或者生成跟蹤日志並使用 Traceview查看它們。
   雖然Systrace無法定位到某一行需要優化的代碼,但通過Alerts和Frames以根據TraceView分析具體函數花了多長時間來進一步優化代碼提高性能。
 
 (2)分析HAL層線程處理性能
  
  ①用chrome打開trace的html文件。
  ②搜索trace關鍵字,然后按"m"鍵Mark一下,搜索的線程所處階段會被高亮。

 

  
 

代碼中添加標記生成 trace log

  由於systrace是在系統級顯示有關進程的信息,因此很難在HTML報告中的某個特定時間知道您的應用程序正在執行什么方法。 在Android 4.3(API級別18)及更高版本中,您可以使用代碼中的Trace類在HTML報告中標記執行事件。 您不需要用代碼來記錄systrace的跟蹤記錄,但是這樣做可以幫助您查看應用程序代碼的哪些部分可能會導致線程掛起或UI斷線。這種方法與使用Debug類不同,Trace類簡單地將標志添加到systrace報告中,而Debug類可幫助您通過生成.trace文件來檢查詳細的app CPU使用情況。

 (1)Java代碼要生成包含已檢測的跟蹤事件的systrace HTML報告,如果使用指令方式抓取則需要使用-a或--app命令行選項運行systrace,並指定應用程序的包名稱。

   通常在懷疑引起jank代碼地方的開始處添加 Trace.beginSection("defined by yourself"); 結束處添加Trace.endSection(); (注:這兩個方法需要在同一個線程中成對出現,否則多次調用beginSection時,調用endSection只會結束最近的beginSection方法),添加方法參考如下

public class MyAdapter extends RecyclerView.Adapter<MyViewHolder> {
    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        Trace.beginSection("MyAdapter.onCreateViewHolder");
        MyViewHolder myViewHolder;
        try {
            myViewHolder = MyViewHolder.newInstance(parent);
        } finally {
            // In try and catch statements, always call "endSection()" in a
            // "finally" block. That way, the method is invoked even when an
            // exception occurs.
            Trace.endSection();
        }
        return myViewHolder;
    }

   @Override
    public void onBindViewHolder(MyViewHolder holder, int position) {
        Trace.beginSection("MyAdapter.onBindViewHolder");
        try {
            try {
                Trace.beginSection("MyAdapter.queryDatabase");
                RowItem rowItem = queryDatabase(position);
                dataset.add(rowItem);
            } finally {
                Trace.endSection();
            }
            holder.bind(dataset.get(position));
        } finally {
            Trace.endSection();
        }
    }
}

  生成 trace.html 指令:

python systrace.py -a com.example.myapp -b 16384 -o my_systrace_report.html sched freq idle am wm gfx view binder_driver hal dalvik camera input res 

 

 (2)Native層在Android 6.0 (API level 23)及以上版本支持添加trace步驟如下:

    ①為ATrace函數定義函數指針,如下面的代碼片段所示:

#include <android/trace.h>
#include <dlfcn.h>

void *(*ATrace_beginSection) (const char* sectionName);
void *(*ATrace_endSection) (void);

typedef void *(*fp_ATrace_beginSection) (const char* sectionName);
typedef void *(*fp_ATrace_endSection) (void);

    ②加載ATrace_xxx符號,如下面的代碼片段所示:

// Retrieve a handle to libandroid.
void *lib = dlopen("libandroid.so", RTLD_NOW || RTLD_LOCAL);

// Access the native tracing functions.
if (lib != NULL) {
    // Use dlsym() to prevent crashes on devices running Android 5.1
    // (API level 22) or lower.
    ATrace_beginSection = reinterpret_cast<fp_ATrace_beginSection>(
        dlsym(lib, "ATrace_beginSection"));
    ATrace_endSEction = reinterpret_cast<fp_ATrace_endSection>(
        dlsym(lib, "ATrace_endSection"));
}

     注:出於安全考慮,dlopen操作只在debug時使用,另外如果要在Android 4.3 (API級別18)使用trace功能,可以通過JNI調用如上接口。

    ③在需要分析的函數開始和結束處分別調用ATrace_beginSection()和ATrace_endSection():

#include <android/trace.h>

char *customEventName = new char[32];
sprintf(customEventName, "User tapped %s button", buttonName);

ATrace_beginSection(customEventName);
// Your app or game's response to the button being pressed.
ATrace_endSection();

 

 C++中可以進一步通過宏定義利用構造及析構函數進行封裝,便於使用:

   ①宏定義:

#define ATRACE_NAME(name) ScopedTrace ___tracer(name)

// ATRACE_CALL is an ATRACE_NAME that uses the current function name.
#define ATRACE_CALL() ATRACE_NAME(__FUNCTION__)

class ScopedTrace {
  public:
    inline ScopedTrace(const char *name) {
      ATrace_beginSection(name);
    }

    inline ~ScopedTrace() {
      ATrace_endSection();
    }
};

   ②在需要追蹤的代碼部分開頭使用宏定義,即可給當前函數添加trace標記:

void myExpensiveFunction() {
  ATRACE_CALL();
  // Code that you want to trace.
}

 

>>>補充:

 (1)集成好trace庫的Android平台可以通過如下方法直接使用:

    #include <utils/Trace.h>
    #define ATRACE_TAG ATRACE_TAG_ALWAYS
    ATRACE_CALL();

Android 4.3 以上也可以用如下方式:
    #include <cutils/trace.h>
    ATRACE_BEGIN("TEST");
    ATRACE_END();
 (2)手動開啟App的自定義Label的Trace功能要調用一個SDK @hide的函數,需要反射調用,把下面這段代碼放在Application的`attachBaseContext`中即可,在非debuggable的版本中也適用!
Class<?> trace = Class.forName("android.os.Trace");
Method setAppTracingAllowed = trace.getDeclaredMethod("setAppTracingAllowed", boolean.class);
setAppTracingAllowed.invoke(null, true);
  (3) 通過 trace_marker 模擬實現用戶態函數跟蹤,封裝代碼如下:

  Utils_Trace.cpp

#include "Utils_Trace.h"
#include "stdio.h"

#define ATRACE_MESSAGE_LEN 256
#ifdef __cplusplus
extern "C" {
#endif
int trace_init(int * phHandle)
{
    int atrace_marker_fd = open("/sys/kernel/debug/tracing/trace_marker", O_WRONLY);
    if (atrace_marker_fd == -1) {
        return -1;
    }
    *phHandle = atrace_marker_fd;

    return 0;
}

void trace_uninit(int hHandle)
{
    if (-1 == hHandle) {
        return;
    }
    close(hHandle);
}

void trace_begin(int hHandle, const char *name)
{
    if (-1 == hHandle) {
        return;
    }
    char buf[ATRACE_MESSAGE_LEN] = { 0 };
    int len = snprintf(buf, ATRACE_MESSAGE_LEN, "B|%d|%s", getpid(), name);
    write(hHandle, buf, len);
}

void trace_end(int hHandle,const char *name)
{
    if (-1 == hHandle) {
        return;
    }
    char buf[ATRACE_MESSAGE_LEN] = { 0 };
    int len = snprintf(buf, ATRACE_MESSAGE_LEN, "E|%d|%s", getpid(), name);
    //char c = 'E';
    //write(hHandle, &c, 1);
    write(hHandle, buf, len);
}

void trace_async_begin(int hHandle, const char *name, const int32_t cookie)
{
    if (-1 == hHandle) {
        return;
    }
    char buf[ATRACE_MESSAGE_LEN] = { 0 };
    int len = snprintf(buf, ATRACE_MESSAGE_LEN, "S|%d|%s|%i", getpid(), name, cookie);
    write(hHandle, buf, len);
}

void trace_async_end(int hHandle, const char *name, const int32_t cookie)
{
    if (-1 == hHandle) {
        return;
    }
    char buf[ATRACE_MESSAGE_LEN] = { 0 };
    int len = snprintf(buf, ATRACE_MESSAGE_LEN, "F|%d|%s|%i", getpid(), name, cookie);
    write(hHandle, buf, len);
}

void trace_counter(int hHandle, const char *name, const int value)
{
    if (-1 == hHandle) {
        return;
    }
    char buf[ATRACE_MESSAGE_LEN] = { 0 };
    int len = snprintf(buf, ATRACE_MESSAGE_LEN, "C|%d|%s|%i", getpid(), name, value);
    write(hHandle, buf, len);
}
#ifdef __cplusplus
}
#endif

  Utils_Trace.h

#ifndef __UTILS_TRACE___H___
#define __UTILS_TRACE___H___

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#ifdef __cplusplus extern "C" { #endif /** @brief open trace. * * @param[in/out] phHandle Handle of the trace. * * @return -1 if fail, otherwise success. */ int trace_init(int * phHandle); /** @brief close trace. * * @param[in] hHandle Handle of the trace. * */ void trace_uninit(int hHandle); /** @brief begin trace. * * @param[in] hHandle Handle of the trace. * @param[in] name name of the trace. */ void trace_begin(int hHandle, const char *name); /** @brief stop trace. * * @param[in] hHandle Handle of the trace. * */ void trace_end(int hHandle, const char *name); /** @brief begin asynchronous trace. * Asynchronous traces produce non-nested (i.e. simply overlapping) intervals. * They show up as grey segments above the thin thread-state bar in the systrace HTML output. * They take an extra 32-bit integer argument that "distinguishes simultaneous events". * The same name and integer must be used when ending traces * * @param[in] hHandle Handle of the trace. * @param[in] name name of the trace. */ void trace_async_begin(int hHandle, const char *name, const int32_t cookie); /** @brief stop asynchronous trace. * * @param[in] hHandle Handle of the trace. * @param[in] name name of the trace. */ void trace_async_end(int hHandle, const char *name, const int32_t cookie); /** @brief Counters track the value of an integer and draw a little graph in the systrace HTML output.. * * @param[in] hHandle Handle of the trace. * @param[in] name name of the trace. * @param[in] counter counter of the trace. */ void trace_counter(int hHandle, const char *name, const int value); #ifdef __cplusplus } #endif #endif//__UTILS_TRACE___H___
  使用方法:
①. 把Utils_Trace.h、Utils_Trace.cpp放在工作代碼中,並編譯。
②. 初始化trace_init。
③. Insert tracepoints。   注: 同一個線程里使用trace_begin和trace_end。 不同的線程里使用trace_async_begin和trace_async_end (同一線程也可使用)。
④. 反初始化trace_uninit。
 
 
 (4)修改線程名:
#include <pthread.h>

static void *render_scene(void *parm) { // Code for preparing your app or game's visual components. } static void *load_main_menu(void *parm) { // Code that executes your app or game's main logic. } void init_threads() { pthread_t render_thread, main_thread; pthread_create(&render_thread, NULL, render_scene, NULL); pthread_create(&main_thread, NULL, load_main_menu, NULL); pthread_setname_np(render_thread, "MyRenderer"); pthread_setname_np(main_thread, "MyMainMenu"); }

  (5)修改進程名:

prctl(PR_SET_NAME, “process_name”, NULL, NULL, NULL);
   第一個參數是操作類型,指定PR_SET_NAME,即設置進程名。
  第二個參數是進程名字符串,長度至多16字節。
 
 
TraceView的使用

  Traceview是提供跟蹤日志的圖形工具。您可以通過使用Debug類來設置代碼來生成日志。 這種跟蹤方法非常精確,因為您可以准確指定要啟動的代碼中的哪個位置,並停止記錄跟蹤數據。 如果尚未生成這些跟蹤日志並將其從連接的設備保存到本地計算機,請轉至通過檢測應用程序生成跟蹤日志。 使用Traceview檢查這些日志可幫助您調試您的應用程序並剖析其性能。如果您不需要查看通過使用Debug類檢測應用程序來記錄的跟蹤日志,則可以使用Android Studio 3.0及更高版本中包含的CPU分析器來查看應用程序的線程和記錄方法跟蹤。使用Android Device Monitor可以查看trace Log內容。

  Android SDK自帶的Debug類使用方法如下:

在開始記錄的點寫上代碼Debug.startMethodTracing("tracePath"); 在終止記錄的點寫上代碼Debug.stopMethodTracing(); 通過adb pull /mnt/sdcard/tracePath.trace .將trace導出指定的文件夾中 通過Android studio打開trace文件,界面同CPU Profiler差不多。

  提示:可以使用命令行中的dmtracedump來生成跟蹤日志文件的圖形調用堆棧圖。

如上圖所示,CPU Profiler的視圖包括以下內容:
①App timeline:顯示CPU在執行過程中的時間軸。
②線程 timeline:顯示線程列表以及每個線程在某個時間段占用的CPU的資源情況。

綠色: 線程處於活動狀態或准備好使用CPU。也就是說,它處於”運行”或”可運行”狀態。
黃色:線程處於活動狀態,但是在完成其工作之前,它正在等待I / O操作(如文件或網絡I / O)。
灰色:線程正在睡眠,不會消耗任何CPU時間,當線程需要訪問尚未可用的資源時,有時會發生這種情況。要么線程進入自願性睡眠,要么內核使線程休眠,直到所需的資源可用。

③CPU timeline:列出CPU在App運行過程中CPU使用情況。
④Method Trace:在指定線程中,執行的方法棧,橫行表示執行的時間軸,縱向表示方法執行的調用軸。

橙色:系統方法
藍色:第三方API(包括java語言的api)
綠色:App自身方法 

 

linux進程、線程與cpu的親和性(affinity)

 一、什么是cpu親和性(affinity)

  軟親和性:  就是進程要在指定的 CPU 上盡量長時間地運行而不被遷移到其他處理器,Linux 內核進程調度器天生就具有被稱為 軟 CPU 親和性(affinity) 的特性,這意味着進程通常不會在處理器之間頻繁遷移。這種狀態正是我們希望的,因為進程遷移的頻率小就意味着產生的負載小。

  硬親和性:簡單來說就是利用linux內核提供給用戶的API,強行將進程或者線程綁定到某一個指定的cpu核運行。

  解釋:在linux內核中,所有的進程都有一個相關的數據結構,稱為 task_struct。這個結構非常重要,原因有很多;其中與親和性(affinity)相關度最高的是 cpus_allowed 位掩碼。這個位掩碼由 n 位組成,與系統中的 n 個邏輯處理器一一對應。 具有 4 個物理 CPU 的系統可以有 4 位。如果這些 CPU 都啟用了超線程,那么這個系統就有一個 8 位的位掩碼。 如果為給定的進程設置了給定的位,那么這個進程就可以在相關的 CPU 上運行。因此,如果一個進程可以在任何 CPU 上運行,並且能夠根據需要在處理器之間進行遷移,那么位掩碼就全是 1。實際上,這就是 Linux 中進程的缺省狀態;

 

二、進程與cpu的綁定

  sched_setaffinity可以將某個進程綁定到一個特定的CPU。你比操作系統更了解自己的程序,為了避免調度器愚蠢的調度你的程序,或是為了在多線程程序中避免緩存失效造成的開銷,可以自行將當前進程綁定到期望運行的CPU核上。
  以下示例將當前進程綁定到0、1、2、3號cpu上:

#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

/* sysconf( _SC_NPROCESSORS_CONF ) 查看cpu的個數;打印用%ld長整。
 * sysconf( _SC_NPROCESSORS_ONLN ) 查看在使用的cpu個數;打印用%ld長整 */
int main(int argc, char **argv)
{
    int cpus = 0;
    int  i = 0;
    cpu_set_t mask;
    cpu_set_t get;

    cpus = sysconf(_SC_NPROCESSORS_CONF);
    printf("cpus: %d\n", cpus);

    CPU_ZERO(&mask);    /* 初始化set集,將set置為空*/
    CPU_SET(0, &mask);  /* 依次將0、1、2、3號cpu加入到集合,前提是你的機器是多核處理器*/
    CPU_SET(1, &mask);
    CPU_SET(2, &mask);
    CPU_SET(3, &mask);

  //void CPU_CLR (int cpu, cpu_set_t *set) //這個宏將 指定的 cpu 從 CPU 集 set 中刪除
  //int CPU_ISSET (int cpu, const cpu_set_t *set) //如果 cpu 是 CPU 集 set 的一員,這個宏就返回一個非零值(true),否則就返回零(false)

  /*設置cpu 親和性(affinity)*/

    /*sched_setaffinity函數設置進程為pid的這個進程,讓它運行在mask所設定的CPU上.如果pid的值為0,
     *則表示指定的是當前進程,使當前進程運行在mask所設定的那些CPU上.
     *第二個參數cpusetsize是mask所指定的數的長度.通常設定為sizeof(cpu_set_t).
     *如果當前pid所指定的進程此時沒有運行在mask所指定的任意一個CPU上,
     *則該指定的進程會從其它CPU上遷移到mask的指定的一個CPU上運行.*/
    if (sched_setaffinity(0, sizeof(mask), &mask) == -1) {
        printf("Set CPU affinity failue, ERROR:%s\n", strerror(errno));
        return -1; 
    }   
    usleep(1000); /* 讓當前的設置有足夠時間生效*/

    /*查看當前進程的cpu 親和性*/
    CPU_ZERO(&get);
  /*sched_getaffinity函數獲得pid所指示的進程的CPU位掩碼,並將該掩碼返回到mask所指向的結構中.
   *即獲得指定pid當前可以運行在哪些CPU上.
   *同樣,如果pid的值為0.也表示的是當前進程*/
    if (sched_getaffinity(0, sizeof(get), &get) == -1) {
        printf("get CPU affinity failue, ERROR:%s\n", strerror(errno));
        return -1; 
    }
    
    /*查看當前進程的在哪個cpu核上運行*/
    for(i = 0; i < cpus; i++) {
        if (CPU_ISSET(i, &get)) { /*查看cpu i 是否在get 集合當中*/
            printf("this process %d of running processor: %d\n", getpid(), i); 
        }    
    }
    sleep(3); //讓程序停在這兒,方便top命令查看
       
    return 0;
}

運行結果如下:

[root@localhost test]# ./test    
cpus: 24
this process 2848 of running processor: 0
this process 2848 of running processor: 1
this process 2848 of running processor: 2
this process 2848 of running processor: 3

  其中syscall是一個系統調用,根據指定的參數number和所有系統調用的接口來確定調用哪個系統調用,用於用戶空間跟內核之間的數據交換。下面是syscall函數原型及一些常用的number:

//syscall - indirect system call
SYNOPSIS
       #define _GNU_SOURCE         /* See feature_test_macros(7) */
       #include <unistd.h>
       #include <sys/syscall.h>   /* For SYS_xxx definitions */

       int syscall(int number, ...);
       
/* sysconf( _SC_PAGESIZE );  此宏查看緩存內存頁面的大小;打印用%ld長整型。
sysconf( _SC_PHYS_PAGES ) 此宏查看內存的總頁數;打印用%ld長整型。
sysconf( _SC_AVPHYS_PAGES ) 此宏查看可以利用的總頁數;打印用%ld長整型。
sysconf( _SC_NPROCESSORS_CONF ) 查看cpu的個數;打印用%ld長整。
sysconf( _SC_NPROCESSORS_ONLN ) 查看在使用的cpu個數;打印用%ld長整。
(long long)sysconf(_SC_PAGESIZE) * (long long)sysconf(_SC_PHYS_PAGES) 計算內存大小。
sysconf( _SC_LOGIN_NAME_MAX ) 查看最大登錄名長度;打印用%ld長整。
sysconf( _SC_HOST_NAME_MAX ) 查看最大主機長度;打印用%ld長整。
sysconf( _SC_OPEN_MAX )  每個進程運行時打開的文件數目;打印用%ld長整。
sysconf(_SC_CLK_TCK) 查看每秒中跑過的運算速率;打印用%ld長整。*/

三、線程與cpu的綁定

  線程於進程的綁定方法大體一致,需要注意的是線程綁定於進程的區別是所用函數不一樣線程綁定用到下面兩個函數,跟進程類似就不做詳細說明,將當前線程綁定到0、1、2、3號cpu上示例如下:

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sched.h>

void *testfunc(void *arg)
{
    int i, cpus = 0;
    cpu_set_t mask;
    cpu_set_t get;

    cpus = sysconf(_SC_NPROCESSORS_CONF);
    printf("this system has %d processor(s)\n", cpus);
    
    CPU_ZERO(&mask);
    for (i = 0; i < 4; i++) { /*將0、1、2、3添加到集合中*/
        CPU_SET(i, &mask);
    }   

    /* 設置cpu 親和性(affinity)*/
    if (pthread_setaffinity_np(pthread_self(), sizeof(mask), &mask) < 0) {
        fprintf(stderr, "set thread affinity failed\n");
    }   
    
    /* 查看cpu 親和性(affinity)*/
    CPU_ZERO(&get);
    if (pthread_getaffinity_np(pthread_self(), sizeof(get), &get) < 0) {
        fprintf(stderr, "get thread affinity failed\n");
    }   

    /* 查看當前線程所運行的所有cpu*/
    for (i = 0; i < cpus; i++) {
        if (CPU_ISSET(i, &get)) {
            printf("this thread %d is running in processor %d\n", (int)pthread_self(), i); 
        }   
    }   
    sleep(3); //查看
    
    pthread_exit(NULL);
}
 
int main(int argc, char *argv[])
{
    pthread_t tid;
    if (pthread_create(&tid, NULL, (void *)testfunc, NULL) != 0) {
        fprintf(stderr, "thread create failed\n");
        return -1; 
    }   

    pthread_join(tid, NULL);
    return 0;
}

運行結果如下:

[root@localhost thread]# ./test                      
this system has 24 processor(s)
this thread 2812323584 is running in processor 0
this thread 2812323584 is running in processor 1
this thread 2812323584 is running in processor 2
this thread 2812323584 is running in processor 3

 

-end-


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM