顯示具有 CS豆知識 標籤的文章。 顯示所有文章
顯示具有 CS豆知識 標籤的文章。 顯示所有文章

2021年1月31日 星期日

bss_eval–an conventional tool for blind source separation

這個工具是Emmanuel Vincent於2006年發表,至今已快過了15年,但我仍在2020年的論文看到有人使用它,因此回想起10年前在法國IRCAM研究的題目,特此紀錄在這久未更新的筆記本上。

根據論文所述,今天我們拿到一個混合音檔(mixture),它是由多個樂器、或同樂器但不同音源發聲所疊加混合而成。假設我們針對某一個目標音源去做分離,分出來的結果通常不完美,而可以用以下的式子表示:

代表該分離的結果包含了target、interference、noise、及artifact等四個成分。如果分離很完美,則後三者成分的值會是 0。接著,論文又定義這四個成份的算法為:

論文用了許多數學式子定義上述各式的變數,我後來試著用向量的方式來表示,試著幫助我自己更瞭解原本的精神。這裡我假設音源有兩個,並將noise那一項拿掉,一方面方便表示,另一方面是一般論文會將noise項歸納成其他音源而不列入計算中。圖如下所示。
其對應SIR, SAR, 及SDR評分的公式如下,單位是dB,顏色可比對上方向量圖:

這裡我直接歸納幾個重點:

  1. SIR跟紅線有關,SAR跟桃線有關,SDR跟藍線有關。
  2. 分離結果的音量大小不影響評分。假設你分離出來的向量是st,就算不是s1,你三項評分都是無限大dB。
  3. 分離的結果只要座落在音源的線性組合空間 (也就是圖中的平面Ps),那SAR會無限大dB。舉個極端例子,就是你直接拿混音當作分離完的結果。
  4. 同上列例子,SIR會很慘。
  5. 要看overall performance還是看SDR較準。

接下來回顧過去的研究日子。

2011年去法國IRCAM時接了一個訊源分離的題目,前人用了這套工具來評估,但評估的"手法"有點特別,當時他將SIR跟SAR放在二維向量的兩個軸,然後企圖營造出「在上方或右方效果較好」的感覺。

我花了時間研究這篇論文的公式,最後畫出了底下這張圖,用向量的方式向當時的指導教授Axel說明我的想法。這應該是我這輩子第一次質疑大家常用的評斷工具,然後應該也是第一次用英文說服處女座的德國人了 (累攤),這大概也導致我之後看到任何評分工具都會帶著質疑的角度去深入理解它。

但老實說,對事物保持懷疑,「盡信書不如無書」的觀念,是受蘇文鈺教授的影響就是了,它幫助我在求知的路上走得更踏實。(不過作法/行為上還是要小心不要得罪人…)

2018年3月11日 星期日

Audio Effects on Unity–Native Audio Plugin SDK(2)

時光飛逝,過了半年才回來補這篇技術文章,在進入正題之前先簡述一下,這段時間 Unity 已經改版至 2018.1 beta,Native Audio Plugin 也有做一些改動。近期開發 spatial audio 的公司變多了,所以 Unity 在 spatializer 的支援也改版得地別勤勞,從原本 Unity 5.0 開始,後來 5.5 內建 Microsoft HRTF 跟 Oculus spatializer,到現在最新的 2018.1 beta 也內建了 Google Resonance spatializer,有興趣的朋友可以自行深入查詢各家的特色。本篇文章將只針對 Unity Native Audio Spatializer Plugin 的幾個 API 作介紹,也會提及一些經驗上的分享,其適用於 Unity 5.0 到 2017.3,2018.1 beta 我個人還沒試過不過理論上應該也可以支援。


在閱讀本文之前,大家可以先到這裡下載API與範例程式,從文件及範例程式,我整理出幾個設計程式的步驟:
  1. 製訂好你設計的 spatializer 有哪些參數可供外部設定,然後參考InternalRegisterEffectDefinition 函式內的寫法去註冊你的參數。
  2. 前面訂好參數後,可實作 SetFloatParameterCallback 這個 interface 來實現與外部控制單元的接口,往後在 C# script 端就可以透過 Unity audio source 物件的 setSpatializerFloat 函式將 Unity 場景中某個 audio source 的參數給傳進 plugin。
  3. CreateCallback 與 ReleaseCallback 相當然爾就是對應到 audio source 物件生成與消失時底層 plugin 物件實體的生成/消失。
  4. ProcessCallback,最重要的部份,audio source 上掛的音檔,在執行過程中會以一個個 audio frame 的方式透過 ProcessCallback 不斷的傳進來 plugin,我們必須實作這個函式去進 input buffer 進行處理後再填到 output buffer,最後就會往 audio source 所指定的 Mixer 送去。這裡需要注意的是第四個參數 length,它的單位是 sample,假設它的值是 1024,如果 inchannels = 2 (雙聲道),則代表 input buffer 一共有 1024 * 2 個 floats。
  5. 別忘了在某個地方加上這行:
    definition.flags |= UnityAudioEffectDefinitionFlags_IsSpatializer;
  6. 善用 UnityAudioSpatializerData 去取得當前 audio source 及 audio listener 的位置/旋轉資訊。
