Memory efficient Python with bytearray

The story

I was developing an audio broadcasting server.  I wrote the server with Twisted.  It works fine, but there is still a big problem to solve: the memory usage.  My audio broadcasting server use memory so much.  You can see it from the following figure.

As you see, the memory usage goes up like crazy when there are more listeners on the line.  It is almost an exponential growth. And it doesn’t make sense why it takes so much memory.  Then I started to find the reason of memory overuse.  At first, I thought it might be some memory leaks from the C-modules of Twisted or other third-party packages.  But it is not reasonable, if it is memory leaking, why there is no relative raising of memory usage at all peaks of radios/listeners?

Surprising result from guppy

I inspected the memory usage detail with guppy, and get a surprising result:


Partition of a set of 116280 objects. Total size = 9552004 bytes.
 Index  Count   %     Size   % Cumulative  % Type
  0  52874  45  4505404  47   4505404  47 str
  1   5927   5  2231096  23   6736500  71 dict
  2  29215  25  1099676  12   7836176  82 tuple
  3   7503   6   510204   5   8346380  87 types.CodeType
  4   7625   7   427000   4   8773380  92 function
  5    672   1   292968   3   9066348  95 type
  6    866   1    82176   1   9148524  96 list
  7   1796   2    71840   1   9220364  97 __builtin__.weakref
  8   1140   1    41040   0   9261404  97 __builtin__.wrapper_descriptor
  9   2603   2    31236   0   9292640  97 int

Even the RSS reported by ps is almost 100MB, there is still little memory actually is used by Python objects.  I then considered another reason: the Fragmentation.

Fragmentation

I was not sure at first, I also didn’t do memory leak detecting on my server.  I am just guessing.  I reviewed the design of server.   Following class is the core class of the design.

class AudioStream(object):
    """Audio stream
 
    """
 
    def __init__(self, memoryLimit=128*1024):
        """
 
        @param memoryLimit: limit of memory usage in bytes
        """
        # limit of memory usage
        self.memoryLimit = memoryLimit
        # offset of audio stream (how many chunks)
        self.offset = 0
        # queue for audio data chunk
        self.chunks = []
        # total bytes of chunk in queue
        self.totalSize = 0
 
    def write(self, chunk):
        """Write audio data to audio stream
 
        @param chunk: audio data chunk to write
        """
        # append chunk to queue
        self.chunks.append(chunk)
        self.totalSize += len(chunk)
 
        # check the usage of memory, if exceeded, pop chunks
        while self.totalSize > self.memoryLimit:
            poppedSize = len(self.chunks.pop(0))
            self.totalSize -= poppedSize
            self.offset += 1
            log.debug('AudioStream pop chunk %d, remaining size %d, offset %d',
                poppedSize, self.totalSize, self.offset)

It is a simple idea: every radio has its own buffer: a list of string, and when a listener needs more data, just pick up a chunk and send it to peer.  A list of string?  That might be the reason of overuse of memory, I wondered.  And I tried to understand how Python manages memory.  I found the article Improving Python’s Memory Allocator.   Then I understand how it works.  It’s not difficult,  every object smaller than 256 bytes will be allocated in the memory pool,  otherwise it will be allocated with malloc.  Then I can imagine what happened behind the scene.  The chunks are not in fixed size,  their size might be 200~300 bytes to 1xxx bytes.  The list of chunks is limited in a fixed size,  when the size of total chunks is too big,  it just pops some old chunks out.  Here is the key point of the reason of fragmentation.  You can imagine:

  1. A chunk with 987 bytes occupies a piece of memory at 0×000123
  2. When it is no longer needed, it was freed, then there is a free memory chunk at 0×000123 with 987 bytes.
  3. Here comes another memory allocation request with size 768,  malloc founds the free chunk at 0×000123, it just occupies it and return
  4. Here is the fragment!  987 – 768 = 219, we got a small chunk of free memory, it is too small and difficult to be used

This thing happened again, again and again during the server is serving, then there will be more and more fragments !  I finally know the reason of overuse of memory.  Then here comes another question: how to fix it?  List of string is an easy way to store buffering chunks,  but it makes fragments.  I then wondered a Python C-module with memory chunk allocation and accessing function might be a good idea.

