2012年3月8日 星期四

Android 開發教學筆記 - Renderscript Parallel Computing 之 Define a structure for your data

雖然網路上有許多 Renderscript 處理繪圖運算的範例,但它不只可以作繪圖加速還可以作平行運算,在 PandaBoard ES Rev B 板子上,發現雙核心 CPU 的架構下,似乎是沒問題的。然而,網路上場看得範例都是處理圖檔,讀檔的資料結構很簡單,就是最基本的 Bitmap 而已,於是花了不少功夫推敲,才找到如何定義自己的資料結構進行平行運算!


作法很簡單,先把你想要的資料結構定義在 Renderscript (myscript.rs) 檔案內,如 C 語言的 structure:


#pragma version(1)
#pragma rs java_package_name(com.example.android.rs.MyParallelCompute)


#include "rs_graphics.rsh"


struct MyDataIn {
        int a;
        int b;
};


//struct MyDataOut {
//         int sum;
//};
//void root(const struct MyDataIn *in, struct MyDataOut* out) {


void root(const struct MyDataIn *in, int* out) {
        //out->sum = (int)a+b;
        *out = in->a + in->b;
        rsDebug("myscript a=", (int) in->a);
        rsDebug("myscript b=", (int) in->b);
}


如此一來,編譯後自動產生 ScriptField_MyDataIn.java (gen/com.example.android.rs.MyParallelCompute裡),之後在 Java 端撰寫程式時,使用這個 class 來包裝你的資料:


private void createScript() {
        mRS = RenderScript.create(this);

        int myDataCount = 5;


        ScriptField_MyDataIn in = new ScriptField_MyDataIn(mRS,myDataCount);
        //ScriptField_MyDataOut out = new ScriptField_MyDataOut(mRS,myDataCount,myDataCount);


        // initial data
        for( int i=0 ; i<myDataCount ; ++i) {
                in.set_a(i, i, false);
                in.set_b(i, i*2, false);
        }
        in.copyAll();


        //Allocation dataOut = Allocation.createTyped(mRS, out.getType());
        Allocation dataOut = Allocation.createSized(mRS, Element.I32(mRS), myDataCount);


        // call renderscript
        mScript = new ScriptC_myscript(mRS, getResources(), R.raw.myscript);
        mScript.forEach_root(in.getAllocation(),dataOut);


        //byte []x = new byte[ ScriptField_MyDataOut.Item.sizeof * myDataCount ];
        //dataOut.copyTo(x);


        int[] x = new int[myDataCount];
        dataOut.copyTo(x);


        // result data
        for( int i=0 ; i<myDataCount ; ++i) {
                //Log.e("MyParalle","(a,b,sum)=("+in.get_a(i)+"+"+in.get_b(i)+"="+out.get_sum(i)+")");
                Log.e("MyParalle","(a,b,sum)=("+in.get_a(i)+"+"+in.get_b(i)+"="+x[i]+")");
        }
}


此例很簡單,使用平行運算把輸入的資料(struct MyDataIn)進行相加後輸出(int)。紅色是把資料轉成 RS 的輸入,綠色則是用來儲存運算結果,而藍色則是把綠色取得的資料,轉成在 Java 常用的結構。


幾個小筆記:



  • 用 forEach_root 進行平行運算,輸入的資料個數要跟輸出個數一樣才能使用,在此資料個數就是 myDataCount,而從 Java 傳遞給 forEach_root 的參數輸入跟輸出必須是 Allocation 型態

  • 想要自訂結構,請在 *.rs 先定義(如 struct MyDataIn),接著編譯後,則可以再 Java 中使用(如 ScriptField_MyDataIn)

  • 在 Java 中使用 ScriptField_* 進行資料初始化後,想要把它變成有效的 Allocation 時,記得先做 copyAll(),不然則是每次設值時,最後一個 copyNow 參數設定成 true,詳情起看 ScriptField_*.java 查閱實作

  • 運算後的資料也是 Allocation,必須把它轉成自己的資料結構,由於 Allocation 目前只支援輸出(copyTo)成 int [], short [], float [], byte[] 和 Bitmap 等,所以建議先以這些結構來設計,不然輸出還要花心力去做轉換,希望之後的架構可以支援直接輸出成自訂結構

  • Allocation 初始化有不少 Element.XXX 函式可以使用,例如 rs 用 void root( uchar4 *in ) 的話,那就用 Element.U8_4(mRS) 來對應


2012年3月4日 星期日

PayPal 使用心得


最近試用了一下 PayPal,覺得挺方便的,可以用在國際交易方面,例如購買商品等等。用法十分簡單,只要依自己習慣的貨幣存入帳戶,接著進行交易時,可以匯出對方貨幣,在按幾步後,就完成交易了!至於手續費的部分,可能還要查閱一下,以我這次用買家(付錢)的角度來看,看起來沒有任何手續費,但是,魔鬼都藏在細節裡,其實也存在一種手續費叫作「匯率」。