這裡我跳過對每個函式的解說,直接分享幾個心得,或許對初次接觸 audio plugin 的人沒有幫助,但相信對有開發經驗的人能節省一些錯誤的嘗試。
  • audio 演算法的設計,在這個架構下必須要將「控制」(SetFloatParameterCallback) 與「計算」(ProcessCallback) 分開,剛從 Matlab 跨過來的人可能會有點障礙。
  • Unity audio source 生成、執行、到消失時,可確定其呼叫順序為 CreateCallback -> ProcessCallback (多次) -> ReleaseCallback。
  • Unity C# script 的遊戲物通常會繼承 MonoBehavior class,它也有 Awake, Start, Enable, Update, OnDestroy 之類的 API,來對應它的 life cycle。不幸的是,這些 API 跟前面提到的 callback 沒有任何先後關係。也就是說,如果你想設計一個遊戲物件去影響一個 audio source 的效果 (例如有殘響效果的房間),請小心處理兩者的 life cycle,否則會出現你想 Update 但 audio source 已被 destroyed 的情況。
  • 當audio source播完你指定的audio clip時,Unity 5.6 及之前的版本會繼續空轉 ProcessCallback,造成CPU資源的浪費,2017 以後的版本才會停止呼叫。這算是 Unity 之前版本的 bug,但我們也是有辦法可以讓他停啦...

半年一篇CS豆知識,希望以後還有時間分享。

2017年8月16日 星期三

Audio Effects on Unity–Native Audio Plugin SDK(1)

Reference:

1. Unity manual  for Native Audio Plugin SDK

2. Native Audio Plugins Bitbucket


在介紹主題之前,我先簡介一下 Unity 的聲音傳導過程。

Unity的元件列表中,可以找到 audio source 與 audio listener ,前者就像是喇叭,後者則是麥克風。假設我們將 audio source attach 在一個球體物件上,將 audio listener attach 在 main camera 上,再將音檔拖曳至 audio source 的 audio clip 欄位上,在按下 play 鍵時,audio source 會開始在場景中播放音檔,audio listener 則接收音訊資料並將結果輸出至電腦的音效裝置,完成這回合。只要你將任何一個人 disable 掉 (取消勾勾),就聽不到聲音。

由上圖 audio source 的屬性頁可以發現, Unity 已經提供了一些很基本 3D Sound Effect 的功能了,因此,在 Play 後你改變了兩者的相對位置及距離的話,就可以聽到聲音的方向有些許的改變。

這時候問題來了:如果我想要改變聲音的特性,或加上一些音效,該怎麼做呢?

一、MonoBehaviour.Update()

只要有一個元件它是繼承自 MonoBehaviour,當它有被 Enable,則 Unity 每過一段時間就會執行它的 Update,我們可以在取得 audio source 的 audio clip 後開始播放它,然後另外新增一個 audio clip 的物件,當 Update 被執行時將 original audio clip 丟進音效處理器,再將 output 寫去 new audio clip ,最後就是將 new audio clip attach 回 audio source 給它播放。

老實說這個作法在邏輯上看起來就怪怪的,我目前還是不懂為什麼可以新舊兩個 audio clip 同時播,卻只有新的被 attach 到 audio source,另個團隊能想到這個作法真的很厲害。

二、MonoBehaviour.OnAudioFilterRead()

原理跟上面差不多,但這個就是直接拿 original audio clip 來做,做完就寫回去並播放,在邏輯上比較合理。另外,這個 function 是由另一條 audio thread 所呼叫,因此在程式執行上比前者更有效率。

三、鏘鏘鏘鏘

沒錯!最有效率的當然是做成 Native Audio Plugin 了,原因無它,前兩者看到的函式都是 C# script,而這個方法則是在底層 (C++) 就做掉了,所以當你在 trace 其他家廠商寫的 Plugin 時,是看不到哪邊有 audio I/O 的行為的。

 

つづく

Audio Effects on Unity–Preface

好久沒有寫blog,心裡有種莫名的感動,工作兩年半來,總算在換工作後有時間可以沉澱自己所做所學並留下紀錄,做筆記對像我這個記性不好的人來說是個加深印象的好方法。