The final solution: bytearray

I started to write my memory chunk allocation C-module. But when I am doing it, I found there is a better solution just fits what I need: the bytearray.  I found it in the CPython source code.   It is a single memory chunk, you can change its size, it might be reallocated.  But if you don’t change its length, it is always same memory buffer.  At first, I was curious, why I can’t find anything for bytearray in Python2.6 documents? I then noticed it is a back-port from Python3, that’s why there is no document for it.  Okay, now let’s see the better solution with bytearray:

class AudioStream(object):
    """Audio stream
 
    """
 
    def __init__(self, size=1024, count=128):
        """
 
        The bytes is a big memory chunk, it buffers all incoming audio data.
        There are blocks in the memory chunk, they are the basic unit to send to
        peer. 
 
        <-------------- Memory chunk ------------------>
        <--Block1--><--Block2--><--Block3--><--Block4-->
        ^          ^          ^          ^
        L1         L2         L3         L4
 
        We map blocks to the real audio stream
 
        <------------------ Audio Stream -------------->  ---> time goes
        <--Block3--><--Block4--><--Block1--><--Block2-->
 
                          Map to
 
        <-------------- Memory chunk ------------------>
        <--Block1--><--Block2--><--Block3--><--Block4-->        
 
        Every listener got their offset of whole audio stream, so that we can
        know which block he got.
 
        ------------<------------------ Audio Stream --------------> --->
                    <--Block3--><--Block4--><--Block1--><--Block2-->
        ^
        L5
 
        When there is a listener point to a out of buffer window place, we
        should move the pointer to the first current block.
 
        ------------<------------------ Audio Stream --------------> --->
                    <--Block3--><--Block4--><--Block1--><--Block2-->
                    ^
                    L5 
 
        @param size: size of block
        @param count: count of blocks
        """
        self._size = size
        self._count = count
        self._bufferSize = size*count
 
        # offset of begin of buffer window in audio stream
        self._offset = 0
        # bytes array
        self._bytes = bytearray(self.bufferSize)
        # small chunks, they are not big enough to fit a block
        self._pieces = []
        # total size of pieces
        self._pieceSize = 0
 
    def _getSize(self):
        return self._size
    size = property(_getSize)
 
    def _getCount(self):
        return self._count
    count = property(_getCount)
 
    def _getOffset(self):
        return self._offset
    offset = property(_getOffset)
 
    def _getBufferSize(self):
        return self._bufferSize
    bufferSize = property(_getBufferSize)
 
    def write(self, chunk):
        """Write audio data to audio stream
 
        @param chunk: audio data chunk to write
        """
        # append chunk to pieces
        self._pieces.append(chunk)
        self._pieceSize += len(chunk)
 
        while self._pieceSize >= self.size:
            total = ''.join(self._pieces)
            block = total[:self.size]
            # there is still some remain piece
            if self._pieceSize - self.size > 0:
                self._pieces = [total[self.size:]]
                self._pieceSize = len(self._pieces[0])
            else:
                self._pieces = []
                self._pieceSize = 0
 
            # write the block to buffer
            begin = self.offset % self.bufferSize
            oldSize = len(self._bytes)
            self._bytes[begin:begin+self.size] = block
            assert len(self._bytes) == oldSize, "buffer size is changed"
 
            self._offset += len(block)
 
    def read(self, offset):
        """Read a block from audio stream
 
        @param offset: offset to read block
        @return: (block, new offset)
        """
        begin = offset % self.bufferSize
        assert begin >= 0
        assert begin < self.bufferSize
        block = str(self._bytes[begin:begin+self.size])
        offset += self.size
        return block, offset


With new design, there are no more allocations and deallocations.  That saves a huge mount of memory.  Here is the memory usage figure with new design:

As you can see, we have almost 800 listeners on line, and the memory usage is still low and stable.  The original server uses over 100MB even with only 6x listeners on the line.  That’s huge difference.

Posted in 分享, English Articles, Python | Tagged , , , | 1 Comment

Ajax/Comet實作聊天室心得

今天為now.in做了一個最陽春版本的聊天室

