2012年7月5日 星期四

Linux Interrupt 整理 (一)

  • 前言
CPU的速度很快,假設每每都必須等待週邊的硬體動作完成CPU才能繼續工作的話,效率未免太低了。因此,我們的系統設計了中斷(Interrupt),當有 event 發生(例如 工作完成)就觸發一個Interrupt告訴CPU,這時候CPU就會"馬上"去處理。這麼一來就可以避免等待時間的浪費。
我們可以針對裝置的Interrupt建立Handler,讓不同的Device Interrupt發生後去執行相對應的"程序" ,這個程序就稱為 Interrupt Service Routine(ISR)。如果沒有相對應的ISR則僅會回覆ACK而不作任何事。
傳統電腦是利用兩個Programmable Interrupt Controller (PIC)提供15組Interrupt Line連接Device和CPU。Device 觸發中斷請求(Interrupt Request)給PIC然後在傳至CPU,也因為數量有限所以很寶貴。現代的設計是使用Advanced Programmable Interrupt Controller(APIC) 
在Linux Kernel 中存在一個資料結構叫irq_desc 實作在kernel/irq用來鍊結IRQ及ISR,另外也建立Radix treer叫irq_desc_tree來存放irq_desc結構的變數。
  • 建立IRQ
要使用IRQ必須要向核心註冊。註冊的方式則是利用request_irq長相如下:
//include/linux/interrupt.h
/**
 *第一個參數是IRQ的號碼
 *第二個參數是ISR的pointer
 *第三個參數
 *    IRQF_SHARED         Interrupt is shared
 *    IRQF_SAMPLE_RANDOM  代表這個interrupt可以作為系統random的依據(有固定週期的interrupt
 *                        不適合當成random依據)
 *    IRQF_TRIGGER_*      Specify active edge(s) or level
 *第四個參數是顯示在例如/proc/interrupts的名稱
 *第五個參數用途可以參考http://www.cnblogs.com/ITmelody/archive/2012/05/12/2497018.html
 *一般直接填NULL
*/
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
     const char *name, void *dev)
{
    //request_threaded_irq第三個參數是irq_handler_t thread_fn
    return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}
從這裡我們也可以發現其實request_irq()會直接call request_threaded_irq()。因此我們也會看到有人就直接使用request_threaded_irq()。直接舉nVidia的mmc的例子。
//driver/mmc/host/sdhci-tegra.c
static irqreturn_t carddetect_irq(int irq, void *data)
{
 ...
}

static int __devinit sdhci_tegra_probe(struct platform_device *pdev)
{
    struct sdhci_host *host;
    ...
    rc = request_threaded_irq(gpio_to_irq(plat->cd_gpio), NULL,
     carddetect_irq,
     IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
     mmc_hostname(host->mmc), host);
    ...
}
上述的例子是利用GPIO的變化來當成Interrupt的條件。IRQ number是利用gpio_to_irq取得對應的IRQ;ISR是carddetect_irq(),觸發的條件是當此GPIO rising/falling都會觸發Interrupt。

當我們remove module的時候,我們可能需要free掉這個IRQ,這個時候就可以呼叫free_irq來達成。
static int __devexit sdhci_tegra_remove(struct platform_device *pdev)
{
    ...
    if (gpio_is_valid(plat->cd_gpio)) {
        free_irq(gpio_to_irq(plat->cd_gpio), host);
        ...
    }
    ...
}
  • ISR Top and Bottom Halves
Interrupt設計的目的就是不希望卡在攏長的處理。因此,收到中斷後,我們也會希望可以快速處理完要作的事情。然而,常常我們需要作的事情就是這麼多無法快速的做完造成系統的遲鈍。因此,Linux Kernel提出了將ISR拆成top half 跟bottom half。當Interrupt發生後一定要馬上作的事情就屬於Top half直接寫在原本的ISR,而可以晚點做的就屬於Bottom half。我們可以利用SoftIRQTaskletWorkqueu來排程bottom half的部份。下表比較了三種的差異。
下面我們分別介紹Tasklet及Workqueue。
 Tasklet
Tasklets 與Linux Kernel的timer類似,他們都在Interrupt time(Atomic time)執行而且會執行在schedule他的CPU上。不同於timer,我們無法要求tasklet的task在我們期望的時間執行 。同樣的,我們使用nVidia的mmc driver為例來說明Tasklet。
首先,我們必須先註冊一個tasklet,指定這個tasklet的名字、func及data。
//mmc/host/sdhci.c
static void sdhci_tasklet_card(unsigned long param)
{
   ...
}

int sdhci_add_host(struct sdhci_host *host){
   ...
/*
 * Init tasklet.
 *void tasklet_init(struct tasklet_struct *t,
 *                  void (*func)(unsigned long), unsigned long data)
 *簡單來說就是tasklet_init(name, func,data)
*/
    tasklet_init(&host->card_tasklet,
                 sdhci_tasklet_card, (unsigned long)host);
 
   ...
}