五月底加入新團隊後才剛開始接觸 Unity,在這之前我們實驗的 Unity 場景是商請另外一個團隊幫忙制作,但該團隊對於 Unity 的 audio 運作機制也不熟,所以用了一個很奇特的方式將 audio input buffer 導入某個函式內,讓我們統一在裡面處理完後再丟到 output buffer 。好處是,audio 演算法開發者不需要懂 Unity,只要寫一個 DLL export function 讓 Unity 執行到該函式時可以呼叫即可,我接手程式後上手很快,同事需要橋接新的參數也很容易;缺點是,會有 latency。

老實說我不覺得自己程式功力有多熟練,系統程式的概念多半在碩班建立,在前公司也只是寫一些小模組套用到前人已經完成的系統上。到了新團隊後,我才體會到我對程式也有某種程度上的潔癖,命名規則倒還好,我比較受不了程式架構的雜亂無章,各種神奇的全域變數飛來飛去。因此,我主動向主管提出說我自願幫忙做 code refactor 。另一方面,因為工作任務的關係我也開始研究 Unity 上各家廠商的 SDK,也可擷取別人的長處來改善現行的架構。

新工作即將到職滿三個月,就以一系列的技術文作為慶祝(?)吧!希望日後能夠持續做筆記,讓後輩們能夠更快找到出路。

喔對了,希望台電不要這時候停電,大家快用愛發電吧!

2013年5月27日 星期一

在Windows 7 64-bit環境下如何在點擊多個.m檔後使他們都開在同一個editor下?

很長的標題,很常見的問題,對一個對程式有某種程度龜毛的我來說卻是不得不解決的問題。這一切得從我心血來潮裝了 MATLAB R2013a 開始講起。

我本來用 R2011a 用得好好的,後來我發現它竟然沒有自動找出游標所在的變數名稱變成灰底的功能,這個功能的好處是當我要找程式中某個變數所有出現的位置時不需要用 CTRL + F,而只要將游標點在該變數上,從右邊slider bar就可以找到所有位置 (灰色線)。查了一下google發現是 R2011b 以後才有的功能,既然要重裝了乾脆一次裝到最新的版本,果然,R2013a 的介面跟以前截然不同外,還讓我遇到兩個問題:

  1. .m 檔沒有與 MATLAB 建立關聯性
  2. 1.的問題解決後,在檔案總管中依序點擊數個.m檔後,每個.m檔會各自開一個 MATLAB 出來執行。

2012年6月19日 星期二

[Matlab] function_memo

醉資心最近掛了,害我無法查我以前紀錄下來的 matlab 常用功能,只好也在blog備份一篇。

How can I select multiple points using the Data Cursor and export the coordinates to the MATLAB workspace?

來源: mathworks , datacursormode

steps -

  1. plot the figure
  2. click the Data Cursor button on the toolbar
  3. click any point of your choice on the figure. if you wanna set multiple points, press Alt key.
  4. right-click anywhere on the figure –> “export cursor data to workspace” –> enter variable name –>  enter

他會將cursor data存成structure,每個element有三個欄位。

  • Target : 這筆cursor來自哪個figure (handle)
  • Position: 座標
  • DataIndex: 這筆cursor位於每個陣列的哪個位置

程式自動化的作法:

fig = figure;
z = peaks;
plot(z(:,30:35))
dcm_obj = datacursormode(fig);
set(dcm_obj,'DisplayStyle','datatip',...
'SnapToDataVertex','off','Enable','on')

% Now, click on the line to place datatip before executing the next line of code.

c_info = getCursorInfo(dcm_obj)

How to plot an image onto another one transparently ?

在Audio Signal Processing的實驗中,我們會很常需要把實驗結果貼在spectrogram上來看,用imagesc function來畫spectrogram會比用spectrogram function快很多,因為前者是2D後者是3D。在我用imagesc畫完後想要新增一張新的image貼在上面時試了很多方法,例如:

h1 = image(plot_t, plot_f, ones(size(MskMaxtrix)), 'AlphaData', 0.2*(1-MskMaxtrix), 'CData', colormap(flipud(copper(2))));
或
h1 = image(plot_t, plot_f, ones(size(MskMaxtrix)),'CData',colormap(flipud(copper(2))));
或者乾脆先畫出值再調整colormap
h1 = image(plot_t, plot_f, 0.1*ones(size(MskMaxtrix)));
colormap(flipud(copper(2)));
通通沒用!最後解決的方法如下:

green = cat(3, zeros(len_f, len_t), ones(len_f, len_t), zeros(len_f, len_t));

hold on;
h1 = imagesc(plot_t, plot_f, green);
set(h1,'alphadata',0.2*(1-MskMaxtrix));
hold off;