這聊天室用了AJAX/Comet技術,說穿了AJAX/Comet真的沒什麼,其實不過就一個http request在server端在接受到後,直到有事件發生才回傳,也有另一種做法是利用比較特別的header讓browser對於每次收到的資料分批處理,而這些都在一個response中完成,因為似乎後者還得有browser的支援,所以我用的是前者,而雖然概念上很簡單,但實際做起來會有些細節得注意的

前端不可以有proxy

原本我寫好後放上去結果不能跑,發現原因出在於透過WebFaction前端的Apache再rewrite到我的server,似乎在前端的連線數很快就被擠暴了,因為線上會有數百個連線一直在線上,所以如果前端有proxy,很容易就會被塞暴,後來改用直接連線,問題就解決了

一般的伺服器架構其實根本不適合Ajax/Comet

事實上一般常見的伺服器架構大多都不適合寫Ajax/Comet,為什麼呢? 原因很簡單,大多的伺服器架構都是thread pool之類的做法,一個連線進來,就從thread pool中拿一個worker丟給他去做,在完成之前都屬於在處理的階段,因此你有同時100個連線被Ajax/Comet卡住,就佔掉了thread pool中100個worker,這些worker什麼都不能做,通常只能sleep,定期檢查事件發生了沒,除了伺服器很快就會被塞暴以外,一點效率都沒有,像是Apache就是這類的架構

解決的方案: 非同步的IO

解決方案其實很簡單,就是改用非同步IO的網路框架,我個人最熟的就是Twisted,自然就是選擇這個,身為愛好Python的程式設計師,其實很多時候苦腦的不是沒有選擇,而是選擇太多,Python的非同步網路函式庫不是用一打就能數完的,多到誇張的一個地步,而且一直在增加,不久前又多出了一個Tornado,這也只是冰山一角,有人寫了一篇文章專門是在比較Python非同步網路函式庫的

Asynchronous Servers in Python

當然,這文章裡列出的也只是一部份而已

Twisted的Ajax/Comet做法

class Chatroom(resource.Resource):
    isLeaf = True
    def wait(self, request):
        """Wait for notifications
 
        """
        self.waitingRequests.append(request)
        log.debug('[%s] %d users are waiting',
                  self.name,
                  len(self.waitingRequests))
        return server.NOT_DONE_YET
 
    def notify(self):
        """Notify all waiting users
 
        """
        log.info('[%s] Notify %d users', self.name, len(self.waitingRequests))
        for request in self.waitingRequests:
            if not request._disconnected:
                userTick = int(request.args['tick'][0])
                messages = self.getMessagesByTick(userTick)
                result = dict(tick=self.tick, messages=messages)
                request.write(json.dumps(result))
                request.finish()
        self.waitingRequests = []

省略掉雜七雜八的部份,其實最關鍵的地方很簡單,在於Twisted.web的resource的render可以回傳server.NOT_DONE_YET,來用以延後資料的回傳,接著之後要回傳時,只要呼叫request.write()就可以寫資料回去,而當request要結束時呼叫request.finish()即可,就是這麼簡單,在設計上很簡單,當發現使用者的tick已經跟上最新進度,伺服器這邊沒有新的東西給使用者,既然如此就讓使用者等待事件發生,把request丟進waitingRequest的list中,而當有新的資料進來,就可以呼叫notify,把新的資料丟回給每個在等待的使用者

在一般網頁伺服器也能用Ajax/Comet的解決方案

當然,並不是所有網頁程式用非同步的網路框架寫起來都會輕鬆容易,很多時候我們希望能夠在一般的PHP、Django、TurboGears網頁程式裡也能有Ajax/Comet的好處,但不幸的是這些都是同步式的伺服器架構,兩者各有優缺點,於是我就想到一個解決方案,各取其長,很簡單的解決方案就是,我們將需要長期等待事件發生的request都丟給非同步的伺服器,而一般的網頁都在一般的網頁伺服器處理,要怎麼做呢? 想法很簡單

  1. 當一般的網頁伺服器有需要讓使用者等候事件發生的場合時,只要用xmlrpc呼叫非同步的網頁伺服器,建立並回傳一個對應事件的唯一的url,一般的網頁伺服器拿到url後將user轉向到此url,所有的request連到這個url在timeout前都會一直等待
  2. 當事件發生時,以xmlrpc呼叫非同步網頁伺服器來讓那些request停止等待
  3. 當client發現request已載入或失敗時,會連到一般的網頁伺服器檢查是否有新資料,有的話進行更新,沒有的話重覆步驟1繼續等待