接著,這個task必須要在呼叫schedule_tasklet之後才會被排程執行。理所當然,我們會在top half執行完之後去啟動bottom half的部份。因此我們回到top half(carddetect_irq())看看我們如何去啟動bottom half。
//driver/mmc/host/sdhci-tegra.c
static irqreturn_t carddetect_irq(int irq, void *data)
{
    ...
    tasklet_schedule(&sdhost->card_tasklet);
    
    return IRQ_HANDLED;
};


Workqueue
Workqueue的部份則利用omap的mmc driver來舉例。如同tasklet,我們必須利用INIT_WORK(name,func)先註冊一個workqueue。以下面的範例,利用schedule_work()將這個workqueue交給一個叫"event"的公用work thread來執行。
首先我們來看註冊 workqueue的部份。
//driver/mmc/omap_hsmmc.c
static void omap_hsmmc_detect(struct work_struct *work)
{
   ...
}

static int __init omap_hsmmc_probe(struct platform_device *pdev)
{
   ...
   INIT_WORK(&host->mmc_carddetect_work, omap_hsmmc_detect);
   ...
}
而去啟動他的部份則是在top half的omap_hsmmc_cd_handler()則是...
//driver/mmc/omap_hsmmc.c

static irqreturn_t omap_hsmmc_cd_handler(int irq, void *dev_id)
{
 struct omap_hsmmc_host *host = (struct omap_hsmmc_host *)dev_id;

 if (host->suspended)
  return IRQ_HANDLED;
 //啟動Workqueue 
 schedule_work(&host->mmc_carddetect_work);

 return IRQ_HANDLED;
}

  • Reference
Linux Kernel Development Third Edition 
Linux Device Drivers, Third Edition
Intel APIC Architecture
Interrupt_request
Essential Linux Device Drivers

2012年7月4日 星期三

GPIO整理-GPIOLIB (二)

上一篇介紹了GPIO的基礎,在這篇將詳細介紹 GPIOLIB
  • 前言
在以前,假設我們chip上面都有提供GPIO。假設我們今天要 call soc上面的GPIO我們可以使用gpio_direction_output()。但是如果要使用audio chip上面的GPIO以下圖為,我們可能就得呼叫audio_gpio_direction_output() depends on chip 的driver實作。
為什麼呢?因為每個chip如何去控制他們的GPIO作法可能會不一樣,並且要填的memory位址也不一樣。這麼一來會造成什麼現象呢?如果chip一多,要分別控制上面的GPIO 我們可能就會看到一堆 audio_gpio_direction_output、 pmu_gpio_direction_output、 charger_gpio_direction_output。而且,以gpio_direction_output為例,可能每一個driver的實作都會有一些重複的code例如檢查是否valid等等。
因此,GPIOLIB的引入可以解決這個問題。每一個chip都使用gpio_chip結構來表示,每一個chip driver實作可以定義GPIO的個數、號碼及相關的callback function。 
以gpio_direction_output為例。當我們呼叫gpio_direction_output(number)之後,gpio_direction_output實做在GPIOLIB中,它會去檢查這個number的GPIO是屬於哪個chip然後去call 該chip的driver的request實作。
  • GPIOLIB分析
註冊gpio_chip 
我們從nVidia的git抓下來的kernel裡面隨便找driver/gpio/wm8994-gpio.c為例子trace一下。他的probe如下:
static struct gpio_chip template_chip = {
 .label   = "wm8994",
 .owner   = THIS_MODULE,
/**
 *如果執行gpio_direction_input的GPIO number屬於這個chip
 *則執行wm8994_gpio_direction_in這個callback function
 *以下同義
*/
 .direction_input = wm8994_gpio_direction_in,
 .get   = wm8994_gpio_get,
 .direction_output = wm8994_gpio_direction_out,
 .set   = wm8994_gpio_set,
 .dbg_show  = wm8994_gpio_dbg_show,
 .can_sleep  = 1,
};

