C++程序移植到Java的一點體會

發佈於2012年5月18日 -

這兩天在做高級數據結構(ADS)課程的大作業,內容是做一個推薦系統。我們選取的是C. Wang and D. Blei. Collaborative topic modeling for recommending scientific articles.一文中描述的模型。文章的作者C. Wang在個人主頁上發佈了論文中使用系統的部分代碼,代碼使用C++語言完成的。而我們的系統是用Java搭建的,因此我們需要將C++代碼移植到Java中。

本以爲這是一個簡單的工作,只需要簡單的改寫即可,但沒想到第一次移植的版本跑出來的結果的準確率只有作者程序的1/10。於是我意識到程序改錯了。經過漫長的人工靜態debug,終於把程序改對了。

下面總結一下移植的一點體會:

1.C++程序中使用了GSL(GNU Scientific Libarary)。通過Google,Java下有一個叫JGSL的庫,但是下載下來後,才發現JGSL只實現了GSL的一小部分功能,而且都不是我們這個項目所需要的。考慮到程序中主要需要的是GSL的線性代數模塊,我找到了JAMA這個項目。然而,由於GSL出於效率的考慮,再結合C++語言的種種特性,大量使用了指針和引用,因此我們需要在JAMA中找到合適的方法來處理。例如,對於

gsl_blas_dger(a_minus_b, &v.vector, &v.vector, A);

這樣的代碼,我們可以改寫爲:

A.plusEquals(v.transpose().times(v).timesEquals(a_minus_b));

可以看到,一方面需要理解函數的意義,另一方面,也需要根據庫與庫之間的差異進行改寫,在數學表達式中,向量通常都指列向量,而在JAMA中,是沒有向量的,這時只能通過1×n的矩陣來模擬向量,這樣就會產生行向量和列向量的問題,改寫時需要格外注意。

2.C++中傳遞引用,可以修改變量的值。而Java中雖然也傳遞引用,但卻與C++有着非常大的不同。例如,我們可以改變對象的值,但不能改變對象本身。這就給移植帶來了一定的困難。

3.對對象的隱示修改。

const c_document doc =  c->m_docs[j];
likelihood += doc_inference(doc, &theta_v.vector, log_beta, phi, gamma, word_ss, true); 
optimize_simplex(gamma, &v.vector, param->lambda_v, &theta_v.vector);

這裏,我們看到在optimize_simplex函數中,傳遞了兩個地址,但實際上,通過查看參數聲明,第一個指針實際上是const類型,所以只有第二個指針可能會在代碼中被修改。

經過閱讀代碼,我們發現,這裏theta_v確實會被修改,因此,在改寫爲Java代碼時,還需要加上額外的一行:

likelihood += doc_inference(corpus.getDoc(j), theta_v, log_beta, phi, gamma, word_ss, true);
optimize_simplex(gamma, v, lambda_v, theta_v);
theta.setMatrix(j, j, 0, num_factors-1, theta_v);

這樣才能保證對theta_v的更新能正確的更新到theta中。

總的來說,在改寫中,一定要特別注意引用類型和指針類型的傳遞,搞清楚哪些會有可能修改傳入對象的值,哪些不會,這樣纔能有針對的改寫爲Java代碼。

最後,項目位於https://github.com/dangfan/ADS-AR