為什麼建立的url需要是唯一的呢? 很簡單,當事件的url已存在,一般的伺服器要重導給client,在這段期間如果事件被觸發,url如果一樣有可能會等到新的事件,而當事件被觸發,url會回傳404,所以整個流程可以重跑一次,可以正確並正常執行

這樣的解決方案有個最大的優點,就是任何網頁程式開發工具,只要是能使用xmlrpc都可以建立Ajax/Comet事件,我原本聊天室的設計就是採用這樣的方案,但是同樣的它也有不少缺點,一來就是當事件url被載入後,還要再開另一個request到我TurboGears2伺服器要資料來更新,再來就是Cross-Domain的問題,Ajax的request有不能跨domain的限制,如果將伺服器分開,這樣就沒辦法用XMLHtmlRequest來送要求,得改用iframe等解決方案,然而這也不是無解,一來是新的browser支援一種新的協定,可以讓Ajax跨domain送request,我曾經有實作過,但發現其實支援的browser實在太有限,我當時試似乎只有FireFox和某些有支援,IE? 甭提了,因此考慮到佔了大半的使用者,那不會是好的解決方案,另一種做法是將兩種server掛在同一個前端server下,用virtual host的方式,但是有個問題,如果前端的伺服器是同步式的伺服器,會遇到我先前提到的問題,所以前端的也得是非同步的伺服器,nginx似乎就是一種,Twisted也可以辦到

因為這樣的寫法還得加資料庫進去,為了方便加上省掉那個檢查新資料用的request,於是我就將聊天室所有東西都放在同一個Twisted的伺服器,包括訊息的暫存,這樣就簡單又可靠

Twisted + Ajax/Comet = TwistedComet

上段中提到的解決方案,因為是用Twisted加上又是Ajax/Comet所以叫TwistedComet,我開了一個Google code project,把程式丟上去,其實程式非常地簡單,就只是如上面提到,提供xmlrpc介面,然後提供非同步request的等待和觸發等動作,專案的網址在此

TwistedComet

有興趣可以玩看看

Posted in 中文文章, Python | Tagged , , , , , , | 8 Comments

Python套件依賴關係圖工具: Gluttony

前一陣子在裝TurboGears2時遇到某個套件因為依賴的某個套件不支援Python2.6,無法使用easy_install成功安裝,必需先手動安裝那個套件再安裝TurboGears2,我當時覺得很討厭,就開始思考,當一個專案的依賴套件越來越多時,只要其中有一個套件不能用,整個系統可能就會因此無法使用,特別是在越底層的套件出問題,所造成的影響可能就會越大,我開始想說,如果我們能夠把這些套件之間的依賴關係圖用一個工具畫出來,那就可以瞭解它的依賴情況,如此一來在決定是否要使用或是找問題時會較容易,也可以研究依賴關係對於大型專案會造成的影響,於是就花了一點時間寫了一個工具,是基於pip上面的,它叫做Gluttony,如果有看過鋼之鍊金術士大概就知道那是什麼東西,七原罪之一的暴食,因為我看那工具在爬依賴套件時就像在瘋狂吃東西似的,於是就想到這名字,而且這個名字在pypi裡沒有人用,於是就選了這個名字

工具的用法和下載、安裝等等,都可以在Google code的頁面找到,在此就不贅述

Google code: Glottony

一些專案的依賴關係圖

當我完成這些工具之後,最開心的事就是去找一些專案來跑跑看,看到底會出現什麼結果

TurboGears1.1

TurboGears1.1依賴關系圖

像這樣的依賴關系其實算還好,簡單明瞭,而且也很有結構

TurboGears2

TurboGears2依賴關系圖

我們可以發現在TurboGears2的依賴關係又更複雜了一些,因為它是架構在Pylons上,所以大部份依賴都在Pylons上,而devtools是用於開發用的工具,事實上有一些細節上的依賴關係會在setup專案時才會被裝進來,因為TurboGears2講求的是彈性,像是Template language就可以支援好幾種,也可以自行將那部份換掉,所以圖上可能看不到,整體上來看,這樣的依賴關係算是還蠻健康的,層次分明,接著我們就來看一個病態一點的例子