先畫完後,再用set去調整 alphadata 屬性。這裡的MskMaxtrix是一個非0即1的矩陣,1代表通過,0代表沒通過,因此上面的例子是為了畫沒通過(過濾掉)的頻譜,0.2代表透明程度。

2011年4月30日 星期六

[matlab] Out of memory

最近寫的matlab程式需要宣告一大塊matrix來存放資料,導致產生了out of memory的warning,造成這個問題的原因其實有很多種,但私心總是覺得「不是我的錯!」,因此便展開一系列解決之道。

2010年6月29日 星期二

[java] How to pass native structure by JNI ? Java與C++之間的愛恨交織 ( Field Accessing篇)

有段時間沒有寫文件了,剛好最近學弟妹問到這個問題,做完實驗後來整理一下心得。其實上次使用JNI已經很久一段時間,後來每當有學弟來問我相關問題時我都得重新翻一次以前做的文章才能恢復記憶,程式這東西果然還是要常用啊!

噢!不過他們問我的問題當然不是標題寫的那樣,大家關心的是如何讓java與native c++的資料互相傳遞及溝通,其中最直覺的方法便是透過函式的參數列傳遞,在簡介與教學一文中已有介紹,可是但我有很多筆資料要傳時參數列不就要寫一整排?況且又沒有彈性。其實在變數的溝通上還有其他方式,今天先介紹一種:Fields Accessing。



這個方法的觀念很簡單,就是:java native function既然會屬於某一個class,理論上它就可以存取這個class裡所有的member fields。因此,這個class中所有的程式,無論在java還是c++,都可以對同一組資料做存取而無需透過參數傳遞。

例子:有個StructPass class,裡面成員有

    private String name;
    private int age;

Constructor

public StructPass(String n, int a) {
         name = n;  age = a;
    }

兩個native funciton

public native void toNativeStruct();
public native void fromNativeStruct();

現在,我希望在toNativeStruct裡可抓出在java中初始化好的fields,重點便是在於GetObjectField這個function。當他取得field後再做適當的型態轉換 (jstring, for example),便可以使用。

     jfieldID fid;   /* store the field ID */
     jstring jstr;
     const char *str;
 
     /* Get a reference to obj's class */
     jclass cls = env->GetObjectClass(obj);
 
     printf("In C:\n");
 
     /* Look for the instance field name in cls */
     fid = env->GetFieldID(cls, "name",
                              "Ljava/lang/String;");
     if (fid == NULL) {
         return; /* failed to find the field */
     }
     /* Read the instance field name */
     jstr = (jstring) env->GetObjectField(obj, fid);
     str = env->GetStringUTFChars(jstr, NULL);
     if (str == NULL) {
         return; /* out of memory */
     }
     printf("  c.name = \"%s\"\n", str);
     env->ReleaseStringUTFChars(jstr, str);

之後,我們希望fromNativeStruct函式中可改變name這個field,相信聰明的大家一定猜到是使用SetObjectField,當然前面的GetObjectClass, GetFieldID仍免不了囉。

      jfieldID fid;   /* store the field ID */
     jstring jstr;
 
     /* Get a reference to obj's class */
     jclass cls = env->GetObjectClass( obj);
 

 
     /* Look for the instance field s in cls */
     fid = env->GetFieldID( cls, "name",
                              "Ljava/lang/String;");
     if (fid == NULL) {
         return; /* failed to find the field */
     }


    /* Create a new string and overwrite the instance field */
     jstr = env->NewStringUTF("123");
     if (jstr == NULL) {
         return; /* out of memory */
     }
     env->SetObjectField(obj, fid, jstr);

在java的main function如下,執行後原本初始化的字串"kerker"就會變成"123"了。

    public static void main(String[] args) 
      {
        StructPass hello = new StructPass(new String("kerker"), 10);
        hello.toNativeStruct();
        hello.fromNativeStruct();
        System.out.println("In Java:");
         System.out.println("  hello.name = \"" + hello.name + "\"");

      }

好大一個分隔線

以上介紹的是傳單一primitive type variable的方法,如果是要傳的是primitive array呢?用法一樣!只是在native c++中只靠GetObjectField所取得的值,其型態為jxxxArray (xxx may be int, float, double, …etc),需搭配GetxxxArrayElements才能取出每個array中的值。

例子:成員

private float [] float_array;

Constructor 初始化

    public StructPass(String n, int a) {
         for(int i = 0 ; i < 10 ; i++)
         {
                 float_array[i] = i + 0.5f;
         }
    }

