python vs java

語文地址:http://www.keakon.net/2009/09/12/Python%E5%92%8CJava%E4%BB%A3%E7%A0%81%E7%9A%84%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E6%AF%94%E8%BE%83
我想概述什麼的大家都看煩了,所以我就直接以代碼來說明了。

這個例子是從一個UTF-8編碼的文本文件裏讀取所有字符,轉換成Shift-JIS編碼,再將每個字節與0xAB異或,最後寫入另一個文件。可以算是破解日文遊戲經常需要做的事,尚據一定代表性吧~

測試平臺:
CPU:Intel Core2 Duo T9400 @ 2.53GHz
內存:3GB
操作系統:Windows XP Pro SP2 英文版
Python SDK:2.5.4
Java SDK:1.6.0_13

測試的文本是從《家族計劃》裏選的最大的一個,大小爲781 KB (800,053 bytes)。(不要和我扯爲什麼不弄幾百兆的來測試,我不是來翻譯百科全書的。)

先上Python的代碼:
from __future__ import with_statement
import codecs
from cStringIO import StringIO
from time import time
t = time()
with codecs.open(r'B:\test.txt', 'r', 'utf8') as inFile:
  inFile.seek(3) # skip BOM
  content = inFile.read()
  content = content.encode('sjis')
  outBuf = StringIO()
  for c in content:
    outBuf.write(chr(ord(c) ^ 0xAB))
  with open(r'B:\test2.txt', 'wb') as outFile:
    outFile.write(outBuf.getvalue())
print time() - t
測試結果:約550毫秒(波動較大)。

接着是Java:
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
public final class Hello {
    public static void main(String[] args) throws Exception {
        long t = System.currentTimeMillis();
        Reader inFile = new BufferedReader(new InputStreamReader(new FileInputStream("B:\\test.txt"), "UTF-8"));
        inFile.skip(1); // skip BOM
        int size = 1 << 20; // 1M cache
        CharBuffer inBuf = CharBuffer.allocate(size);
        inFile.read(inBuf);
        inFile.close();
        inBuf.position(0);
        ByteBuffer outBuf = Charset.forName("shift-jis").newEncoder().encode(inBuf);
        byte[] outBytes = outBuf.array();
        for (int i = 0, length = outBytes.length; i < length; ++i) {
            if (outBytes[i] == 0) {
                size = i;
                break;
            }
            outBytes[i] ^= 0xAB;
        }
        FileOutputStream outFile = new FileOutputStream("B:\\test2.txt");
        outFile.write(outBytes, 0, size);
        outFile.close();
        System.out.println(System.currentTimeMillis() - t);
    }
}
測試結果:約62毫秒。

性能約是Python的9倍,非常值得Java程序員驕傲了。這點也和通常的看法一樣,靜態語言一般是比動態語言快1個數量級的。

但性能背後卻留下了很多問題,我仍不得不提。

首先顯而易見的是代碼量,Java足足比Python多了一倍。而且初看上去,Python的核心代碼不到10行,邏輯一目瞭然;Java卻多了很多稀奇古怪的東西,語句也特別長。

其次是寫代碼所用時間。我對API算不上熟悉,實際上裏面幾乎每行代碼我都查看了文檔或Google了一下,Python頂多用了半小時,Java用了2個多小時。

接 着是代碼的健壯性。爲了減少代碼量,在Java代碼裏,我設置了1M的固定緩衝區,超過的話這個程序就是不能正常工作的;我也沒去捕捉IO異常,這可能導 致文件不會正常關閉;還有一點下面會說到,我不知道怎麼解決。而在Python裏,我不需要多餘的代碼去做這些事(除了引入with語句)。