Plone

Plone依賴關係圖

點圖片下載全尺吋圖檔

你一定在想這沱黑黑的到底是什麼東西,別懷疑,這個是Plone的套件依賴關系圖,全圖檔案大小是19.3MB,原本圖檔還太大,Graphiz還render不出來,我是縮小了一點尺吋才畫出來的,看了我真的覺得這實在是太誇張了,Plone到底是怎樣開發出來的,我很慶幸當時把ez2learn.com從Plone移到其它CMS,我原本還打算學Plone怎麼玩,還好當時覺得他底層東西實在太多,不然可能到現在我還在研究 囧

Posted in 中文文章, Python | Tagged , , , | 6 Comments

讓Python程式出錯時自動寄信給你

我喜歡Python的原因之一就是它的標準函式庫把該有的東西幾乎都包進去了,像是ConfigParser用來讀取設定檔,而logging更是不可缺少的,用來記錄程式的訊息非常好用的函式庫,而且這些函式庫不是只是陽春的玩具,而是功能齊全的基礎,其中logging一個很棒的功能就是將錯誤訊息寄給你,因為通常錯誤發生雖然都可以寫到記錄檔中,但是你有多少時間會去翻記錄檔? 通常程式出錯了,你也不會有認何知覺,將錯誤訊息寄給你的好處就是,一有錯誤你就立刻可以知道,而且少見的錯誤也不會被漏掉,例如有人試圖攻擊你的伺服器,在這過程中如果伺服器發生錯誤會寄信給你,在他成功之前你可能就有機會發現,總之將錯誤訊息寄給自己是很有用的技巧

一個錯誤報告的例子

最棒的就是,要用此功能非常簡單,以下是一個簡單的範例:

# -*- coding: utf8 -*-
import logging
import logging.handlers
 
rootLogger = logging.getLogger('')
rootLogger.setLevel(logging.ERROR)
handler = logging.handlers.SMTPHandler(
    mailhost='smtp.example.com',
    fromaddr='marines@starcraft2.com',
    toaddrs='player@starcraft2.com',
    subject="Houston, We've Got a Problem",
    credentials=('username', 'password')
)
rootLogger.addHandler(handler)
 
log = logging.getLogger(__name__)
log.fatal('HELP! We are under attack!')

如果你的SMTP設定沒問題的話,以應該可以收到下面這樣的一封信

就是這麼簡單,這樣一來當你的程式出問題時,你再也不會沒有發覺了

Posted in 中文文章, Python, Uncategorized | Tagged , , , | Leave a comment

99乘法表C++ Meta Programming版

前些日子有人在PTT C++版問了99乘法表相關的問題,於是我手癢就寫了一個C++ Meta-Programming的版本,沒用真正寫過Meta-Programming,因為感覺上實在是太炫技了,像是帕格尼尼的炫技曲似的,如果inline最佳化都成功的話,應該是會整個被展開來,用的是編譯時間來換效能,因為和一般的程式寫法和思考方式都不太一樣,所以這樣的東西還是盡量別亂用的好,不然別人通常都難以維護,所以這只是純寫好玩的

如果你在修C++的課,剛好遇到99乘法表的作業,或許你可以把這拿去交,看助教看了會有什麼反應 XD

以下是程式碼

<code>
 
#include <iostream>
 
using namespace std;
 
inline void printLine(int i, int j) {
    cout << i << " * " << j  << " = " << i * j << endl;
}
 
template <int I, int J>
struct Line {
    inline static void print() {
        Line<I, J-1>::print();
        printLine(I, J);
    }
};
 
template <int I>
struct Line<I, 1> {
    inline static void print() {
        printLine(I, 1);
    }
};
 
template <int NUM, int N>
struct Group {
    inline static void print() {
        Group<NUM, N - 1>::print();
        cout << endl;
        Line<N, NUM>::print();
    }
};
 
template <int NUM>
struct Group<NUM, 1> {
    inline static void print() {
        Line<1, NUM>::print();
    }
};
 
template <int NUM>
struct Meta99 {
    inline static void print() {
        Group<NUM,NUM>::print();
    }
};
 