例如:


在 PayPal 存了美金 20 元,想要購買港幣 100 元的東西,使用 PayPal 需要支付 13.23 美金,過程中沒有任何手續費。


那匯率是多少?


1 美金 = 7.56 港幣


但問 Google 可以查到美金港幣的匯率是 1:7.76 左右,因此等於每一塊美金少了 0.2 港幣的價值,因此變相的手續費就在這裡。


目前不太清楚金額大小會不會影響匯率,如果匯率不會因金額大而變好的話,那變相的手續費還滿可觀的 Orz 


Android 開發筆記 - 實作 Mono 照片特效 @ Ubuntu 10.04

tw.study.rs_screenout


之前接觸 Renderscript - HelloCompute 時,第一次了解 Mono 特效是怎樣做的。這次則是要來筆記純用 Java 和 NDK 作的方式。


環境概述(以 Renderscript - HelloCompute 架構為例):


private Bitmap mBitmapIn;
private Bitmap mBitmapOut;


private Bitmap loadBitmap(int resource) {
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inPreferredConfig = Bitmap.Config.ARGB_8888;
        return BitmapFactory.decodeResource(getResources(), resource, options);
}


protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);


        ...
        mBitmapIn = loadBitmap(R.drawable.data); // R.drawable.data 是一張圖片
        mBitmapOut = Bitmap.createBitmap(mBitmapIn.getWidth(), mBitmapIn.getHeight(), mBitmapIn.getConfig()); // 空資料,大小跟 mBitmapIn 一樣
        ...

        // doMonoByJava(); // use Java only
        // doMonoByNDK(); // use NDK
        // createScript();     // use Renderscript
        ...
}


純 Java 版:


private void doMonoByJava() {
        final float gMonoMult[] = {0.299f, 0.587f, 0.114f};
        int imageHeight = mBitmapIn.getHeight();
        int imageWidth = mBitmapIn.getWidth();

        /* only assign, 速度慢
        for( int i=0 ;i<imageWidth; ++i )
                for( int j=0;j<imageHeight; ++j)
                        mBitmapOut.setPixel(i, j, mBitmapIn.getPixel(i, j));
        */

        /* only assign, 速度快
        int[] pixels = new int[imageWidth*imageHeight];
        mBitmapIn.getPixels(pixels,0,imageWidth,0,0,imageWidth,imageHeight);
        mBitmapOut.setPixels(pixels, 0, imageWidth, 0, 0, imageWidth, imageHeight);
        */


        int[] pixels = new int[imageWidth*imageHeight];
        mBitmapIn.getPixels(pixels,0,imageWidth,0,0,imageWidth,imageHeight);
        for( int i=0 ; i<imageHeight*imageWidth ; ++i ) {
                int R = (pixels[i] >> 16) & 0xff; 
                int G = (pixels[i] >> 8) & 0xff;
                int B = pixels[i] & 0xff;

                int m = (int)((float)R*gMonoMult[0] + (float)G*gMonoMult[1] + (float)B*gMonoMult[2] );
                R = m;
                G = m;
                B = m;


                pixels[i] = 0xff000000 | (R << 16) | (G << 8) | B;
        }
        mBitmapOut.setPixels(pixels, 0, imageWidth, 0, 0, imageWidth, imageHeight);
}


使用 NDK 版:


HelloCompute.java:


package com.example.android.rs.hellocompute;

public class HelloCompute extends Activity {
        ...
        static {
                System.loadLibrary("my-jni");
        }
        public native void doByNDK( int [] pixels, int size);

        private void doMonoByNDK() {
                int imageHeight = mBitmapIn.getHeight();
                int imageWidth = mBitmapIn.getWidth();
                int size = imageWidth*imageHeight;
                int[] pixels = new int[size];
                mBitmapIn.getPixels(pixels,0,imageWidth,0,0,imageWidth,imageHeight);
                doByNDK(pixels,size);
                mBitmapOut.setPixels(pixels, 0, imageWidth, 0, 0, imageWidth, imageHeight);
        }
        ...
}


my-jni.c:


#include <jni.h>
void Java_com_example_android_rs_hellocompute_HelloCompute_doByNDK( JNIEnv* env, jobject thiz, jintArray pixels, jint size) {
        float gMonoMult[] = {0.299f, 0.587f, 0.114f};
        int i, R, G, B, m;


        jboolean isCopy = JNI_FALSE;
        jint *jpixels = (*env)->GetIntArrayElements(env, pixels, &isCopy);


        for(i=0 ; i<size ; ++i ) {
                R = (jpixels[i] >> 16) & 0xff; //bitwise shifting
                G = (jpixels[i] >> 8) & 0xff;
                B = jpixels[i] & 0xff;
                m = (int)((float)R*gMonoMult[0] + (float)G*gMonoMult[1] + (float)B*gMonoMult[2] );
                R = m;
                G = m;
                B = m;
                jpixels[i] = 0xff000000 | (R << 16) | (G << 8) | B;
        }
}