static int __devinit wm8994_gpio_probe(struct platform_device *pdev)
{
    struct wm8994 *wm8994 = dev_get_drvdata(pdev->dev.parent);
    //取得platform_device定義的platform_data
    struct wm8994_pdata *pdata = wm8994->dev->platform_data;
    struct wm8994_gpio *wm8994_gpio;
    int ret;
    wm8994_gpio = kzalloc(sizeof(*wm8994_gpio), GFP_KERNEL);
    if (wm8994_gpio == NULL)
 return -ENOMEM;
    
    wm8994_gpio->wm8994 = wm8994;
    /**
     *我們剛剛有說每一個chip使用gpio_chip結構表示。
     *上面宣告了template_chip的gpio_chip結構。
    */
    wm8994_gpio->gpio_chip = template_chip;
    wm8994_gpio->gpio_chip.ngpio = WM8994_GPIO_MAX;
    wm8994_gpio->gpio_chip.dev = &pdev->dev;

    //取得gpio_base。意思是這個chip的GPIO number從多少開始編號 
    if (pdata && pdata->gpio_base)
 wm8994_gpio->gpio_chip.base = pdata->gpio_base;
    else
 wm8994_gpio->gpio_chip.base = -1;
    
    //將這個gpio_chip 註冊進去。待會將分析gpiochip_add
    ret = gpiochip_add(&wm8994_gpio->gpio_chip);
    if (ret < 0) {
 dev_err(&pdev->dev, "Could not register gpiochip, %d\n",
         ret);
 goto err;
    }

    platform_set_drvdata(pdev, wm8994_gpio);
    return ret;

err:
 kfree(wm8994_gpio);
 return ret;
}
接著我們看一下gpiochip_add() 做了什麼事。
//擷取gpio_desc的定義。gpio_desc定義了GPIO是屬於哪個chip,他的flag及label。
struct gpio_desc {
 struct gpio_chip *chip;
 unsigned long  flags;
#ifdef CONFIG_DEBUG_FS
 const char  *label;
#endif
};
//gpio_desc陣列存放著所有gpio的定義
static struct gpio_desc gpio_desc[ARCH_NR_GPIOS];
int gpiochip_add(struct gpio_chip *chip)
{
 unsigned long flags;
 int  status = 0;
 unsigned id;
 int  base = chip->base;

 if ((!gpio_is_valid(base) || !gpio_is_valid(base + chip->ngpio - 1))
   && base >= 0) {
  status = -EINVAL;
  goto fail;
 }

 spin_lock_irqsave(&gpio_lock, flags);

 if (base < 0) {
  base = gpiochip_find_base(chip->ngpio);
  if (base < 0) {
   status = base;
   goto unlock;
  }
  chip->base = base;
 }

 /**
          *these GPIO numbers must not be managed by another gpio_chip 
          *換句話說就是沒有重複到其他gpio_chip定義的號碼
        */
 for (id = base; id < base + chip->ngpio; id++) {
  if (gpio_desc[id].chip != NULL) {
   status = -EBUSY;
   break;
  }
 }
 if (status == 0) {
     for (id = base; id < base + chip->ngpio; id++) {
         //將這個chip上的所有GPIO的定義加入gpio_desc
                gpio_desc[id].chip = chip;

  /* REVISIT:  most hardware initializes GPIOs as
   * inputs (often with pullups enabled) so power
   * usage is minimized.  Linux code should set the
   * gpio direction first thing; but until it does,
   * we may expose the wrong direction in sysfs.
   */
  gpio_desc[id].flags = !chip->direction_input
   ? (1 << FLAG_IS_OUT)
   : 0;
     }
 }

 of_gpiochip_add(chip);

unlock:
 spin_unlock_irqrestore(&gpio_lock, flags);

 if (status)
  goto fail;

 status = gpiochip_export(chip);
 if (status)
  goto fail;

 return 0;
fail:
 /* failures here can mean systems won't boot... */
 pr_err("gpiochip_add: gpios %d..%d (%s) failed to register\n",
  chip->base, chip->base + chip->ngpio - 1,
  chip->label ? : "generic");
 return status;
}
EXPORT_SYMBOL_GPL(gpiochip_add);
到這裡,我們的kernel已經多了wm8994這個chip的GPIO可以使用了。但是從我們呼叫gpio_direction_input是怎麼跑到driver定義的wm8994_gpio_direction_in呢?
GPIOLIB與CHIP DRIVER結合 
我們看一下gpio_direction_input() 的code。
int gpio_direction_input(unsigned gpio)
{
 unsigned long  flags;
 struct gpio_chip *chip;
 struct gpio_desc *desc = &gpio_desc[gpio];
 int   status = -EINVAL;

 spin_lock_irqsave(&gpio_lock, flags);
        //先檢查這個gpio是否valid
 if (!gpio_is_valid(gpio))
  goto fail;
        //找到這個gpio屬於哪個chip的
 chip = desc->chip;
        //如果chip不存在或這個gpio不能設為input則return fail
 if (!chip || !chip->get || !chip->direction_input)
  goto fail;
        
        //下面兩行檢查該GPIO的編號是否在該chip的範圍內
 gpio -= chip->base;
 if (gpio >= chip->ngpio)
  goto fail;
 status = gpio_ensure_requested(desc, gpio);
 if (status < 0)
  goto fail;

 /* now we know the gpio is valid and chip won't vanish */

 spin_unlock_irqrestore(&gpio_lock, flags);

 might_sleep_if(chip->can_sleep);

 if (status) {
                //呼叫chip定義的request檢查是否可用
  status = chip->request(chip, gpio);
  if (status < 0) {
   pr_debug("GPIO-%d: chip request fail, %d\n",
    chip->base + gpio, status);
   /* and it's not available to anyone else ...
    * gpio_request() is the fully clean solution.
    */
   goto lose;
  }
 }
        //如果是可用的,call chip的direction_input()
 status = chip->direction_input(chip, gpio);
 if (status == 0)
  clear_bit(FLAG_IS_OUT, &desc->flags);

 trace_gpio_direction(chip->base + gpio, 1, status);
lose:
 return status;
fail:
 spin_unlock_irqrestore(&gpio_lock, flags);
 if (status)
  pr_debug("%s: gpio-%d status %d\n",
   __func__, gpio, status);
 return status;
}
EXPORT_SYMBOL_GPL(gpio_direction_input);
至此,應該就都串起來了吧!簡單來說就如下圖:
  • Reference