最後看看API的易用性。
Java讀寫文件的組合實在太多了,看得我頭疼。爲了能讀取UTF-8,加上文件很小,所以我並未使用nio。當然,這段代碼是從網上抄來的。
接着將文件內容讀入了CharBuffer中,再編碼到一個ByteBuffer裏,然後生成一個字節數組,再寫入文件。這裏又費了我不少時間。
但是運行發現出錯了,生成了一堆空白的文件,調試了半天才知道得把輸入緩衝的position設爲0。
設 完後終於出內容了,但大小卻不對勁,老是生成1MB的文件。於是接着調試,發現是CharBuffer的容量設爲1MB了,於是沒從文件中讀取到的就都是 '\0'了;而它又直接全部傳給了ByteBuffer,繼而生成了後面全是0的字節數組。我試了很多辦法,例如截取一個子序列,但沒想到子序列居然共用 同一個數組…
看到這個問題沒法解決,我就只好在數組上下工夫了。可惜我那種方式讀取文件只能知道UTF-8的字符數,和Shift-JIS的字節數是不成正比的,而且數組也不支持切片操作。
沒辦法只好遍歷判斷是不是0了,這種行爲有個缺點,如果數組中間有0的話,後面就都被截斷了,這也就是前面所述的第3點不健壯的地方;但好在文本文件不會這麼搞,所以我的程序仍勉強湊合…
說實話寫完就想罵人了,這緩衝區也太無語了吧…

當然,雖然累了大半夜,但Java高手肯定寫得比我好,我也就懶得再琢磨Java代碼了,轉而看Python。
性能差的原因一下就能找到,因爲就那麼幾行代碼,讀寫文件是不可能去優化的,就只能優化循環中那句chr(ord(c) ^ 0xAB)了。
我把它改成c,時間立刻就減少到200多毫秒,也就是約有40%的時間用在這上面了。
很顯然,將字符串轉成數字,再轉回字符串,這個操作非常彆扭。Python不像C,沒有內置的char類型,因此每次都構造一個字符串的開銷是很大的,於是就想到了array。
稍微改了4行代碼(包括import),優化版就誕生了:
from __future__ import with_statement
import codecs
#from cStringIO import StringIO
from array import array
from time import time
t = time()
with codecs.open(r'B:\test.txt', 'r', 'utf8') as inFile:
  inFile.seek(3) # skip BOM
  content = inFile.read()
  content = content.encode('sjis')
  #outBuf = StringIO()
  outBuf = array('B')
  for c in content:
    #outBuf.write(chr(ord(c) ^ 0xAB))
    outBuf.append(ord(c) ^ 0xAB)
  with open(r'B:\test2.txt', 'wb') as outFile:
    #outFile.write(outBuf.getvalue())
    outBuf.tofile(outFile)
print time() - t
雖然不敢保證array比cStringIO快,但循環裏少了個chr函數,應該不會慢,而測試也證明了這點:約360毫秒,加快了約53%。

當然,ord的調用也很費時,所以可以繼續優化,只是這樣就比較難看懂了:
from __future__ import with_statement
import codecs
from array import array
from struct import unpack
from time import clock
t = clock()
with codecs.open(r'B:\test.txt', 'r', 'utf8') as inFile:
  inFile.seek(3) # skip BOM
  content = inFile.read()
  content = content.encode('sjis')
  outBuf = array('B')
  unpackedContent = unpack('%dB' % len(content), content)
  for byte in unpackedContent:
    outBuf.append(byte ^ 0xAB)
  with open(r'B:\test2.txt', 'wb') as outFile:
    outBuf.tofile(outFile)
print clock() - t
結果是約310毫秒,比最初版本快了約77%,是Java的1/5。

當然,剩下的就沒什麼可優化的了,除非把遍歷content進行異或寫成C擴展。(當然,Psyco或許對第2個版本有一定幫助,畢竟調用了很多次ord函數。)
順便說下,如果不進行異或的話,Python需要約15毫秒,而Java仍然是62毫秒。也就是說,Python用C編寫的模塊在IO性能(讀寫小文件)並進行編碼處理上比Java更好,只是沒有內置byte類型,而在處理字節方面開銷大了不少。
反觀Java,初看上去完全不知道該怎麼優化,或許多組合幾種IO類型,調整一下緩衝區大小,改進一下數組結束的判斷都可能會提升,但那需要非常枯燥而又沒有技術含量的測試了。

綜上,對我而言,Python在編碼方面少花了2個小時,這就足夠了。哪怕這個文本需要使用這個程序10000次(一般來說不會超過100次),我也少浪費了不少時間,還不必爲了無語的緩衝區而吐血。
當然,Python也不是萬能的,例如dll就還是得用C/C++寫…大家愛挑什麼毛病都能挑出來,所以我就此打住了=。=
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章