在toNativeStruct (c++)中

     /* Look for the instance field s in cls */
     fid = env->GetFieldID(cls, "float_array",
                              "[F");
     if (fid == NULL) {
         return; /* failed to find the field */
     }
     jfloatArray jfarray = (jfloatArray) env->GetObjectField(obj, fid);
     jsize len = env->GetArrayLength(jfarray);
     jfloat *body = env->GetFloatArrayElements(jfarray, 0);
     for(int i = 0 ; i < len ; i++)
     {
         printf("float[%d] = %f\n", i, body[i]);
         // modify some of them
         if (i%2 == 0)        body[i] = 99.99;
     }
     
     // ReleaseIntArrayElements enables the JNI to copy back and free the memory
     env->ReleaseFloatArrayElements(jfarray, body, 0);

這裡特別需要注意的是,一旦你更改了field的陣列值,在呼叫ReleaseFloatArrayElements後不但可以釋放JNI中使用的這塊記憶體,還可以把你改過的值更新回java的field array中。因此,這個例子就不需要再寫額外的code在fromNativeStruct裡面。

程式原始碼下載:點我點我

後記

本來隔天想要實作在網路上找的另外兩個方法,一個是將java的物件利用ByteBuffer包裝成連續的記憶體然後傳到native這邊,不過文章中有人回應說這個方法的變數很多,跟compiler, variable format, byte order等都有關。另一個方法就是用XML-like serialization來傳。不過後來想想,只要把想要傳的資料包成一個class (java沒有structure),然後用本文教的方法來傳應該就可以解決問題了,因此java或native c++的愛恨交織暫時就告一段落啦!

reference

[1] doc chap 4 : Fields and Methods

[2] Accessing Java Arrays

[3] JNI functions

2009年6月25日 星期四

[java] Java Native Interface(JNI) 簡介與教學

睡前來統整一下以前找過跟做過的資料,方便給隊友們參考,當然有其他不足或錯誤的地方也請大家指正。我主要是針對幾個常用的case來介紹,並不打算深入探討細節的部份。

JNI 是用來讓Java跟別種語言溝通的函式庫,如果我們舉C/C++ 為例,便分為C call Java與Java call C。

Java call C

這段是參考[1]的第二章。

1. 建立一個 Java class (HelloWord.java)。在這個class裡宣告一個native method (print),然後在 main() 裡呼叫這個 method。此時,我們呼叫 printf() 時,它的實體是用 C/C++ 所寫的。

 class HelloWorld {
    private native void print();
    public static void main(String[] args) {
        new HelloWorld().print();
    }
    static {
        System.loadLibrary("HelloWorld");
    }
}

上例中的System.loadLibrary("HelloWorld")會去找你程式目錄下的HelloWorld.dll或HelloWorld.so,這之後會提到。

2. Compile HelloWorld.java,用javac指令。

3. 產生一個 native method 的 header file。

指令: javah -jni HelloWorld

這裡,我們用javah可以產生 .h 檔,接著就是實作這個 .h 檔的 .cpp,然後就可以 compile 成 .dll 或 .so 了。cpp 的範例如下:

 #include <jni.h>
#include <stdio.h>
#include "HelloWorld.h"

JNIEXPORT void JNICALL
Java_HelloWorld_print(JNIEnv *env, jobject obj)
{
    printf("Hello World!\n");
    return;
}

記得專案的設定中需指定好 jni.h 的目錄。如果是用VS系列的話,專案新增時選DLL版本,compile過後就會輸出HelloWorld.dll。

4. 最後,將HelloWorld.class跟HelloWorld.dll放在一起後,執行 java HelloWorld 就可以看到結果囉。

註: HelloWorld.dll 其實也不一定要跟 HelloWorld.class 放在一起,有個環境變數叫LD_LIBRARY_PATH,它便是用來設定 native library path。還有一種方式,就是利用java指令來指定路徑。下方是將路徑設為當前目錄。

java -Djava.library.path=. HelloWorld

C call Java

這段是參考[1]的第七章與其他收集的資料。

首先,我們用java寫一個 class Prog,請他印出些字,然後用javac去compile出 .class。

 public class Prog {
    public static void main(String[] args) {
         System.out.println("Hello World " + args[0]);
    }
}

基本上這段程式是可以直接用java指令去執行的,但我們的目標是要產生一個 .c 檔來呼叫它,其流程大致如下:

  1. 喚醒 Java VM
  2. 載入指定class path下的所有 .class 們
  3. 找到你想要執行的class及其 method ID。
  4. 呼叫 method。