2012年7月3日 星期二

GPIO 整理(一)

  • 什麼是GPIO?
GPIO全名是General Purpose Input/Output。他是一個有彈性的軟體可控制的數位信號。在許多chip例如CPU或是PMU(Power Management Unit)等等的裝置上都提供了GPIO的設計。每一個GPIO用一個bit(0/1) 來表示他的狀態。GPIO可以拿來做什麼事呢?
  1. Output Value
      輸出的值是可寫的(high=1, low=0)。例如果們可以利用 GPIO來控制LED的亮/暗。
  2. Input Value
      輸入的值是可讀的(1/0)。當然我們也可以藉由讀取GPIO來知道現在例如 LED的亮/暗
  3. Used as IRQ signal
      可以把GPIO拿來當IRQ的信號,例如利用GPIO來判斷 SDcard的"插入/拔出"的動作或是按鍵的"按/放"
  4. Alternative Use
      或甚至,我們可以利用GPIO來實做成SPI等等應用
  • 使用GPIO
在Driver裡面如果要使用GPIO可以簡單分成五個步驟:
  1. Include Header
      在Driver裡面include linux/gpio.h
      #include <linux/gpio.h>
  2. Check Valid(Optional)
      每一個chip上面的GPIO數量是有限制的(而且GPIO號碼一定>=0),為了防止指定的GPIO號碼是錯的,可以先使用gpio_is_valid來檢查。
      int gpio_is_valid(int number);//return true if valid.
  3. Request GPIO(Optional, but recommended)
      gpio_request 可以檢查GPIO number是否超出範圍或<0 及 指定的GPIO是否正在使用。有時候別的地方也控制著同一個GPIO 可能會發生程式預期外的結果
      /**
       *return -EINVAL if GPIO is not valid(same as gpio_is_valid)
       *return -EBUSY if GPIO is already used
       *return 0 if GPIO if fine to use
       *因此我們只要檢查回傳質是否>=0就可以判斷是否可用。
       *第一個參數是填入GPIO的號碼、第二個參數是想要對這個GPIO的命名。
       *$ cat /sys/kernel/debug/gpio就可以看到 
      */
      int gpio_request(unsigned gpio, const char *label);
  4. Write/Read Value
      接著我們選擇要讀值還是寫值。
      //return 1 or 0
      int gpio_direction_input(unsigned gpio);
      /**
       *gpio_direction_output會作許多檢查,例如該GPIO是否可以設high?low?
       *也會呼叫gpio_is_valid來檢查是否可用等等
       *第一個參數是GPIO號碼
       *第二個參數是要寫入的值(0/1)
      */
      int gpio_direction_output(unsigned gpio, int value);
  5. Free GPIO
      當使用完GPIO之後利用gpio_free來釋放GPIO 。
      int gpio_free(unsigned gpio);
將上述串起來以對CARD_DETECT_GPIO寫入1為例:
#include <linux/gpio.h>

...

int ret = gpio_request(CARD_DETECT_GPIO, "CARD_DETECT");
if (ret<0)
    pr_err("%s: gpio_request failed for gpio %d\n",
         __func__, CARD_DETECT_GPIO);
else
    gpio_direction_output(CARD_DETECT_GPIO, 1);
gpio_free(CARD_DETECT_GPIO);
  • 其他函式
/**
 *當我們對某個GPIO設定direction_output之後
 *我們可以直接透過gpio_set_value去改變output的值
*/
void gpio_set_value(unsigned gpio, int value)


/**
 *某些裝置像omap允許設置debounce time(不是所有裝置都有支援)。
*/
int gpio_set_debounce(unsigned gpio, unsigned debounce)


/**
 *對於外部的GPIO controller裝置可能是透過SOC的I2C或其他bus來連接,要
 *控制裝置上的GPIO就必須透過I2C/其他bus來傳送命令。如果這個裝置是sleepable
 *這個時候就必須使用_cansleep了。
*/
int gpio_get_value_cansleep(unsigned gpio);
void gpio_set_value_cansleep(unsigned gpio, int value);