int main() {
    Meta99<20>::print();
    return 0;
}
 
</code>
Posted in 中文文章, C/C++ | Tagged , | 5 Comments

化整為零的次世代網頁開發標準: WSGI

今天,我要介紹Python網頁開發的標準: WSGI,我個人在看見這類英文縮寫時,都一定會試著去記住它的全寫,因為縮寫本身一點意義都沒有,難以記憶,WSGI的全寫是"Web Server Gateway Interface",它的發音有點像是whiskey,光知道這個名字還是很難理解這到底是用來做什麼用的,簡單的來說,它是Python定義網頁程式和伺服器溝通的介面,如果你有寫過CGI (Common Gateway Interface),它的作用基本上就是和CGI類似的功用,定義一個標準的溝通方式,讓你寫的程式可以和伺服器溝通,但是WSGI不是設計用來給任何語言使用的,它是設計給Python用的,而它其實是基於CGI的延伸,在Python的部份進一步做更多的定義,而因為他是基於CGI,所以它也可以和CGI的介面相容,只要透過一個轉接器,就能把WSGI的程式接到CGI,說了這麼多,相信大部份人對於WSGI是什麼還是一頭霧水,會有一堆疑問,為什麼有了CGI還要有WSGI? Middleware又是什麼? 這很正常,我一開始也對WSGI一點概念都沒有,接下來我們就來介紹WSGI的特色。

Continue reading

Posted in 中文文章, Python | Tagged , , , , , , , , , | 14 Comments

如何評估比較程式語言

有人提到以過去的觀點來看現在的PHP是不公平的,確實我有好一陣子沒有碰PHP,對於新版的PHP並不瞭解,因此我花了一些時間檢視新的PHP規格,的確新版的PHP遠比我預料的進步許多,很多之前提到的問題有所改善,我承認上一篇文章說PHP很爛是在發洩過去對於PHP的種種不滿,有人認為程式語言沒有好壞,全部都事在人為,而我認為程式語言的好壞有主觀也有客觀的部份,當你討厭或喜歡一個程式語言一定有某些原因,但就客觀的來看語言的設計來講,PHP在早期真的是爛得一榻糊塗,是公認的爛,但是在當時少有選擇,能選的工具不多,就只有PHP,在累積夠久的不滿後就會一次暴發開來,對於現在的PHP5.3還有未來的PHP6而言,他已經跳離了以前的單純語言設計上的爛,往更好的方向前進,我能說PHP的壞話少了很多,因為當語言層面的問題大部份都解決了,接著就是哲學和個人喜好的問題了,對與目前和新版的PHP的誤解在這邊說聲抱歉

所以這篇我想說的,怎樣看客觀地評估一款語言的好壞,還有主觀地評估好與壞,很多人說程式語言沒有好與壞,只有適合和不適合,基本上我不認同這樣的說法,如果說兩種差別很大用途不同的語言拿來一起比較就很奇怪,但是如果是性質接近的語言都符合你需求,就可以做好壞的比較,不然你要怎麼做選擇? 就如同我們買車子好了,你的目的是載貨,買的自然是卡車、貨車,但是如果是轎車呢? 就算是卡車也有選擇的,程式語言也一樣,目前大部份人所見到的程式語言,都是所謂的"通用目的"語言,也就是說這樣的語言沒有預設任何用途,基本上他可以拿來做任何用途,很多人因為不知道要從何比較起,所以都說程式語言沒有好壞之分,只有適合和不適合,如果有一堆工具都適合,既然沒有好壞之分,那你又該如何做出進一步的評估與選擇? 適不適合不就是透過比較而來的嗎? 不比較怎麼知道適不適合? 這不是自相矛盾嗎? 只靠喜好嗎? 閉著眼睛亂選? 大家都知道車子我們可以比較它的馬力、耗油、安全評等,但是大多數人不知道程式語言該比較些什麼,而我今天所要說的,就是程式語言該拿什麼來比較

Continue reading

Posted in 中文文章, 分享, Python | Tagged , , , , , , | 17 Comments

以前的PHP有很多缺陷