若要計算效率,可以再用這段程式碼:


Long timeCost = System.currentTimeMillis();

// do something ...

timeCost = System.currentTimeMillis() - timeCost;
System.out.println("Cost:"+timeCost);


2012年3月3日 星期六

[Android] 變更 CPU 狀態、使之 offline、降頻、超頻和穩頻 @ PandaBoard

之前被要求測試 Android 4.0 的 Renderscript 是否可以做平行處理,但是隨著工作量的增加,一直把這件事擺在後頭,今天,終於測試完了!最早之前,我想到的是修改 HelloCompute 範例中的 mono.rs 檔案,裡頭的 root 函式是一個工作單位,如果一台裝置是多 CPU 且系統支援平行處理的話,理論上一次會有多個工作單位在進行。因此想說擺一個 static 變數在 root 函式中,每次把數值加一並印出,依此觀察系統運作時,印出的數字是否不連續,如果不連續或有相同時,想要把這當作有平行的依據。今天則想到一個更簡單的妙招,就是在 root 函式中,用一個 loop 讓它不結束,如此一來,在雙核心的 CPU 下,就會看到兩次訊息,單核心就只會看到一個!理由很簡單,因為工作沒做完,就不會分配新的工作。


這在 Pandaboard 上測試很成功,我看到了兩次 root 函式印出的訊息,然而,我更想要驗證自己設計的方式是不是對的,那就必須找只有一個核心的裝置,甚至四核心的裝置測試,如果在一個 CPU 的裝置只看到一次,在四核心的裝置可以看到四次,那代表驗證成功了。結果把構想跟同事分享後,同事教了我一個密技!那就是手動去更改 CPU 的狀態!對 Pandaboard 來說,就是強制把一個 CPU 關掉(offline),慶幸的,驗證對了!


這個用法其實最常看到別人修改機子的 CPU 頻率,例如一台裝置他本身的 CPU 可以跑到 500 MHz,但為了省電、穩定、商業手段等理由,系統廠會降頻跑,例如用 300 MHz 運行等等,這些則可以透過系統設定檔進行操作的,也因此有人刷機改機追求好的效能。


此例只在雙核心的板子上,手動把一顆 CPU 關掉:


$ adb devices
List of devices attached
################ device
$ adb shell ls -R /sys | grep -i cpu
...
/sys/devices/system/cpu:
cpu0
cpu1
cpufreq
cpuidle
...
$ adb shell
root@android:/ # cd /sys/devices/system/cpu
root@android:/sys/devices/system/cpu # ls
cpu0
cpu1
cpufreq
cpuidle
kernel_max
offline
online
possible
present
root@android:/sys/devices/system/cpu # cat online
0-1
root@android:/sys/devices/system/cpu # cat cpu*/online
1
1
root@android:/sys/devices/system/cpu # echo "0" > cpu1/online
root@android:/sys/devices/system/cpu # cat cpu*/online
1
0
root@android:/sys/devices/system/cpu # cat online
0


在 Pandaboard 上,因為是雙核心架構,所以會看到 CPU0 和 CPU1 的資訊,接著分別印出 /sys/devices/system/cpu/cpu0/online 和 /sys/devices/system/cpu/cpu1/online 資訊,標記 1 代表都是運作中,同時印出 /sys/devices/system/cpu/online 會是 0-1 的資訊。接著使用 echo "0" > /sys/devices/system/cpu/cpu1/online ,代表令 CPU1 下線不運作,反之 echo "1" > /sys/devices/system/cpu/cpu1/online 則是讓他醒來運作。透過這個簡單的方式,就能讓控制裝置多核心的狀態啦!真是超方便的,但別忘了執行 adb shell 時,必須有 root 權限才行。


至於 CPU 調頻、穩頻,還沒親手測過,但原理很像,就順便筆記一下:


以 /sys/devices/system/cpu/cpu0 來說,裡頭 cpufreq 目錄,裡面又有 cpuinfo_cur_freq、cpuinfo_max_freq 和 cpuinfo_min_freq 等資訊,不難猜到吧?只要把 scaling_max_freq 跟 scaling_min_freq 設成一樣就是穩頻,把 scaling_max_freq 調高則是超頻吧,有空再來試試!穩頻應該很好用,可以在一樣的 CPU 狀態下收集測試數據。


2012年2月26日 星期日

[Python] 使用 Heroku 筆記 @ Windows 7