對照第七章的範例來看的話,步驟一加二的程式如下。舊版的JNI 有提供JNI_GetDefaultJavaVMInitArgs,新版的還是請大家自己乖乖指定好 class path 跟其他資訊。JNI_CreateJavaVM 會提供兩個重要的指標,jvm 是指向一個新開的JavaVM,env則是待會讓你用來對java class做怪怪事的介面。

     JavaVMInitArgs vm_args;
    JavaVMOption options[1];
    options[0].optionString =
        "-Djava.class.path=" USER_CLASSPATH;
    vm_args.version = 0x00010002;
    vm_args.options = options;
    vm_args.nOptions = 1;
    vm_args.ignoreUnrecognized = JNI_TRUE;
    /* Create the Java VM */
    res = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);

步驟三加四如下。

     cls = (*env)->FindClass(env, "Prog");
    if (cls == NULL) {
        goto destroy;
    }

    mid = (*env)->GetStaticMethodID(env, cls, "main",
                                    "([Ljava/lang/String;)V");
    if (mid == NULL) {
        goto destroy;
    }
    jstr = (*env)->NewStringUTF(env, " from C!");
    if (jstr == NULL) {
        goto destroy;
    }
    stringClass = (*env)->FindClass(env, "java/lang/String");
    args = (*env)->NewObjectArray(env, 1, stringClass, jstr);
    if (args == NULL) {
        goto destroy;
    }
    (*env)->CallStaticVoidMethod(env, cls, mid, args);

比較可能看不懂的地方是在GetStaticMethodID的第四個參數,這種詭異的寫法稱為JNI signature,當我們呼叫 Java method 時需要寫一些 signature,用來檢查你指定的型態是否與該 method 一樣。舉些例子好了。

Java constructor:

String s

對應到的signature為:

(Ljava/lang/String;)V

Java method:

String toString()

對應到的signature為:

()Ljava/lang/String;

Java method:

long myMethod(int n, String s, int[] arr)

對應到的signature為:

