<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    莊周夢(mèng)蝶

    生活、程序、未來(lái)
       :: 首頁(yè) ::  ::  :: 聚合  :: 管理


        今年在閱讀某個(gè)項(xiàng)目源碼的時(shí)候看到DelayQueue的使用,xmemcached 1.2.6.1的重連任務(wù)也是采用DelayQueue管理,ReconnectRequest實(shí)現(xiàn)Delayed接口,我突然想起去review下xmc的源碼,發(fā)現(xiàn)一個(gè)嚴(yán)重的BUG,原始代碼如下:
    public final class ReconnectRequest implements Delayed {
          
    public long getDelay(TimeUnit unit) {
            
    return  nextReconnectTimestamp - System.currentTimeMillis();
        }
    }

        getDelay返回該任務(wù)還剩下多少時(shí)間可以被執(zhí)行,將下次執(zhí)行的時(shí)間戳減去當(dāng)前時(shí)間即可,問(wèn)題在于這里返回的是毫秒,而沒(méi)有調(diào)用getDelay傳入的TimeUnit做轉(zhuǎn)換,在DelayQueue內(nèi)部其實(shí)是用納秒做單位交給Condition對(duì)象去等待
      for (;;) {
                    E first 
    = q.peek();
                    
    if (first == null) {
                        available.await();
                    } 
    else {
                        
    long delay =  first.getDelay(TimeUnit.NANOSECONDS);
                        
    if (delay > 0) {
                            
    long tl = available.awaitNanos(delay);
                        } 
    else {
                            E x 
    = q.poll();
                            
    assert x != null;
                            
    if (q.size() != 0)
                                available.signalAll(); 
    // wake up other takers
                            return x;

                        }
                    }
                }
      
         最終導(dǎo)致的問(wèn)題是,awaitNanos很快返回(awaitNanos接受的是納秒,這里卻傳入毫秒),循環(huán)執(zhí)行發(fā)現(xiàn)重新計(jì)算的delay仍然大于0,循環(huán)等到getDelay返回的越來(lái)越小直到0才執(zhí)行相應(yīng)的Task,,造成的現(xiàn)象是在重連的時(shí)候cpu占用率很高。

         單元測(cè)試的時(shí)候沒(méi)有發(fā)現(xiàn)這個(gè)問(wèn)題,主要是因?yàn)楣δ苷?,沒(méi)有關(guān)注資源消耗情況,因此慚愧地忽略了。

          解決的辦法很簡(jiǎn)單,修改getDelay方法即可:
        public long getDelay(TimeUnit unit) {
            
    return unit.convert(
                    nextReconnectTimestamp 
    - System.currentTimeMillis(),
                    TimeUnit.MILLISECONDS);
        }

          這個(gè)BUG比較嚴(yán)重,已經(jīng)升級(jí)1.2.6.1的朋友建議馬上升級(jí)到1.2.6.2,使用maven的朋友只要修改版本即可,沒(méi)有使用maven的請(qǐng)到這里下載。

    posted @ 2010-10-22 15:52 dennis 閱讀(4708) | 評(píng)論 (4)編輯 收藏


        NIO中的Selector封裝了底層的系統(tǒng)調(diào)用,其中wakeup用于喚醒阻塞在select方法上的線程,它的實(shí)現(xiàn)很簡(jiǎn)單,在linux上就是創(chuàng)建一個(gè)管道并加入poll的fd集合,wakeup就是往管道里寫一個(gè)字節(jié),那么阻塞的poll方法有數(shù)據(jù)可讀就立即返回。證明這一點(diǎn)很簡(jiǎn)單,strace即可知道:
    public class SelectorTest {
        
    public static void main(String[] args) throws Exception {
            Selector selector 
    = Selector.open();
            selector.wakeup();
        }
    }

         使用strace調(diào)用,只關(guān)心write的系統(tǒng)調(diào)用
    sudo strace --e write java SelectorTest

         輸出:
    Process 29181 attached
    Process 
    29182 attached
    Process 
    29183 attached
    Process 
    29184 attached
    Process 
    29185 attached
    Process 
    29186 attached
    Process 
    29187 attached
    Process 
    29188 attached
    Process 
    29189 attached
    Process 
    29190 attached
    Process 
    29191 attached
    [pid 
    29181] write(36"\1"1)          = 1
    Process 
    29191 detached
    Process 
    29184 detached
    Process 
    29181 detached

        有的同學(xué)說(shuō)了,怎么證明這個(gè)write是wakeup方法調(diào)用的,而不是其他方法呢,這個(gè)很好證明,我們多調(diào)用幾次:
    public class SelectorTest {
        
    public static void main(String[] args) throws Exception {
            Selector selector 
    = Selector.open();
            selector.wakeup();
            selector.selectNow();
            selector.wakeup();
            selector.selectNow();
            selector.wakeup();
        }
    }

        修改程序調(diào)用三次wakeup,心細(xì)的朋友肯定注意到我們還調(diào)用了兩次selectNow,這是因?yàn)樵趦纱纬晒Φ膕elect方法之間調(diào)用wakeup多次都只算做一次,為了顯示3次write,這里就每次調(diào)用前select一下將前一次寫入的字節(jié)讀到,同樣執(zhí)行上面的strace調(diào)用,輸出:

    Process 29303 attached
    Process 
    29304 attached
    Process 
    29305 attached
    Process 
    29306 attached
    Process 
    29307 attached
    Process 
    29308 attached
    Process 
    29309 attached
    Process 
    29310 attached
    Process 
    29311 attached
    Process 
    29312 attached
    Process 
    29313 attached
    [pid 
    29303] write(36"\1"1)          = 1
    [pid 
    29303] write(36"\1"1)          = 1
    [pid 
    29303] write(36"\1"1)          = 1
    Process 
    29313 detached
    Process 
    29309 detached
    Process 
    29306 detached
    Process 
    29303 detached

         果然是3次write的系統(tǒng)調(diào)用,都是寫入一個(gè)字節(jié),如果我們?nèi)サ魋electNow,那么三次wakeup還是等于一次:
    public class SelectorTest {
        
    public static void main(String[] args) throws Exception {
            Selector selector 
    = Selector.open();
            selector.wakeup();
            selector.wakeup();
            selector.wakeup();
        }
    }
     
       輸出:
    Process 29331 attached
    Process 
    29332 attached
    Process 
    29333 attached
    Process 
    29334 attached
    Process 
    29335 attached
    Process 
    29336 attached
    Process 
    29337 attached
    Process 
    29338 attached
    Process 
    29339 attached
    Process 
    29340 attached
    Process 
    29341 attached
    [pid 
    29331] write(36"\1"1)          = 1
    Process 
    29341 detached
    Process 
    29337 detached
    Process 
    29334 detached
    Process 
    29331 detached

          wakeup方法的API說(shuō)明沒(méi)有欺騙我們。wakeup方法的API還告訴我們,如果當(dāng)前Selector沒(méi)有阻塞在select方法上,那么本次wakeup調(diào)用會(huì)在下一次select阻塞的時(shí)候生效,這個(gè)道理很簡(jiǎn)單,wakeup方法寫入一個(gè)字節(jié),下次poll等待的時(shí)候立即發(fā)現(xiàn)可讀并返回,因此不會(huì)阻塞。

         具體到源碼級(jí)別,在linux平臺(tái)上的wakeup方法其實(shí)調(diào)用了pipe創(chuàng)建了管道,wakeup調(diào)用了EPollArrayWrapperinterrupt方法:
    public  void interrupt() 

    {
            interrupt(outgoingInterruptFD);
    }

        實(shí)際調(diào)用的是interrupt(fd)的native方法,查看EPollArrayWrapper.c可見(jiàn)清晰的write系統(tǒng)調(diào)用:

    JNIEXPORT 
    void JNICALL
    Java_sun_nio_ch_EPollArrayWrapper_interrupt(JNIEnv 
    *env, jobject this, jint fd)
    {
        
    int fakebuf[1];
        fakebuf[
    0= 1;
        
    if (write(fd, fakebuf, 1< 0) {
            JNU_ThrowIOExceptionWithLastError(env,
    "write to interrupt fd failed");
        }
    }
        寫入一個(gè)字節(jié)的fakebuf。有朋友問(wèn)起這個(gè)問(wèn)題,寫個(gè)注記在此。strace充分利用對(duì)了解這些細(xì)節(jié)很有幫助。
     

    posted @ 2010-10-22 10:35 dennis 閱讀(6228) | 評(píng)論 (3)編輯 收藏


         Xmemcached 1.2.6.1正式發(fā)布,這個(gè)版本的主要是做bug fix以及一些細(xì)節(jié)改進(jìn),主要變動(dòng)如下:

    1、修復(fù)BUG,包括:
    Issue 85: 當(dāng)存在多個(gè)MemcachedClient的時(shí)候,JMX的統(tǒng)計(jì)只顯示其中一個(gè)
    Issue 87: 當(dāng)使用一致性哈希的時(shí)候,連接池不起作用
    Issue 90: 用戶線程中斷引起的連接關(guān)閉
    Issue 94:BinaryMemcachedClientUnitTest測(cè)試失敗
    Issue 95: JMX addServer,removeServer存在缺陷
    Issue 96: OOM Error while decompressing 60 KB of actuall data
    Issue 97: 使得關(guān)閉連接更友好

    2、改進(jìn)重連機(jī)制,重連不再是以固定間隔(默認(rèn)2秒)做重試連接,而是以一個(gè)等差數(shù)列遞增間隔時(shí)間,第一次2秒,第二次4秒,第三次6秒……直到最大間隔時(shí)間1分鐘做重連嘗試。

    3、改善關(guān)閉機(jī)制,關(guān)閉連接前發(fā)送quit命令,盡量做到友好關(guān)閉,等待服務(wù)器主動(dòng)斷開(kāi)連接。

    4、添加新的方法用于設(shè)置XmemcachedClient實(shí)例名稱,用于區(qū)分不同的緩存集群,方便統(tǒng)計(jì)顯示:
    public interface MemcachedClient{
           
    /**
         * Return the cache instance name
         * 
         * 
    @return
         
    */
        
    public String getName();

        
    /**
         * Set cache instance name
         * 
         * 
    @param name
         
    */
        
    public void setName(String name);
    }

    名稱也可通過(guò)spring配置。

    5、提供英文的用戶指南,非常感謝cnscud的幫助,這份文檔是他一人搞定的。

    6、更新了Java Memcached Client Benchmark,跟最新版本的spymemcached,Java-Memcached-Client做對(duì)比測(cè)試,提供給需要的朋友參考。


    項(xiàng)目首頁(yè)  http://code.google.com/p/xmemcached/
    下載地址  http://code.google.com/p/xmemcached/downloads/list
    wiki地址   http://code.google.com/p/xmemcached/w/list




    posted @ 2010-10-17 14:06 dennis 閱讀(2449) | 評(píng)論 (0)編輯 收藏


         Xmemcached 1.2.6.1 released,所以更新了一下Java Memcached Client Benchmark。對(duì)比下Xmemached,SpymemcachedJava-Memcached-Client這三個(gè)開(kāi)源客戶端的性能,具體的測(cè)試信息可以看這個(gè)鏈接。

        測(cè)試源碼
    svn co http://xmemcached.googlecode.com/svn/trunk/benchmark/
        測(cè)試結(jié)果:
    svn co http://xmemcached.googlecode.com/svn/trunk/benchmark/result

        總結(jié)下測(cè)試結(jié)果,為還在選擇和考察java memcached client的朋友提供參考:

    1、Java-Memcached-Client 2.5.1這個(gè)版本果然有很大改進(jìn),性能上有非常大的提升,從測(cè)試結(jié)果來(lái)看在小于100并發(fā)下有非常明顯的優(yōu)勢(shì),同時(shí)耗費(fèi)資源也相對(duì)較多。但是在300并發(fā)訪問(wèn)下,Java-Memcached-Client會(huì)不斷地報(bào)錯(cuò):
    com.schooner.MemCached.SchoonerSockIOPool Sun Oct 17 11:09:05 GMT+08:00 2010 - ++++ failed to get SockIO obj for10.232.36.82:12000
    com.schooner.MemCached.SchoonerSockIOPool Sun Oct 17 11:09:05 GMT+08:00 2010 - ++++ failed to get SockIO obj for: 10.232.36.82:12000
    com.schooner.MemCached.SchoonerSockIOPool Sun Oct 17 11:09:05 GMT+08:00 2010 - ++++ failed to get SockIO obj for: 10.232.36.90:12000
    com.schooner.MemCached.SchoonerSockIOPool Sun Oct 17 11:09:05 GMT+08:00 2010 - ++++ failed to get SockIO obj for: 10.232.36.82:12000
    com.schooner.MemCached.SchoonerSockIOPool Sun Oct 17 11:09:05 GMT+08:00 2010 - ++++ failed to get SockIO obj for: 10.232.36.90:12000
    com.schooner.MemCached.SchoonerSockIOPool Sun Oct 17 11:09:05 GMT+08:00 2010 - ++++ failed to get SockIO obj for: 10.232.36.82:12000


     并且無(wú)法正常地存取數(shù)據(jù), 而xmc和spy卻可以正常應(yīng)對(duì)這一場(chǎng)景。因此可以看到在300并發(fā)下,Java-Memcached-Client測(cè)試的結(jié)果直接為0,因?yàn)闇y(cè)試無(wú)法完成。盡管我嘗試將最大連接數(shù)調(diào)到2000,仍然是無(wú)法完成測(cè)試。

    2、Xmemcached無(wú)論在低并發(fā)還是高并發(fā)訪問(wèn)的情況下,都可以保持一個(gè)比較優(yōu)秀的性能表現(xiàn),從xmc和spy的對(duì)比來(lái)看,xmc的優(yōu)勢(shì)相當(dāng)大。

    3、從用戶選擇角度來(lái)說(shuō),如果你的應(yīng)用對(duì)memached的訪問(wèn)負(fù)載并不高,Java-Memcached-Client是一個(gè)不錯(cuò)的選擇,但是在高峰訪問(wèn)的時(shí)候可能命中率會(huì)有個(gè)急劇的波動(dòng);如果你的應(yīng)用訪問(wèn)memached的負(fù)載較高,此時(shí)我推薦你選擇xmemcached;如果你需要異步的批量處理(future模式),可以選擇spymemcached;如果你不知道你的應(yīng)用是什么狀況,我推薦你用xmemcached,可以在任何情況下獲得一個(gè)比較好的性能表現(xiàn)。


    posted @ 2010-10-17 13:44 dennis 閱讀(4201) | 評(píng)論 (0)編輯 收藏


        Kilim是一個(gè)Java的actor框架,讓你可以在JVM里使用基于協(xié)程的actor模型,bluedavy曾經(jīng)介紹過(guò),這里不再贅言。這篇blog的目的在于分析下kilim實(shí)現(xiàn)的基本原理,看看怎么在JVM上實(shí)現(xiàn)協(xié)程。

        在一些語(yǔ)言層面上支持協(xié)程的語(yǔ)言,如lua、ruby,都是直接在VM級(jí)別支持協(xié)程,VM幫你做context的保存和恢復(fù)。JVM沒(méi)有提供這樣的指令來(lái)保存和恢復(fù)方法棧的狀態(tài),因此kilim的實(shí)現(xiàn)還是需要在bytecode級(jí)別做文章。首先,試想下,如果是你來(lái)實(shí)現(xiàn)協(xié)程,你會(huì)怎么做?協(xié)程的兩個(gè)基本原語(yǔ)resume和yield,resume運(yùn)行協(xié)程,yield讓出執(zhí)行權(quán),下次resume的時(shí)候會(huì)從yield的地方重新執(zhí)行,并且context保持不變??梢?jiàn),你需要做這么幾個(gè)事情:
    1、在yield的時(shí)候保存當(dāng)前context。
    2、在resume的時(shí)候恢復(fù)context,并根據(jù)pc計(jì)數(shù)來(lái)決定從哪里恢復(fù)執(zhí)行。
    3、半?yún)f(xié)程的實(shí)現(xiàn)來(lái)說(shuō),還需要一個(gè)調(diào)度器來(lái)調(diào)度所有協(xié)程。
    4、為了做到用戶代碼透明,可能需要某種手段去修改用戶代碼,自動(dòng)幫你做上面三個(gè)事情。

        kilim的實(shí)現(xiàn)就是干了這么幾個(gè)事情:
    1、利用字節(jié)碼增強(qiáng),將普通的java代碼轉(zhuǎn)換為支持協(xié)程的代碼。
    2、在調(diào)用pausable方法的時(shí)候,如果pause了就保存當(dāng)前方法棧的State,停止執(zhí)行當(dāng)前協(xié)程,將控制權(quán)交給調(diào)度器
    3、調(diào)度器負(fù)責(zé)調(diào)度就緒的協(xié)程
    4、協(xié)程resume的時(shí)候,自動(dòng)恢復(fù)State,根據(jù)協(xié)程的pc計(jì)數(shù)跳轉(zhuǎn)到上次執(zhí)行的位置,繼續(xù)執(zhí)行。


        下面是來(lái)自kilim文檔的一個(gè)例子,同一段代碼,在字節(jié)碼增前前后的變化:

       
    左邊是原始代碼,右邊是通過(guò)字節(jié)碼增強(qiáng)后的代碼。其中h方法是pausable的,也就是說(shuō)可能被暫停阻塞的,g方法因?yàn)檎{(diào)用了h這個(gè)方法也變成了pausable。

    首先看,原始的h方法是沒(méi)有傳入任何參數(shù)的,增強(qiáng)后的代碼,多了個(gè)參數(shù)Fiber,指向當(dāng)前的協(xié)程,同樣,g方法本來(lái)只有一個(gè)參數(shù)n,現(xiàn)在在后面也多了個(gè)Fiber類型的參數(shù),同樣是指向當(dāng)前執(zhí)行的協(xié)程。

    其次,在原始的g方法里,一旦調(diào)用,馬上進(jìn)入一個(gè)for循環(huán)。但是在增強(qiáng)后的代碼,多了個(gè)switch派發(fā)的過(guò)程,這就是前面提到的,根據(jù)當(dāng)前的Fiber的pc計(jì)數(shù),跳轉(zhuǎn)到上一次執(zhí)行的地方執(zhí)行。如果是第一次resume,也就是啟動(dòng)協(xié)程,那么就將初始循環(huán)的i設(shè)置為0,進(jìn)入原始代碼的循環(huán)部分。Fiber有一個(gè)pc計(jì)數(shù),稱為程序計(jì)數(shù)器,用于指向恢復(fù)context的時(shí)候需要跳轉(zhuǎn)到位置。

    第三,在g方法里調(diào)用h這個(gè)可被暫停阻塞的方法的時(shí)候,在h方法前后多了一些調(diào)用:
    f.down();
    h(f);
    f.up();

    kilim的Fiber將每個(gè)pauseable方法的調(diào)用組織成一個(gè)棧,每個(gè)pauseable方法都有一個(gè)activation frame,翻譯過(guò)來(lái)可以稱為活動(dòng)棧幀,這個(gè)棧幀記錄了當(dāng)前的棧的State,注意這個(gè)棧跟java本身的方法調(diào)用棧區(qū)分開(kāi)來(lái),一個(gè)是VM層面的,一個(gè)是kilim框架層面的。這里的down方法就是將棧向下延伸,表示將調(diào)用一個(gè)pauseable方法,并且設(shè)置當(dāng)前State和pc計(jì)數(shù)。
    調(diào)用了down之后,才是調(diào)用實(shí)際的h方法,最后還要調(diào)用一次up,顧名思義,就是說(shuō)一次pauseable方法調(diào)用完成,fiber的活動(dòng)棧要遞增一層,回到上一層。但是h方法調(diào)用可能出現(xiàn)四種情況:
    1、正常的順利返回,沒(méi)有狀態(tài)需要恢復(fù),所謂NOT_PAUSING__NO_STATE
    2、也是正常返回,有狀態(tài)需要恢復(fù),也就是NOT_PAUSING__HAS_STATE
    3、h方法暫停阻塞,當(dāng)前沒(méi)有保存狀態(tài),需要保存狀態(tài),這是第一次暫停的時(shí)候,稱為PAUSING__NO_STATE
    4、h方法暫停阻塞,當(dāng)前已經(jīng)有狀態(tài),不需要保存狀態(tài),這是第一次暫停之后的resume再次暫停,稱為PAUSING__HAS_STATE,通常不需要處理什么。

    第四,可以看到,在up之后,就要根據(jù)up返回的上述4種狀態(tài)執(zhí)行不同的邏輯:
    if (f.isPausing){
        
    //第一次暫停,沒(méi)有狀態(tài)
       if (!f.hasState){
         
    //new一個(gè)State_I2,并保存i和n
         f.state = new State_I2(i,n);
        
    //記錄pc,還記的前面的switch嗎?
         f.pc = H1;
       }
       
    return;
    else if (f.hasState)
       
    //正常返回,有狀態(tài)需要恢復(fù),恢復(fù)i和n
       State_I2 st = (State_I2) f.state;
       i 
    = st.i1; n = st.i2;
    }


    這里沒(méi)有處理NOT_PAUSING__NO_STATE和PAUSING__HAS_STATE,因?yàn)檫@兩種情況在這里不需要處理。


        通過(guò)上面的分析,我想大家對(duì)kilim的實(shí)現(xiàn)應(yīng)該已經(jīng)有一個(gè)很基本的認(rèn)識(shí)。下一步,我們分析一個(gè)實(shí)際的代碼例子,查看整個(gè)運(yùn)作流程。
     

    posted @ 2010-09-17 12:05 dennis 閱讀(10524) | 評(píng)論 (5)編輯 收藏


        java.util.LinkedList是雙向鏈表,這個(gè)大家都知道,比如Java的基礎(chǔ)面試題喜歡問(wèn)ArrayList和LinkedList的區(qū)別,在什么場(chǎng)景下用。大家都會(huì)說(shuō)LinkedList隨機(jī)增刪多的場(chǎng)景比較合適,而ArrayList的隨機(jī)訪問(wèn)多的場(chǎng)景比較合適。更進(jìn)一步,我有時(shí)候會(huì)問(wèn),LinkedList.remove(Object)方法的時(shí)間復(fù)雜度是什么?有的人回答對(duì)了,有的人回答錯(cuò)了?;卮疱e(cuò)的應(yīng)該是沒(méi)有讀過(guò)源碼。

        理論上說(shuō),雙向鏈表的刪除的時(shí)間復(fù)雜度是O(1),你只需要將要?jiǎng)h除的節(jié)點(diǎn)的前節(jié)點(diǎn)和后節(jié)點(diǎn)相連,然后將要?jiǎng)h除的節(jié)點(diǎn)的前節(jié)點(diǎn)和后節(jié)點(diǎn)置為null即可,
    //偽代碼
    node.prev.next=node.next;
    node.next.prev
    =node.prev;
    node.prev
    =node.next=null;

    這個(gè)操作的時(shí)間復(fù)雜度可以認(rèn)為是O(1)級(jí)別的。但是LinkedList的實(shí)現(xiàn)是一個(gè)通用的數(shù)據(jù)結(jié)構(gòu),因此沒(méi)有暴露內(nèi)部的節(jié)點(diǎn)Entry對(duì)象,remove(Object)傳入的Object其實(shí)是節(jié)點(diǎn)存儲(chǔ)的value,這里還需要一個(gè)查找過(guò)程:
      public boolean remove(Object o) {
            
    if (o==null) {
                
    for (Entry<E> e = header.next; e != header; e = e.next) {
                    
    if (e.element==null) {
                        remove(e);
                        
    return true;
                    }
                }
            } 
    else {
                
    //查找節(jié)點(diǎn)Entry
                for (Entry<E> e = header.next; e != header; e = e.next) {
                    
    if (o.equals(e.element)) {
                        
    //刪除節(jié)點(diǎn)
                        remove(e);
                        
    return true;
                    }
                }
            }
            
    return false;
        }


        刪除節(jié)點(diǎn)的操作就是剛才偽代碼描述的:
       private E remove(Entry<E> e) {
            E result 
    = e.element;
        e.previous.next 
    = e.next;
        e.next.previous 
    = e.previous;
            e.next 
    = e.previous = null;
            e.element 
    = null;
        size
    --;
        modCount
    ++;
            
    return result;
        }

        因此,顯然,LinkedList.remove(Object)方法的時(shí)間復(fù)雜度是O(n)+O(1),結(jié)果仍然是O(n)的時(shí)間復(fù)雜度,而非推測(cè)的O(1)復(fù)雜度。最壞情況下要?jiǎng)h除的元素是最后一個(gè),你都要比較N-1次才能找到要?jiǎng)h除的元素。

        既然如此,說(shuō)LinkedList適合隨機(jī)刪減有個(gè)前提,鏈表的大小不能太大,如果鏈表元素非常多,調(diào)用remove(Object)去刪除一個(gè)元素的效率肯定有影響,一個(gè)簡(jiǎn)單測(cè)試,插入100萬(wàn)數(shù)據(jù),隨機(jī)刪除1000個(gè)元素:
            final List<Integer> list = new LinkedList<Integer>();
            
    final int count = 1000000;
            
    for (int i = 0; i < count; i++) {
                list.add(i);
            }
            
    final Random rand=new Random();
            
    long start=System.nanoTime();
            
    for(int i=0;i<1000;i++){
                
    //這里要強(qiáng)制轉(zhuǎn)型為Integer,否則調(diào)用的是remove(int)
                list.remove((Integer)rand.nextInt(count));
            }
            System.out.println((System.nanoTime()
    -start)/Math.pow(109));
       
        在我的機(jī)器上耗時(shí)近9.5秒,刪除1000個(gè)元素耗時(shí)9.5秒,是不是很恐怖?注意到上面的注釋,產(chǎn)生的隨機(jī)數(shù)強(qiáng)制轉(zhuǎn)為Integer對(duì)象,否則調(diào)用的是remove(int)方法,而非remove(Object)。如果我們調(diào)用remove(int)根據(jù)索引來(lái)刪除:
            for(int i=0;i<1000;i++){
                list.remove(rand.nextInt(list.size()
    -1));
            }
        隨機(jī)數(shù)范圍要遞減,防止數(shù)組越界,換成remove(int)效率提高不少,但是仍然需要2.2秒左右(包括了隨機(jī)數(shù)產(chǎn)生開(kāi)銷)。這是因?yàn)閞emove(int)的實(shí)現(xiàn)很有技巧,它首先判斷索引位置在鏈表的前半部分還是后半部分,如果是前半部分則從head往前查找,如果在后半部分,則從head往后查找(LinkedList的實(shí)現(xiàn)是一個(gè)環(huán)):
            Entry<E> e = header;
            
    if (index < (size >> 1)) {
                
    //前一半,往前找
                for (int i = 0; i <= index; i++)
                    e 
    = e.next;
            } 
    else {
                
    //后一半,往后找
                for (int i = size; i > index; i--)
                    e 
    = e.previous;
            }

         最壞情況下要?jiǎng)h除的節(jié)點(diǎn)在中點(diǎn)左右,查找的次數(shù)仍然達(dá)到n/2次,但是注意到這里沒(méi)有比較的開(kāi)銷,并且比remove(Object)最壞情況下n次查找還是好很多。

        總結(jié)下,LinkedList的兩個(gè)remove方法,remove(Object)和remove(int)的時(shí)間復(fù)雜度都是O(n),在鏈表元素很多并且沒(méi)有索引可用的情況下,LinkedList也并不適合做隨機(jī)增刪元素。在對(duì)性能特別敏感的場(chǎng)景下,還是需要自己實(shí)現(xiàn)專用的雙向鏈表結(jié)構(gòu),真正實(shí)現(xiàn)O(1)級(jí)別的隨機(jī)增刪。更進(jìn)一步,jdk5引入的ConcurrentLinkedQueue是一個(gè)非阻塞的線程安全的雙向隊(duì)列實(shí)現(xiàn),同樣有本文提到的問(wèn)題,有興趣可以測(cè)試一下在大量元素情況下的并發(fā)隨機(jī)增刪,效率跟自己實(shí)現(xiàn)的特定類型的線程安全的鏈表差距是驚人的。

        題外,ArrayList比LinkedList更不適合隨機(jī)增刪的原因是多了一個(gè)數(shù)組移動(dòng)的動(dòng)作,假設(shè)你刪除的元素在m,那么除了要查找m次之外,還需要往前移動(dòng)n-m-1個(gè)元素。


       

    posted @ 2010-09-16 13:51 dennis 閱讀(7754) | 評(píng)論 (9)編輯 收藏

         摘要:     update:更正選擇中數(shù)的描述,在7到39之間的數(shù)組大小選擇median-of-three來(lái)選擇pivot,大小等于7的數(shù)組則直接使用中數(shù)作為pivot。     quicksort可以說(shuō)是應(yīng)用最廣泛的排序算法之一,它的基本思想是分治法,選擇一個(gè)pivot(中軸點(diǎn)),將小于pivot放在左邊,將大于pivot放在右邊,針對(duì)...  閱讀全文

    posted @ 2010-09-08 19:10 dennis 閱讀(19933) | 評(píng)論 (9)編輯 收藏


        Aviator是一個(gè)表達(dá)式執(zhí)行引擎,最近由于工作上的原因,又將這個(gè)東西擴(kuò)充了一下,加入了靜態(tài)的編譯優(yōu)化和seq庫(kù)。

        對(duì)于類似"1+2"這樣由常量組成的表達(dá)式,會(huì)在編譯的時(shí)候直接計(jì)算出結(jié)果而非生成字節(jié)碼運(yùn)行時(shí)計(jì)算。非常量組成的表達(dá)式如"3.14*R*R+4/2"也會(huì)在編譯的時(shí)候優(yōu)化成"3.14*R*R+2",也就是說(shuō)能在編譯的時(shí)候計(jì)算的都計(jì)算出來(lái),不能在編譯的時(shí)候確定的就生成字節(jié)碼,運(yùn)行時(shí)動(dòng)態(tài)計(jì)算。默認(rèn)不啟用編譯優(yōu)化,除非設(shè)置:
    AviatorEvaluator.setOptimize(AviatorEvaluator.EVAL);

        另外,加入了seq庫(kù)用于操作集合和數(shù)組,在aviator中,你可以用[ ]操作符直接訪問(wèn)數(shù)組和java.util.List,除此之外seq庫(kù)添加了一些對(duì)數(shù)組和集合的常用操作,示例如下:
    map(seq,println)            //打印集合
    map(seq,-)                  //取集合中元素的相反數(shù)組成的集合
    include(seq,element)       //判斷element是否在集合中
    sort(seq)                  //排序,返回新的集合
    reduce(seq,+,0)            //求和
    reduce(seq,-,1)           //求積
    filter(seq,seq.gt(3)      //大于3的元素組成的新集合
    filter(seq,seq.exists())  //不為nil元素組成的新集合
    count(seq)            //集合大小

    可以看到seq庫(kù)的風(fēng)格偏向FP,但是能做的事情其實(shí)有限,畢竟aviator不是一門語(yǔ)言,seq庫(kù)只提供了最常見(jiàn)的一些函數(shù),其他的只有用戶自己擴(kuò)展了。

        Aviator的一個(gè)介紹PPT


        Aviator 1.0.1也已經(jīng)放到maven的中心倉(cāng)庫(kù),你可以直接引用:
            <dependency>
                
    <groupId>com.googlecode.aviator</groupId>
                
    <artifactId>aviator</artifactId>
               
    <version>1.0.1</version>
            
    </dependency>
      
        

    posted @ 2010-09-07 14:22 dennis 閱讀(4911) | 評(píng)論 (1)編輯 收藏

        詳解clojure遞歸(上)
        詳解clojure遞歸(下)
       
        這篇blog拖到現(xiàn)在才寫,如果再不寫,估計(jì)就只有上篇沒(méi)有下篇了,趁周末寫一下。

        上篇提到了Clojure僅支持有限的TCO,不支持間接的TCO,但是有一類特殊的尾遞歸clojure是支持,這就是相互遞歸。且看一個(gè)例子,定義兩個(gè)函數(shù)用于判斷奇數(shù)偶數(shù):
    (declare my-odd? my-even?)
    (defn my
    -odd? [n]
          (
    if (= n 0)
              false
             (my
    -even? (dec n))))
    (defn my
    -even? [n]
          (
    if (= n 0)
              true
             (my
    -odd? (dec n))))

        這里使用declare做前向聲明,不然在定義my-odd?的時(shí)候my-even?還沒(méi)有定義,導(dǎo)致出錯(cuò)??梢钥吹剑琺y-odd?調(diào)用了my-even?,而my-even?調(diào)用了my-odd?,這是一個(gè)相互遞歸的定義:如果n為0,那肯定不是奇數(shù),否則就判斷n-1是不是偶數(shù),反之亦然。

        這個(gè)遞歸定義看起來(lái)巧妙,但是剛才已經(jīng)提到clojure通過(guò)recur支持直接的TCO優(yōu)化,這個(gè)相互遞歸在大數(shù)計(jì)算上會(huì)導(dǎo)致堆棧溢出:
    user=> (my-odd? 10000)
    java.lang.StackOverflowError (NO_SOURCE_FILE:
    0)

    user=> (my-even? 10000)
    java.lang.StackOverflowError (NO_SOURCE_FILE:0)


        怎么解決呢?一個(gè)辦法是轉(zhuǎn)化成一個(gè)直接遞歸調(diào)用,定義一個(gè)parity函數(shù),當(dāng)偶數(shù)的時(shí)候返回0,當(dāng)奇數(shù)的時(shí)候返回1:
    (defn parity [n]
          (loop [n n par 
    0]
                (
    if (= n 0)
                    par
                    (recur (dec n) (
    - 1 par)))))
    user=> (parity 3)
    1
    user=> (parity 100)
    0
    user=> (parity 10000)
    0
    user=> (parity 10001)
    1
       

        parity是直接的尾遞歸,并且使用了recur優(yōu)化,重新定義my-odd和my-even就很簡(jiǎn)單了:
    (defn my-even? [n] (= 0 (parity n)))
    (defn my
    -odd?  [n] (= 1 (parity n)))
       
        但是這樣的方式終究不是特別優(yōu)雅,相互遞歸的定義簡(jiǎn)潔優(yōu)雅,有沒(méi)有什么辦法保持這個(gè)優(yōu)雅的定義并且避免堆棧溢出呢?答案是trampoline,翻譯過(guò)來(lái)是彈簧床,查看trampoline函數(shù)文檔:
    user=> (doc trampoline)
    -------------------------
    clojure.core
    /trampoline
    ([f] [f 
    & args])
      trampoline can be used to convert algorithms requiring mutual
      recursion without stack consumption. Calls f with supplied args, 
    if
      any. If f returns a fn, calls that fn with no arguments, and
      continues to repeat, until the 
    return value is not a fn, then
      returns that non
    -fn value. Note that if you want to return a fn as a
      
    final value, you must wrap it in some data structure and unpack it
      after trampoline returns.
       簡(jiǎn)單來(lái)說(shuō)trampoline接收一個(gè)函數(shù)做參數(shù)并調(diào)用,如果結(jié)果為函數(shù),那么就遞歸調(diào)用函數(shù),如果不是,則返回結(jié)果,主要就是為了解決相互遞歸調(diào)用堆棧溢出的問(wèn)題,查看trampoline的定義:
    (defn trampoline
      ([f]
         (let [ret (f)]
           (
    if (fn? ret)
              (recur ret)
              ret)))

       看到trampoline利用recur做了尾遞歸優(yōu)化,原有的my-odd?和my-even?需要做一個(gè)小改動(dòng),就可以利用trampoline來(lái)做遞歸優(yōu)化了:
    (declare my-odd? my-even?)
    (defn my
    -odd? [n]
          (
    if (= n 0)
              
    false
             #(my
    -even? (dec n))))
    (defn my
    -even? [n]
          (
    if (= n 0)
              
    true
             #(my
    -odd? (dec n))))
     
       唯一的改動(dòng)就是函數(shù)的末尾行前面加了個(gè)#,將遞歸調(diào)用修改成返回一個(gè)匿名函數(shù)了,使用trampoline調(diào)用my-even?和my-odd?不會(huì)再堆棧溢出了:
    user=> (trampoline my-odd? 10000000)
    false
    user
    => (trampoline my-even? 10000)  
    true
    user
    => (trampoline my-even? (* 1000 1000))
    true
      
       彈簧床這個(gè)名稱很形象,一次調(diào)用就是落到彈簧床上,如果是函數(shù),反彈回去再來(lái)一次,如果不是,本次表演結(jié)束。
       
     

    posted @ 2010-08-22 13:52 dennis 閱讀(3412) | 評(píng)論 (0)編輯 收藏

        下班后記一些有趣的東西。

        這個(gè)話題是阿寶同學(xué)問(wèn)我為什么clojure的PersistentVector的節(jié)點(diǎn)Node為什么要有個(gè)原子引用指向一個(gè)線程:
    static class Node implements Serializable {
        
    //記錄的線程
        transient final AtomicReference<Thread> edit;
        
    final Object[] array;

        Node(AtomicReference
    <Thread> edit, Object[] array){
            
    this.edit = edit;
            
    this.array = array;
        }

        Node(AtomicReference
    <Thread> edit){
            
    this.edit = edit;
            
    this.array = new Object[32];
        }
    }

        我還真不懂,沒(méi)有細(xì)看過(guò)這部分代碼,早上花點(diǎn)時(shí)間學(xué)習(xí)了下。
        PersistentVector的實(shí)現(xiàn)是另一個(gè)話題,這里不提。我們都知道clojure的數(shù)據(jù)結(jié)構(gòu)是immutable的,修改任意一個(gè)數(shù)據(jù)結(jié)構(gòu)都將生成一個(gè)新的數(shù)據(jù)結(jié)構(gòu),原來(lái)的不變。為了減少?gòu)?fù)制的開(kāi)銷,clojure的數(shù)據(jù)結(jié)構(gòu)同時(shí)是persistent,所謂持久數(shù)據(jù)結(jié)構(gòu)是將數(shù)據(jù)組織為樹(shù)形的層次結(jié)構(gòu),修改的時(shí)候只是root改變,指向不同的節(jié)點(diǎn),原有的節(jié)點(diǎn)仍然復(fù)用,從而避免了大量的數(shù)據(jù)復(fù)制,具體可以搜索下ideal hash trees這篇paper, paper難懂,可以看看這篇blog

        但是在創(chuàng)建PersistentVector的時(shí)候,從一堆現(xiàn)有的元素或者集合創(chuàng)建一個(gè)PersistentVector,如果每次都重新生成一個(gè)PersistentVector,未免太浪費(fèi),創(chuàng)建過(guò)程的性能會(huì)受到影響。我們完全可以假設(shè)創(chuàng)建PersistentVector這個(gè)過(guò)程肯定是線程安全的,沒(méi)有必要每添加一個(gè)元素就重新生成一個(gè)PersistentVector,完全可以在同一個(gè)PersistentVector上修改。這就是TransientVector的意義所在。
        TransientVector就是一個(gè)可修改的Vector,調(diào)用它添加一個(gè)元素,刪除一個(gè)元素,都是在同一個(gè)對(duì)象上進(jìn)行,而不是生成新的對(duì)象。查看PersistentVector的創(chuàng)建:

    static public PersistentVector create(ISeq items){
        TransientVector ret 
    = EMPTY.asTransient();
        
    for(; items != null; items = items.next())
            ret 
    = ret.conj(items.first());
        
    return ret.persistent();
    }

    static public PersistentVector create(List items){
        TransientVector ret 
    = EMPTY.asTransient();
        
    for(Object item : items)
            ret 
    = ret.conj(item);
        
    return ret.persistent();
    }

    static public PersistentVector create(Object items){
        TransientVector ret 
    = EMPTY.asTransient();
        
    for(Object item : items)
            ret 
    = ret.conj(item);
        
    return ret.persistent();
    }


        看到三個(gè)方法的第一步都是將EMPTY集合transient化,生成一個(gè)可修改的TransientVector:
    TransientVector(PersistentVector v){
            
    this(v.cnt, v.shift, editableRoot(v.root), editableTail(v.tail));
        }

    static Node editableRoot(Node node){
            
    return new Node(new AtomicReference<Thread>(Thread.currentThread()), node.array.clone());
        }

        生成的時(shí)候記錄了當(dāng)前的線程在root節(jié)點(diǎn)。然后添加元素的時(shí)候直接調(diào)用TransientVector的conj方法,查看conj可以看到每次返回的都是this:
    public TransientVector conj(Object val){
                    
    //確保修改過(guò)程合法
            ensureEditable();
             
    //忽略邏輯
            return this;
        }

        查看ensureEditable方法:
    void ensureEditable(){
            Thread owner 
    = root.edit.get();
            
    if(owner == Thread.currentThread())
                
    return;
            
    if(owner != null)
                
    throw new IllegalAccessError("Transient used by non-owner thread");
            
    throw new IllegalAccessError("Transient used after persistent! call");
        }


        終于看到Node中的edit引用的線程被使用了,判斷當(dāng)前修改的線程是否是使得集合transient化的線程,如果不是則拋出異常,這是為了保證對(duì)TransientVector的編輯是在同一個(gè)線程里,防止因?yàn)橐馔獍l(fā)布TransientVector引用引起的線程安全隱患。

        知道了transient集合的用途,我們能在clojure中使用嗎?完全沒(méi)問(wèn)題,clojure.core有個(gè)transient方法,可以將一個(gè)集合transient化:
    (defn transient 
      [
    ^clojure.lang.IEditableCollection coll] 
      (.asTransient coll))
      
        前提是這個(gè)集合是可編輯的,clojure的map、vector和set都是可編輯的。讓我們確認(rèn)下transient修改后的集合還是不是自身:
    user=> (def v1 [1 2 3])
    #
    'user/v1
    user=> (def v2 (transient v1))
    #
    'user/v2
    user=> v2
    #
    <TransientVector clojure.lang.PersistentVector$TransientVector@7eb366>

        定義了集合v1,v2是調(diào)用了transient之后的集合,查看v2,果然是一個(gè)TransientVector。查看v2的元素個(gè)數(shù)是不是3個(gè):
    user=> (.count v2)
    3

        沒(méi)問(wèn)題,注意,我們不能直接調(diào)用count函數(shù),因?yàn)関2是個(gè)普通的java對(duì)象,我們必須使用dot操作符來(lái)調(diào)用java對(duì)象的方法。添加一個(gè)元素看看:
    user=> (def v3 (.conj v2 4))
    #
    'user/v3
    user=> v3
    #
    <TransientVector clojure.lang.PersistentVector$TransientVector@7eb366>

        添加一個(gè)元素后形成集合v3,查看v3,跟v2是同一個(gè)對(duì)象#<TransientVector clojure.lang.PersistentVector$TransientVector@7eb366>
    。證明了transient集合修改的是自身,而不是生成一個(gè)新集合。確認(rèn)下4有加入v2和v3:
    user=> (.nth v3 3)
    4
    user
    => (.count v2)
    4
    user
    => (.count v3)
    4
    user
    => (.nth v2 3)
    4

        果然沒(méi)有問(wèn)題。transient集合的使用應(yīng)當(dāng)慎重,除非能確認(rèn)沒(méi)有其他線程會(huì)去修改集合,并且對(duì)線程的可見(jiàn)性要求不高的時(shí)候,也許可以嘗試下這個(gè)技巧。
      

    posted @ 2010-08-18 18:46 dennis 閱讀(2284) | 評(píng)論 (0)編輯 收藏

    僅列出標(biāo)題
    共56頁(yè): First 上一頁(yè) 5 6 7 8 9 10 11 12 13 下一頁(yè) Last 
    主站蜘蛛池模板: 免费va在线观看| 国产乱子伦精品免费无码专区| 国产亚洲一区二区在线观看| 特黄特色大片免费| 免费在线精品视频| 日韩精品无码永久免费网站| 亚洲国产精品无码久久青草| 一日本道a高清免费播放| 中文亚洲成a人片在线观看| 岛国精品一区免费视频在线观看| 中文字幕亚洲一区| 免费播放在线日本感人片| 亚洲色图国产精品| 亚洲三级在线免费观看| 亚洲jjzzjjzz在线播放| 日本高清免费aaaaa大片视频| 女bbbbxxxx另类亚洲| 亚洲国产精品激情在线观看| 狠狠躁狠狠爱免费视频无码| 亚洲AV无码一区二区二三区软件| 一级毛片在线免费观看| 波多野结衣亚洲一级| 免费中文字幕不卡视频| 十八禁在线观看视频播放免费| 91精品国产亚洲爽啪在线影院| 成人人观看的免费毛片| 一级毛片免费观看不收费| 亚洲av日韩av激情亚洲| 亚洲中文无码永久免费| 日日狠狠久久偷偷色综合免费 | 免费无码中文字幕A级毛片| 亚洲一区无码中文字幕乱码| 四虎永久免费网站免费观看| 东方aⅴ免费观看久久av| 亚洲一区二区三区高清不卡| 亚洲精品WWW久久久久久| 97精品免费视频| 国产精品亚洲一区二区三区久久| 亚洲av之男人的天堂网站| 天天看免费高清影视| 免费福利电影在线观看|