差不多該玩一下 Heroku 了,這跟 Google App Engine 一樣,是一套 PaaS 服務架構,仔細看才發現 Heroku 竟然是架設在 EC2 上面的服務,讓我想起 Dropbox 這有名的服務是使用 S3 架構,原來有不少成功的服務不見得從機房都自己打造呢!這樣的感觸滿特別的,不需堅持自己從零開始。使用 Heroku 跟 GAE 這類服務的主因之一,是想要妥善利用身邊的資源,不見得要去租一台虛擬機器來使用,雖然權限大能做的事很多,但一開始太多資源不見得是好事,除此之外,使用 Heroku 跟 GAE 的好處是碰到流量大時,錢砸下去就可以 Scaling ,這樣的投資似乎不差吧?最大的缺點是要去習慣這些架構,轉個角度來看,或許這就是雲端的成本吧?就像把想做的事寫成符合 Hadoop/HBase 架構一樣。


對 Heroku 來說,計費方式是以 process 單位小時的方式計價,例如免費帳號就是一個月使用 750 小時不用錢,使用一隻 process 一個月是 720 小時,所以就不用錢。原先是一隻 process 用一小時 0.05 美元,其中 process 分為 web process 跟 background process 兩種。只是 Heroku 資料庫免費空間僅 5MB 而已,像 GAE 則是 1GB 大小,但是 GAE 還有計算 in/out 的資料量等等。我還沒搞懂 Heroku 有沒計算流量以及是否有限制 library 的部分 :P


好啦,回過頭講一下在 Windows 的操作方式,準備資源:



依序安裝完 Heroku Windows、Python 2.7 後,把 virtualenv-1.7.1.2.tar.gz 解壓縮後,就可以準備操作了,主要參考 Heroku - Getting Started with Python on Heroku/Cedar 這篇,先用最簡單的方式體驗一下,其中有用到 python Flask,這是一個輕量型 MVC 的 framework 囉。


步驟:


Python 2.7 在 C:\Python27\python.exe;virtualenv 在 C:\virtualenv-1.7.1.2;heroku-toolbelt 預設在 C:\Program Files\Heroku。安裝完軟體後,直接用 cmd 運作即可。


建立空目錄,如 helloflask


C:\Users\user>mkdir helloflask && cd helloflask
C:\Users\user\helloflask>


建立 virtualenv 環境


C:\Users\user\helloflask>C:\Python27\python.exe C:\virtualenv-1.7.1.2\virtualenv.py venv --distribute
New python executable in venv\Scripts\python.exe
Installing distribute...done.
Installing pip...done.


啟用虛擬環境:


C:\Users\user\helloflask>venv\Scripts\activate
(venv) C:\Users\user\helloflask>


安裝 Flask:


(venv) C:\Users\user\helloflask>pip install flask


Downloading/unpacking flask
Downloading Flask-0.8.tar.gz (494Kb): 494Kb downloaded
Running setup.py egg_info for package flask
...
Downloading/unpacking Werkzeug>=0.6.1 (from flask)
Downloading Werkzeug-0.8.3.tar.gz (1.1Mb): 1.1Mb downloaded
Running setup.py egg_info for package Werkzeug
...
Downloading/unpacking Jinja2>=2.4 (from flask)
Downloading Jinja2-2.6.tar.gz (389Kb): 389Kb downloaded
Running setup.py egg_info for package Jinja2
...
Successfully installed flask Werkzeug Jinja2
Cleaning up...


建立 app.py 程式(C:\Users\user\helloflask\app.py):


import os


from flask import Flask
app = Flask(__name__)


@app.route('/')
def hello():
        return 'Hello World!'


if __name__ == '__main__':
        # Bind to PORT if defined, otherwise default to 5000.
        port = int(os.environ.get('PORT', 5000))
        app.run(host='0.0.0.0', port=port)


建立 Heroku 環境檔案:


(venv) C:\Users\user\helloflask>pip freeze > requirements.txt
(venv) C:\Users\user\helloflask>cat requirements.txt
Flask==0.8
Jinja2==2.6
Werkzeug==0.8.3
distribute==0.6.24
(venv) C:\Users\user\helloflask>echo web: python app.py > Procfile
(venv) C:\Users\user\helloflask>cat Procfile
web: python app.py


執行 web service:


(venv) C:\Users\user\helloflask>foreman start


接著就可以用瀏覽器瀏覽 http://localhost:5000 啦,可以看到印有 "Hello World!" 字樣,要結束的話就按 ctrl+c 囉


如此一來,就完成本地端的測試開發囉。至於 Deploy 的部分,則是好好看一下官網上面的介紹,這邊就不多提了。


整體上,覺得 GAE 比較簡單一點,過程中只需安裝 python 跟 GAE SDK 環境,但 Heroku 還須使用 Ruby 環境等,或許對已習慣用 Ruby 的人來說,是非常自在的 :D