/**
 *gpio_request_*簡化了gpio_request和gpio_direction_output/input。
 *透過一個function就可以完成 request及設定direction及初始值。
 *第一個參數和第三個參數同gpio_request, 第二個參數如下:
 *     GPIOF_DIR_IN         - to configure direction as input
 *     GPIOF_OUT_INIT_LOW - configured as output, initial level LOW
 *     GPIOF_OUT_INIT_HIGH - configured as output, initial level HIGH
*/

//request/set_direction one GPIO in a single call
int gpio_request_one(unsigned gpio, unsigned long flags, const char *label);


/**
 *如果我們要同時request多個GPIO的話,gpiolib也定義了一個GPIO的struct
 *可讓我們建成一個array丟進gpio_request_array。Struct的組成如下
*/
struct gpio {
    unsigned     gpio;
    unsigned long   flags;
    const char     *label;
};

//request/set_direction multi-GPIO in a single call
int gpio_request_array(struct gpio *array, size_t num);

//free multi-GPIO in a single call
void gpio_free_array(struct gpio *array, size_t num);
下面的範例示範了如何使用gpio_request_one及 同時設定多組GPIO
static struct gpio leds_gpios[] = {
    { 32, GPIOF_OUT_INIT_HIGH, "Power LED" }, /* default to ON */
    { 33, GPIOF_OUT_INIT_LOW,  "Green LED" }, /* default to OFF */
    { 34, GPIOF_OUT_INIT_LOW,  "Red LED"   }, /* default to OFF */
    { 35, GPIOF_OUT_INIT_LOW,  "Blue LED"  }, /* default to OFF */
    { ... },
};
//request/set_direction in a single call
err = gpio_request_one(31, GPIOF_IN, "Reset Button");
if (err)
 ...

//request/set_direction multi-GPIO in a single call
err = gpio_request_array(leds_gpios, ARRAY_SIZE(leds_gpios));
if (err)
    ...
gpio_free_array(leds_gpios, ARRAY_SIZE(leds_gpios));

  • Reference:

2012年6月28日 星期四

gcc strict-aliasing是什麼?

  • 前言 
思考下面這一段code
void foo(int *i1, int *i2, int *res)
{
    for(int i=0;i<10;i++){
        *res += *i1 + *i2;
    }
}
如果想要讓這段code變快,我們可能會想要這樣作
void foo(int *i1, int *i2, int *res)
{
    int tmp=*i1 + *i2;
    for(int i=0;i<10;i++)
        *res += tmp;
    }
}
但考慮到當呼叫端是如下就會發生問題:
foo(&v1,&v3,&v3);
我們永遠不知道caller 到底會丟什麼進來。因此,這樣的code是無法最佳化的。那如果是下面這個例子呢?
void foo(int32_t *i1, int32_t *i2, int64_t *res)
{
    for(int i=0;i<10;i++){
        *res += *i1 + *i2;
    }
}
res的type是int64_t理論上應該就不會有問題了吧?如果呼叫端是:
foo(&v1,&v2,(int64_t *)&v3);
就會發生我們預期外的錯誤了。
  • 什麼是strict-aliasing?
為了讓上面這種狀況不會發生最佳化後的結果是我們預期外的,我們就啟用strict-aliasing來避免"不同型態"的指標指向相同的address除非兩種型態是類似的。例如:unsigned int可以alias int但不可以alias double。這麼我們就可以避免上述的情況發生。 
  • 如何啟用strict-aliasing?
在compile時如果我們開啟-O2,-O3,-Os或直接加上-fstrict-aliasing就會啟動fstrict-aliasing的選項。
  • 簡單的方法避免Werror=strict-aliasing
假設我們的程式中需要作轉型例如
int foo() {
    short a[2];
    a[0]=1;
    a[1]=0;
    int *i=(int*)&a;
}
我們可以用union來表達
union union_a {
    int i;
    short s[2];
};
int foo() {
    union union_a u;
    u.i=1;
    u.s[0]=1;
    u.s[1]=0;
}
這樣就不會有warning: dereferencing type-punned pointer will break strict-aliasing rules的warning了。

2012年6月27日 星期三

Compile AOSP with Linaro-4.7 Note

Linaro是一個非營利的組織,主要貢獻了在不同的嵌入式平台的toolchain, kernel, platform移植或最佳化。在應用Linaro對AOSP(Android Opensource Project)最佳化的patch及使用Linaro-4.7的toolchain時會遇到一些問題的整理。
首先,參考Linaro的patch最直接的最佳化方法就是使用-O3以及strict-aliasing來作最佳化如下 :
------------------------ core/combo/TARGET_linux-arm.mk ------------------------
index 9000b86..90719b9 100644
@@ -66,7 +66,7 @@ endif
  
 TARGET_NO_UNDEFINED_LDFLAGS := -Wl,--no-undefined
  