(ILjava/lang/String;[I)J

看了那麼多一定還看不懂對吧?基本上,一個函式會有參數值與回傳值,signature 的規則便是先用 () 描述參數值型態,後面再接回傳值型態。比較特別的,是如果有用到某個 package,除了要打出 package path 外還要加個分號!

下面列出其對應的所有規則,比較一下就可以知道了。

  • B=byte
  • C=char
  • D=double
  • F=float
  • I=int
  • J=long
  • S=short
  • V=void
  • Z=boolean
  • Lfully-qualified-class=fully qualified class
  • [type=array of type>
  • (argument types)return type=method type. If no arguments, use empty argument types: (). If return type is void (or constructor) use (argument types)V.

如果 method signature 不對的話,還會出現類似以下的錯誤訊息。

#
# An unexpected error has been detected by Java Runtime Environment:
#
#  Internal Error (sharedRuntime.cpp:552), pid=7304, tid=2492
#  Error: guarantee(cb != 0,"exception happened outside interpreter, nmethods and vtable stubs (1)")
#
# Java VM: Java HotSpot(TM) Client VM (10.0-b19 mixed mode, sharing windows-x86)
# If you would like to submit a bug report, please visit:
#   http://java.sun.com/webapps/bugreport/crash.jsp
#

最後,你可能會想問:我要如何知道一個 Java class 裡的所有 method 到底需要哪幾種 signature ? 除了自己一個個看以外,J2SDK 有提供神奇指令來幫助我們!

javap -s -p classname

Reference:

[1] Java Native Interface: Programmer's Guide and Specification

[2] Invocation API of JNI Enhancement

[3] Creating a JVM from a C Program

[4] JNI Spec

[5] JNI Functions

2009年5月9日 星期六

Inter-Process Communication (IPC)

雖然過去mobo在課堂上曾經介紹過,我也曾經印了一本書放在 lab 拜,不過直到今天我還是沒機會在實作上使用這個技術,所以早就忘光了。本來晚上想要繼續研究 arburg function 跟想辦法解決nb不能切磁區來裝ubuntu的問題,但在家實在無法做,就又來整理一下資料了。

顧名思義,IPC 就是數種在 MultiProcess 下相互溝通的方法,其實不只是 process,甚至可推及 thread 間及 computer 間的溝通。這些技術包含像 synchronization, message passing, shared memory, memory-mapped files, pipe, socket…等等。我就我能理解的簡單介紹一下。

synchronization

這個應該是最多人使用過的技術,通常是用在thread間資料同步的溝通,OS課本上聽過的 critical section, mutex, monitor, semaphore等,很多語言是用 thread join 的方式實作,win32 api則是用handle搭配 WaitForSingleObject ,要注意的問題不外乎是 deadlock 跟 race condition,這些細節在 design pattern 中會有些 case 有解說,日後有機會再介紹。

shared memory

這個看英文就知道它是什麼,只是不同的平台有不同的實作方式,像最近很夯的Cuda就有自己提供 c++ library 給我們用,那算是硬體層面自行提供的。如果是在軟體層,兩個 processes 要共用同一塊記憶體時,POSIX 有提供 shm_open,搭配 mmap 就可以實作;在Windows上,shared memory跟shared file一樣都是用 CreateFileMapping。雖然它們在實作上可能有將資料寫到硬碟上,但至少在程式中你看到的會是一整塊的記憶體而非一個檔案。

memory-mapped file

大家可以參考一下msdn上的這張圖。

這東西跟讀寫檔其實沒兩樣,如果process間共用的資料如果非常大,讀寫的機制又不會很複雜的話,這是個不錯的選擇。

pipe

依照wiki的說法,pipe分成 anonymous pipe 跟 named pipe,其實就是仿照硬體 pipeline 的行為,在process間開一條或數條線來放資料。在Unix上,pipe()是個system call;在Windows上則是使用CreatePipe再搭配ReadFile或WriteFile。

socket

雖然很簡單,卻是十分好用的方法,就是不同的process透過socket互傳資料,無論是走tcp/ip還是rtp都可以,在同一台電腦上的話速度還非常的快,唯一的缺點是資料一筆不能太大。這個方法wiki將之歸納為 named pipe 的一種。

後記

其實寫這篇文章,加上請教keiko、wiki、google的時間大約花兩個多小時,寫出來的東西還是只有一點點實在很悶,但沒辦法沒實作過的話也沒什麼心得,無法分享這些作法可應用在哪些不同的情況。以後如果有收集到新資料再補上囉。

2009年4月26日 星期日

#pragma

有寫過C/C++的人應該很常看到這個字,我第一次用它好像是在碩一的時候,當時在練習寫DLL,呼叫DLL裡的function有兩種作法,一種是不需要include .h直接靠LoadLibrary,另一種要include搭配#pragma comment xxxxx。今天,我才想到要去找一下它的意思,這時候不免又要感慨一下自己的程式年齡發展得比別人晚也比別人慢。XD

簡單來說,#pragma是下給compiler看的指令,一些較常用的指令通常都支援跨平台的使用,所以大部份compiler都會看得懂。想當然爾,有些指令就比較特殊,也只有部份compiler看得懂,換個平台可能就無效了。

或許大家會說:用#ifdef不行嗎?furbylin在文章中提到:

#ifdef的方式必須在command line指定一堆定義是很麻煩的, 而且一些特殊能力用#ifdef 也定義不出來. 況且使用你寫的source code的人不見得了解該如何定義以符合他的使用平台或compiler的需求. compiler看到#pragma時如果後面的定義是它不認得的, 它不會理會; 相反的看得懂得compiler就會去執行它.

至於也很常用的 #pragma once ,這種header files的管理還是會建議用 #ifndef 來判斷較好,萬一compiler支援程度不一導致出錯時要再找bug就麻煩了。GCC Doc中曾提到將#pragma once列為"obsolete",不過3.4 release 後GCC有修正其 symbolic and hard links 的行為了。

我怎麼會心血來潮的想找這資料呢?因為開口的 How to Deploy my Visual C++ Applications 一文中有提到OpenMP,我便開始查看這東西是什麼鬼,才發現是用來寫平行程式的,Heresy  在他的blog上很清楚的介紹OpenMP的寫法,其中,便是要透過#pragma向compiler下指令說哪段code要作平行運算。最後我便貼一段他文章上的例子,順便測測PreCode的功能。

#include <omp.h>
#include <stdio.h>
#include <stdlib.h>

void Test( int n )
{
    for( int i = 0; i < 10000; ++ i )
    {
          //do nothing, just waste time
    }
    printf( "%d, ", n );
}

int main(int argc, char* argv[])
{
    #pragma omp parallel for
    for( int i = 0; i < 10; ++ i )
    	Test( i );
    system( "pause" );
}

2009年2月16日 星期一

記憶體跑哪去了?How do I find out who's using all my memory?

        在我思考要用什麼文字當作這篇文章的標題時,看到Keiko丟給我的Ask Leo!網頁,就直接把標題抄下來了。與其想個名詞,不如將整句打上去來得直覺。 
        或許大家會覺得這問題很簡單,只要按下 Ctrl+Alt+Del 便可叫出「工作管理員」(Task Manager),預設就有CPU跟記憶體可以看了。進階一點的玩家可能還會叫出這個畫面,來勾選其他選項來觀察。

image

        回想過去學計組或OS的課程,我們大概知道一支程式在執行期間會將資料暫存於記憶體中,記憶體如果不足,就會開始啟動「恐怖記憶體管理計畫」,計組及OS都花了整整一兩個章節在介紹這些計畫,不過不論用什麼辦法,記憶體不夠就先跟硬碟借一塊來用就對了。既然如此,我就多勾選上圖的「虛擬記憶體大小」選項來看看好了。

image

        如果你認為上圖所寫的「記憶體使用量」跟「虛擬記憶體大小」分別是在physical & virtual memory(hard disk)上,那你就被微軟給騙了!在Ask Leo! 這篇文章中,他介紹我們使用 Process Explorer 去觀察程式的狀態,還教我們叫出 Working Set Size & Virtual Size 兩個欄位。PE 功能之多我這裡不贅述,不過推薦進階玩家們使用,開發這工具的人正是撰寫Windows Internal的作者Mark Russinovich,它可以呈現Windows對記憶體的整體資料結構。另外,如果大家有跟著做的話,會發現:Working Set Size會等於「記憶體使用量」,但Virtual Size卻沒有等於「虛擬記憶體大小」。到底誰講的數字才是對的呢?

        別擔心,其實 PE 有個叫 Private Bytes 的欄位,其數字就會跟「虛擬記憶體大小」一樣,只是這不免再度引人思考:到底哪個數字是被擺在硬碟上的大小?只是想知道OS把我程式的資料擺在哪裡了有必要搞這麼複雜嗎?

        可怕的問題還在後頭。為了測試 x200 的DVMT功能到底是吃掉完整的1G,還是真的能動態將部份記憶體吐還給系統,我寫支程式來測試,用PE的觀察如下圖。

1

        由圖右邊的 Commit Charge 顯示可見到記憶體已被吃掉 3.8 GB,如此一來DVMT確實有發揮其應有的功能,我也不用擔心記憶體平白無故被VGA吃掉1G的情況。奇怪的是,粉紅色的 mem_test.exe,是我執行200次 new doutble [1M] 的結果,大概會用到將近 1.6G 大小的記憶體,為何在 Virtual Size 或 Private Bytes 已經用這麼多了,但 Working Set 仍吃掉 7x MB 呢?而且,如果過一段時間不理他,Working Set 數字還會慢慢變小!另一個更神奇的情況是,如果我繼續再 new 下去,Commit Charge (PF使用量) 甚至可以超過 4G!直到約 4.2 G時程式才會因記憶體不足而被強制中止。

        所有問題的解答,DriverGuru大師在這裡的討論區都有解釋。雖然看完後有了解個大概,但好像仍有一知半解的感覺,所以沒辦法給按END的朋友一個簡單的結論,如果有得道的先進願意提供結論來點化我這愚輩小弟當感激不盡。

2009年2月13日 星期五

[video] IGD DVMT Memory

接續"Lenovo X300 重灌 XP 及相關設定"這篇文章,開始在網路上找了些資料,發現這技術不單只是有自動設定VGA Memory Size的功能而已,它的目的是為了達到最大的 2D/3D 圖形效能,而對記憶體做動態調整的動作。



在 TechARP 裡有一段簡短的介紹。最早是先有Unified Memory Architecture (UMA)的概念,也就是CPU跟VGA共用主記憶體,而後 Intel 以UMA為基礎開發了 Dynamic Video Memory Technology (DVMT)。文中雖說 IGD Bios 可調整這個大小為 32, 64, 128, 及 Maximum DVMT,但顯然 Lenovo 並無提供這功能。

可能有些電腦玩家已經有一個觀念,顯示卡於遊戲中所展現的效能中,記憶體所佔的份量其實不大。也就是說,如果你開 WoW.exe 這個 3D 測試工具走在達拉然北銀時會頓頓的,通常你先該換的是 CPU 跟 GPU。如果你要開兩個解析度為1280*1024的螢幕,那才會有差。一般傳統的 NB 預設給 VGA 的大小在 16-32MB 左右就夠了,x300 的 Mobile Intel 4 Express Chipset 晶片所需最小值為 32MB。如下圖所示。

5

而在 Intel 的Spec中,VGA的Memory Allocation Methods有以下四種。

  1. Pre-Allocated Memory
  2. Fixed Memory
  3. Fixed + DVMT Memory
  4. Maximum DVMT Memory

其概念圖如下。

image

Spec 第五章有提到,最完整的 System BIOS Configurations 是可以選擇你要這四種 mode 的哪一種,可惜這在OEM那邊就被定死了。

Fine ! 既然從硬的這邊來不行,我就從軟的這邊來。(硬的還是比較好…)

我寫了個簡單的程式,一次 new 10^6 個 double,相當於接近 8MB,按一個鍵就 new 一次,然後開 Tast Manager (工作管理員) 及 Process Explorer 來觀察,看看結果會變怎樣。

啊,小新也離開了,週五的夜晚 Lab 只剩我一個好孤單 QQ,還是下回分曉好了。