有人在plurk上PO了一篇文章的連結,PHP 開發迷思 (三) – PHP 很糟糕?,因為我不認同那樣的看法,所以我回覆說php很爛,當你說一個語言很爛時,就要有心理準備有人準備要跟你戰了,果然有人不認同我的說法,當然任何人都可以不認同我所說的,而且好和爛是很主觀的,同時也是是相對的,但是我所說的是有根據的,說php爛不是我一個人的說法,而是已經被說到爛掉的說法,很多比我有經驗多的網頁程式設計師都異口同聲的說PHP很爛,為了解釋為什麼我認為PHP很爛(我在本文指的爛是指語言設計上的眾多嚴重缺陷),我寫了這篇文章

更新:

我承認這篇寫得有點偏激,也是以前的觀點,也是在發洩以前對PHP的不滿,我沒有寫較新的PHP,所以我收回PHP很爛,改成以前的PHP有很多缺陷,我想表達的是一款語言的很多缺陷,而且是在以前的缺陷,現在PHP確實有改進很多,所以我寫了一篇 如何評估比較程式語言

另外我想表達的一件事是,當有人說你用的程式語言有問題時,為什麼一定要這麼抓狂呢? 如果一個程式語言它的缺陷沒有人罵,大家都愛語言如命,那麼語言的開發者或團隊要如何知道這語言要改進什麼呢? PHP有很多缺陷是事實,然而說出來很多人可能就不高興,然而我選擇用較激烈的字眼是因為他改進實在太慢了,我們可以看到以前的Magic quote、unicode問題等等,至今都仍未解決,要php6才會解決,這些問題不是只有我覺得很糟,而是我發現很多人都跟我一樣覺得這太差了,我每次罵PHP都真的很希望他在下一個版本就把這些鳥問題解決掉,但以我之前的經驗它都讓我失望,所以我怨念很深,這篇文章寫起來也特別偏激,但我想表達的是,我說PHP爛某種程度是希望他變好,那你可能會問,我為什麼不說Python爛,然後哪裡爛,很簡單的原因是我太喜歡Python,當你很喜歡一個東西時你是看不見這東西的缺點,如果有人說Python爛,我覺得很好,我想知道他有哪些缺陷,是我沒看到的

除此之外,對於初學者來說,PHP的低門檻讓他成為吸引新手的蜜罐,而很多人都只知道PHP可以寫網頁,但是不知道其實所有程式語言都能寫網頁,我希望透過罵PHP也能讓更多人知道其實還有更多選擇,但其實這會讓人覺得反感,我在這背後目的是希望其它語言能夠有更多人使用,有競爭才有進步,而對於國內大部份人都只用PHP在刻網頁我也覺得很失望,我同時也希望讓大家知道其實網頁的技術現在已經很先進了,很少有人使用框架等次世代的網頁技術,所以有興趣也可以看一些較新的技術,而不是土法煉鋼

Continue reading

Posted in 中文文章, 分享, WTF | Tagged , , , , | 48 Comments

以嵌入式系統實作擴增實境導航系統

終於,我們的畢業專題完成了,我們的方向是車載嵌入式系統,也就是車上的電腦,能做的東西其實蠻多的,但是市面上可能都已經有那樣的產品存在,於是我想做一些比較不一樣的,而在當時我有看見Android手機的Google街景功能,手機轉動,畫面也會跟著動,於是我就想到,為何不能把導航系統以擴增實境的方式呈現呢? 如果我們在車上裝攝影機,然後將拍到的影像和地圖資訊結合,這樣可以用更直覺方式瞭解地圖的資訊,這樣除了直覺還有個好處,就是順便可以做為行車記錄系統,一舉兩得,而市面上似乎沒有這樣的產品,我比較喜歡新奇的東西,如果市面上的產品都做到爛掉了,做一個同樣的東西出來不是很有趣,因此就決定這樣的題目

這樣的導航系統,路都是像下面這張合成圖一樣,貼在拍到的畫面上,所以駕駛人只要看一眼,就能知道路哪條是哪條,不過由於是畢業專題,能做的時間有限,所以只完成了道路的繪製,路名等其它的資訊就沒有加上去

nav01