-TARGET_arm_CFLAGS :=    -O2 \
+TARGET_arm_CFLAGS :=    -O3 \
                         -fomit-frame-pointer \
                         -fstrict-aliasing    \
下載Linaro-4.7之後,我們可以利用
$ export TARGET_TOOLS_PREFIX=[Linaro-4.7 path]/bin/arm-eabi-
來讓AOSP切換toolchain編譯。

問題列表:
  1. error: template with C linkage
      原因:錯誤使用 -isystem。例如在AOSP 的build/core/definitions.mk
               Ref.
      解法 :
      - $(addprefix -isystem ,\
      + $(addprefix -I ,\
            $(if $(PRIVATE_NO_DEFAULT_COMPILER_FLAGS),, \
      -         $(filter-out $(PRIVATE_C_INCLUDES), \
      +         $(filter-out bionic/% $(PRIVATE_C_INCLUDES), \
                    $(PRIVATE_TARGET_PROJECT_INCLUDES) \
                    $(PRIVATE_TARGET_C_INCLUDES)))) \
      + $(addprefix -isystem ,\
      +     $(if $(PRIVATE_NO_DEFAULT_COMPILER_FLAGS),, \
      +         $(filter bionic/%, \
      +             $(filter-out $(PRIVATE_C_INCLUDES), \
      +                 $(PRIVATE_TARGET_PROJECT_INCLUDES) \
      +                 $(PRIVATE_TARGET_C_INCLUDES))))) \
  2. unrecognized command line option '-mno-thumb'
      原因: gcc-4.7似乎沒有-mno-thumb 
      解法:替換成-marm 
  3. -Werror=strict-prototypes
      原因: 在C的話,foo()代表允許無窮的參數,而foo(void)代表"沒有"參數。但是在C++這兩種是一樣的。
      解法:
            將foo()替換成foo(void)
      
  4. dereferencing type-punned pointer will break strict-aliasing rules (Werror=strict-aliasing)
  5. 遇到
    error: ‘truncate’ was not declared in this scope
    error: ‘sleep’ was not declared in this scope
    error: ‘pipe’ was not declared in this scope
    error: there are no arguments to 'offsetof' that depend on a template
    parameter, so a declaration of 'offsetof' must be available
      原因:因為很多標準的C++函式庫的header 都沒有 include unistd.h了。           
      Ref.
      解法 : 加入#include <unistd.h>
  6. 遇到
    error: redeclaration of ‘int i’
    error: ‘int i’ previously declared here
      原因: 重複使用變數,例如
      void f(int);
      
      int main()
      {
          for (int i=0;;++i)
          {
            int i=5;
            f(i);
          }
          return 0;
       }
      Ref. 解法: 不要重複使用"i"
  7. unable to find string literal operator
      原因:  參考Ref. 發生的情形例如
      const char *p = "foobar"__TIME__;
      解法:在macro和 字串中間加個空格
陸續補完中...

2012年6月6日 星期三

Tegra 3 Matrix Mul Performance Test

今天以矩陣相乘為例,測試不同的編譯參數對效能的影響。有些編譯參數應該是沒有直接fit 最佳化的狀況因此最佳化結果不明顯。但這只是初期的測試,未來要更深入的研究一下每一個編譯參數實際上是對什麼case做最佳化。 

測試的數據是以arm-eabi-g++來編譯,後來有試過改成可以讓arm-eabi-gcc來編譯的版本,測試結果其實差不多。以下是測試的程式及環境


  • 測試程式
      #include <sys/time.h>
      #include <math.h>
      #include <stdio.h>
      #include <stdlib.h>
      
      using namespace std;
      
      long double timer[10];
      int matrix_size;
      /*Timer*/
      long double high_prec_time(){
              timeval tv;
              gettimeofday(&tv,0);
              return (long double)tv.tv_sec+tv.tv_usec*1e-6;
      }
      void   set_timer(int k=0){
              timer[k]=high_prec_time();
      }
      double get_timer(int k=0){
              return (double)(high_prec_time()-timer[k]);
      }
      
      /*Matrix Mul*/
      template <class T>
      void _matrix_mul(T *matrixA,T *matrixC){
           T tmpA;
          for(int i=0;i<matrix_size;i++){
              for(int j=0;j<matrix_size;j++){
                  for(int k=0;k<matrix_size;k++){
                      tmpA=*(matrixA+k*matrix_size+j);
                      //cout<<tmpA<<endl;
                      *(matrixC+(i*matrix_size+j)) += tmpA*tmpA;
                  }
              }
          }
      }
      void int_matrix_mul(int matrix_size){
          int matrixA[matrix_size][matrix_size];
          int matrixC[matrix_size][matrix_size];
          //srand(time(0));
          for(int i=0;i<matrix_size;i++){
              for(int j=0;j<matrix_size;j++){
                  matrixA[i][j]=rand()%RAND_MAX;
                  //cout<<matrixA[i][j]<<RAND_MAX<<endl;
                  matrixC[i][j]=0;
              }
          }
          _matrix_mul(&matrixA[0][0],&matrixC[0][0]);
      }
      
      void float_matrix_mul(int matrix_size){
          float matrixA[matrix_size][matrix_size];
          float matrixC[matrix_size][matrix_size];
          //srand(time(0));
          for(int i=0;i<matrix_size;i++){
              for(int j=0;j<matrix_size;j++){
                  matrixA[i][j]=(float)(rand()%RAND_MAX);
                  matrixC[i][j]=0;
              }
          }
          _matrix_mul(&matrixA[0][0],&matrixC[0][0]);
      }
      void double_matrix_mul(int matrix_size){
          double matrixA[matrix_size][matrix_size];
          double matrixC[matrix_size][matrix_size];
          //srand(time(0));
          for(int i=0;i<matrix_size;i++){
              for(int j=0;j<matrix_size;j++){
                  matrixA[i][j]=(double)(rand()%RAND_MAX);
                  matrixC[i][j]=0;
              }
          }
          _matrix_mul(&matrixA[0][0],&matrixC[0][0]);
      }
      
      int main(){
          matrix_size=512;
          set_timer(0);
          int_matrix_mul(matrix_size);
          printf("Integer Matrix Mul spend time: %f",get_timer(0));
          set_timer(0);
          float_matrix_mul(matrix_size);
          printf("Float Matrix Mul spend time: %f",get_timer(0));
          set_timer(0);
          double_matrix_mul(matrix_size);
          printf("Double Matrix Mul spend time: %f",get_timer(0));
      
      }
      
  • 測試環境
      硬體: 
              SOC: TEGRA3
              RAM: 1G 
      軟體:
              ICS 4.0.4
              GCC: arm-eabi-g++ 4.4.3

    測試的方式是每一個編譯出來的執行檔會測試四次,去掉特別大或特別小的值取出三個。測試分別對Integer, Float及 Double的size為256 的square matrix測試。下圖則是測試的結果。

Compiler 做了哪些最佳化?

我們都知道在編譯的時候可以下 O1 O2 等等的最佳化設定,但這些設定到底實際上做了哪些最佳化呢?我們可以利用下面這個指令知道:

$ arm-eabi-gcc -c -Q -O3 --help=optimizers 

以上面這個例子來說,當我們下-O3的時候實際上做了:

The following options control optimizations:
  -O                    
  -Os                           
  -falign-functions             [enabled]
  -falign-jumps                 [enabled]
  -falign-labels                [enabled]
  -falign-loops                 [disabled]
  -fargument-alias              [enabled]
  -fargument-noalias            [disabled]
  -fargument-noalias-anything   [disabled]
  -fargument-noalias-global     [disabled]
  -fasynchronous-unwind-tables   [disabled]
  -fbranch-count-reg            [enabled]
  -fbranch-probabilities        [disabled]
  -fbranch-target-load-optimize  [disabled]
  -fbranch-target-load-optimize2  [disabled]
  -fbtr-bb-exclusive            [disabled]
  -fcaller-saves                [enabled]
  -fcommon                      [enabled]
  -fconserve-stack              [disabled]
  -fcprop-registers             [enabled]
  -fcrossjumping                [enabled]
  -fcse-follow-jumps            [enabled]
  -fcse-skip-blocks             [disabled]
  -fcx-fortran-rules            [disabled]
  -fcx-limited-range            [disabled]
  -fdata-sections               [disabled]
  -fdce                         [enabled]
  -fdefer-pop                   [enabled]
  -fdelayed-branch              [disabled]
  -fdelete-null-pointer-checks   [enabled]
  -fdominance-check             [disabled]
  -fdse                         [enabled]
  -fearly-inlining              [enabled]
  -fexceptions                  [disabled]
  -fexpensive-optimizations     [enabled]
  -ffinite-math-only            [disabled]
  -ffloat-store                 [disabled]
  -fforward-propagate           [enabled]
  -fgcse                        [enabled]
  -fgcse-after-reload           [enabled]
  -fgcse-las                    [disabled]
  -fgcse-lm                     [enabled]
  -fgcse-sm                     [disabled]
  -fgraphite-identity           [disabled]
  -fguess-branch-probability    [enabled]
  -fhandle-exceptions           
  -fif-conversion               [enabled]
  -fif-conversion2              [enabled]
  -finline-functions            [enabled]
  -finline-functions-called-once  [enabled]
  -finline-small-functions      [enabled]
  -fipa-cp                      [enabled]
  -fipa-cp-clone                [enabled]
  -fipa-matrix-reorg            [disabled]
  -fipa-pta                     [disabled]
  -fipa-pure-const              [enabled]
  -fipa-reference               [enabled]
  -fipa-type-escape             [disabled]
  -fivopts                      [enabled]
  -fjump-tables                 [enabled]
  -flimit-hot-components        [disabled]
  -floop-block                  [disabled]
  -floop-interchange            [disabled]
  -floop-strip-mine             [disabled]
  -fmath-errno                  [enabled]
  -fmerge-all-constants         [disabled]
  -fmerge-constants             [enabled]
  -fmodulo-sched                [disabled]
  -fmove-loop-invariants        [enabled]
  -fnon-call-exceptions         [disabled]
  -fomit-frame-pointer          [enabled]
  -foptimize-register-move      [enabled]
  -foptimize-sibling-calls      [enabled]
  -fpack-struct                 [disabled]
  -fpack-struct=        
  -fpeel-loops                  [disabled]
  -fpeephole                    [enabled]
  -fpeephole2                   [enabled]
  -fpessimistic-inline-stack-limit  [enabled]
  -fpredictive-commoning        [enabled]
  -fprefetch-loop-arrays        [disabled]
  -fprofile-dump                [disabled]
  -frecord-options-in-elf       [disabled]
  -freg-struct-return           [enabled]
  -fregmove                     [enabled]
  -frename-registers            [enabled]
  -freorder-blocks              [enabled]
  -freorder-blocks-and-partition  [disabled]
  -freorder-functions           [enabled]
  -frerun-cse-after-loop        [enabled]
  -freschedule-modulo-scheduled-loops  [disabled]
  -frounding-math               [disabled]
  -frtti                        
  -fsample-profile              [disabled]
  -fsample-profile-use-entry    [disabled]
  -fsched-interblock            [enabled]
  -fsched-spec                  [enabled]
  -fsched-spec-load             [disabled]
  -fsched-spec-load-dangerous   [disabled]
  -fsched-stalled-insns         [disabled]
  -fsched-stalled-insns-dep     [enabled]
  -fsched2-use-superblocks      [disabled]
  -fsched2-use-traces           [disabled]
  -fschedule-insns              [enabled]
  -fschedule-insns2             [enabled]
  -fsection-anchors             [enabled]
  -fsel-sched-pipelining        [disabled]
  -fsel-sched-pipelining-outer-loops  [disabled]
  -fsel-sched-reschedule-pipelined  [disabled]
  -fselective-scheduling        [disabled]
  -fselective-scheduling2       [disabled]
  -fshort-double                
  -fshort-enums                 
  -fshort-wchar                 
  -fsignaling-nans              [disabled]
  -fsigned-zeros                [enabled]
  -fsingle-precision-constant   [disabled]
  -fsplit-ivs-in-unroller       [enabled]
  -fsplit-wide-types            [enabled]
  -fstrict-aliasing             [enabled]
  -fstrict-enum-precision       [enabled]
  -fthread-jumps                [enabled]
  -fno-threadsafe-statics       
  -ftoplevel-reorder            [enabled]
  -ftrapping-math               [enabled]
  -ftrapv                       [disabled]
  -ftree-builtin-call-dce       [enabled]
  -ftree-ccp                    [enabled]
  -ftree-ch                     [enabled]
  -ftree-copy-prop              [enabled]
  -ftree-copyrename             [enabled]
  -ftree-cselim                 [enabled]
  -ftree-dce                    [enabled]
  -ftree-dominator-opts         [enabled]
  -ftree-dse                    [enabled]
  -ftree-fre                    [enabled]
  -ftree-loop-distribution      [disabled]
  -ftree-loop-im                [enabled]
  -ftree-loop-ivcanon           [enabled]
  -ftree-loop-linear            [disabled]
  -ftree-loop-optimize          [enabled]
  -ftree-lr-shrinking           [enabled]
  -ftree-lrs                    [disabled]
  -ftree-pre                    [enabled]
  -ftree-reassoc                [enabled]
  -ftree-scev-cprop             [enabled]
  -ftree-sink                   [enabled]
  -ftree-sra                    [enabled]
  -ftree-switch-conversion      [enabled]
  -ftree-ter                    [enabled]
  -ftree-vect-loop-version      [enabled]
  -ftree-vectorize              [enabled]
  -ftree-vrp                    [enabled]
  -funit-at-a-time              [enabled]
  -funroll-all-loops            [disabled]
  -funroll-loops                [disabled]
  -funsafe-loop-optimizations   [disabled]
  -funsafe-math-optimizations   [disabled]
  -funswitch-loops              [enabled]
  -funwind-tables               [disabled]
  -fvar-tracking                [enabled]
  -fvar-tracking-uninit         [disabled]
  -fvariable-expansion-in-unroller  [disabled]
  -fvect-cost-model             [disabled]
  -fvpt                         [disabled]
  -fweb                         [enabled]
  -fwhole-program               [disabled]
  -fwrapv                       [disabled]