在當繪圖系統搞定之後,我們還整合了GPS和電子羅盤,最候我們進行了實機的demo,我們把嵌入式系統擺在手推車上,然後在校園裡推著跑,不得不抱怨,那GPS有夠爛,常常會自己亂飄移,經過樹下就馬上定位失敗,比起我的手機,定位準多了,不過拿AGPS比GPS或許不太公平,而一路上又因為學校的地上是磚路,不停的震動,電子羅盤是接在麵包板上的,用rs232的線接板子,所以常常三不五會震掉,攝影機也亂晃,所以後來找來膠帶貼起來,不過因為程式寫得有點趕,所以似乎有bug,跑到一半不明的原因程式終止了,可以聽見哀豪的聲音,以下是demo的攝影

這樣一個以擴增實境的概念做導航的系統來說,我覺得已經可以現擴增實境導航系統的優勢,不過還有很多問題得解決,像是GPS定位實在不夠精準,可能得改用AGPS來達成更精確的定位,又或著判斷目前座標在地圖上最近的道路是哪一條,我發現市面上好像有些導航系統就是這樣做,這麼一來就能讓畫面的顯示更準確,還有當車子開在上坡和下坡時,應該也要能感應路面的坡度,然後調整投影的參數

雖然這只是一個有些實驗性質的系統,但是在這樣完成之後,我覺得用擴增實境來做導航真的可以試試看,有興趣的廠商可以試著開發看看,優點就像我所說的,道路的地理空間關系可以直覺地被瞭解,而且市面上也有行車記錄系統,做為導航系統,它同時也可以記錄拍到的畫面,做為行車記錄系統,當然,也是有些缺點,缺點就是需要額外的攝影機,還得拉到車子上安裝,這表示需要額外的成本,裝設上也會比較困難,但如果是車子本身就內建這樣的系統,就沒有裝設的問題

最後,希望有一天在市面上可以看見擴增實境的導航系統

Posted in 專題, 中文文章 | Tagged , , , , | 8 Comments

黑心電線

今天在忙著趕畢業專題的電子羅盤時,一直有鬼打牆的情況發生,沒多久前還運作得好好的,一下子突然停掉了,一開始我以為是麵包板上的線插壞或鬆了,花了我一整個晚上在找問題的來源,我拿著三用電錶到處量電壓,終於,最後我發現,一條USB接過來的電源線,在某個鱷魚夾還有將近5V的電壓,但經過某條線之後,電壓就有很大的機會變得很低,那條線很敏感,只要稍微扯動一下,電壓就會變很低,拉USB當電源線,是因為電子羅盤需要5V的電壓驅動,之所以會一下子能運作,一下子運作不了,就是那條線造成的,一開始我認為是接頭觸鬆掉了,於是將金屬接頭的螺斯鬆開,打算截掉後面一點的外皮,再重接上去,然而,當我把金屬接頭拿下來,用剪刀在絕緣外皮切一圈,接著轉動外皮要將它拔下來,其中一條線很容易就把皮轉下來,裡面亮晶晶的銅線露了出來,而另一條線,一轉裡面的導線就斷了,一開始我認為應該是我切深所以斷掉了,再往後面的地方截,下場也是一樣,於是我發現,那條一截就斷的線,之所以會斷是以經繡蝕得很嚴重了,我一開始認為,應該只是因為較接近終端,所以有空氣跑進去讓它生鏽了,於是再往更後面截,截了好幾次,都遇到這種生鏽的線,開始覺得不對勁,那線生鏽到粉狀的地部了,都已經切這麼後面了,絕緣外皮還好好的,沒理由線會鏽成那樣,而且比照另外一條線,是亮晶晶的狀態

IMAG0177

而到了更後面點的地方,原本沒鏽的線生鏽了,生鏽的線那條到那裡變沒鏽了,仔細想想,銅生鏽好像是綠色的,所以那好像也不是生鏽,我開始想,這一點都不像是後來才變成這樣的,像是一開始包進去時就是這個樣子,搜尋了一下"黑心電線",果不其然,發現大陸果然有黑心電線

湖北红旗电缆有限责任公司生产销售的"黑心"电线

情況也很類似,包廢線進去,都已經截到這麼後面了,線還是鏽的,我只能說,這百分之九十九是黑心電線,為了這條黑心電線,我浪費了整個晚上在找出問題在哪,好樣的黑心電線,就感心

Posted in 中文文章, WTF | Tagged | 1 Comment