莊周夢蝶

          生活、程序、未來
             :: 首頁 ::  ::  :: 聚合  :: 管理

          ???

          ??? 開源的Java Memcached Client——Xmemcached 發布1.2.4版本,這個版本主要的工作是BUG修正,主要改動如下:

          1、修正bug,包括issue 68,issue 74Issue 68修復后,現在可以正常地使用TokyoTyrantTranscoder來連接TokyoTyrant。

          2、為修正的BUG添加新的單元測試。

          3、將CachedData.MAX_VALUE修改為可修改狀態,允許用戶設置更大的值,這個值決定了可以向memcached存儲的最大值,默認是1M(通過memcached的-I size選項),單位是字節:

          CachedData.MAX_SIZE = 2 * 1024 * 1024 ;? // 修改為2M


          4、更正用戶指南的錯誤并補充部分資料。


          下載地址: http://code.google.com/p/xmemcached/downloads/list

          項目主頁:http://code.google.com/p/xmemcached/

          Wiki頁??? :http://code.google.com/p/xmemcached/w/list



          posted @ 2010-03-15 13:42 dennis 閱讀(2657) | 評論 (6)編輯 收藏


              Ruby Fiber指南(一)基礎
              Ruby Fiber指南(二)參數傳遞
              Ruby Fiber指南(三)過濾器
              Ruby Fiber指南(四)迭代器
              Ruby Actor指南(五)實現Actor

               上一節介紹了利用Fiber實現類unix管道風格的過濾鏈,這一節將介紹利用Fiber來實現迭代器,我們可以將循環的迭代器看作生產者-消費者模式的特殊的例子。迭代函數產生值給循環體消費。所以可以使用Fiber來實現迭代器。協程的一個關鍵特征是它可以不斷顛倒調用者與被調用者之間的關系,這樣我們毫無顧慮的使用它實現一個迭代器,而不用保存迭代函數返回的狀態,也就是說無需在迭代函數中保存狀態,狀態的保存和恢復交由Fiber自動管理。

               這一節的介紹以一個例子貫穿前后,我們將不斷演化這個例子,直到得到一個比較優雅的可重用的代碼,這個例子就是求數組的全排列。如數組[1,2,3]的全排列包括6種排列:
          2 3 1
          3 2 1
          3 1 2
          1 3 2
          2 1 3
          1 2 3
          全排列的遞歸算法實現很簡單,我們用Ruby實現如下:

          #全排列的遞歸實現
          def permgen (a, n)
              
          if n == 0 then
                 printResult(a)
              
          else
                  n.times do 
          |i|
                     a[n
          -1], a[i] = a[i], a[n-1]
                     permgen(a, n 
          - 1)
                     a[n
          -1], a[i] = a[i], a[n-1]
                 end
              end
          end

          def printResult (a)
              puts a.join(
          " ")
          end

          permgen([
          1,2,3,4],4)
          算法的思路是這樣:
          將數組中的每一個元素放到最后,依次遞歸生成所有剩余元素的排列,沒完成一個排列就打印出來。很顯然,這里有消費者和生產者的關系存在,生產者負責產生排列,消費者負責打印任務,整個程序由消費者驅動,因此用Fiber改寫如下:

          第一步,將打印任務修改為Fiber#yield,生產者產生一個排列后將結果傳遞給消費者并讓出執行權:
          def permgen (a, n)
              
          if n == 0 then
                 Fiber.
          yield(a)
              ……
          end

          第二步,實現一個迭代器工廠,返回一個匿名的迭代函數,迭代函數請求Fiber產生一個新的排列:
          def perm(a)
            f
          =Fiber.new do
                  permgen(a,a.size)
              end
            
          return lambda{ f.resume if f.alive? }
          end

          這樣一來我們就可以利用一個while循環來打印全排列:
          it=perm([1,2,3,4])

          while a=it.call
             printResult(a)
          end

              注意到,在perm方法中有一個很常見的模式,就是將對Fiber的resume封裝在一個匿名函數內,在lua為了支持這種模式還特意提供了一個coroutine.wrap方法來方便編程,在Ruby Fiber中卻沒有,不過我們可以自己實現下,利用open-class的特性實現起來非常簡單:
          #為Fiber添加wrap方法
          class Fiber
            
          def self.wrap
              
          if block_given?
                f
          =Fiber.new do |*args|
                   
          yield *args
                end
                
          return lambda{|*args| f.resume(*args) if f.alive? }
              end
            end
          end
           
              Fiber#wrap方法跟new方法一樣,創建一個新的Fiber,但是返回的是一個匿名函數,這個匿名函數負責去調用fiber的resume,利用wrap改寫perm方法變得更簡潔:
          def perm(a)
            Fiber.wrap{ permgen(a,a.size) }
          end

              但是還不夠,while循環的方式還是不夠優雅,每次都需要明確地調用迭代器的call方法,這一點讓人挺不舒坦,如果能像for...in那樣的泛型循環就好了,我們知道Ruby中的for...in其實是一個語法糖衣,都是轉變成調用集合的each方法并傳入處理的block,因此,要想實現一個優雅的迭代器,我們做下封裝就好了:

          class FiberIterator
            
          def initialize
              @fiber_wrap
          =Fiber.wrap do
                  
          yield
              end
            end
            
          def each
              
          while value=@fiber_wrap.call
                
          yield value
              end
            end
          end

          那么現在的perm方法變成了創建一個迭代器FiberIterator:
          def perm(a)
            FiberIterator.new{ permgen(a,a.size) }
          end
          這樣一來我們就可以通過for...in來調用迭代器了

          it=perm([1,2,3,4])
          for a in it
            printResult(a)
          end


             

              

          posted @ 2010-03-12 12:48 dennis 閱讀(3286) | 評論 (0)編輯 收藏


              Ruby Fiber指南(一)基礎
              Ruby Fiber指南(二)參數傳遞
              Ruby Fiber指南(三)過濾器
              Ruby Fiber指南(四)迭代器
              Ruby Actor指南(五)實現Actor

               在學習了Fiber的基礎知識之后,可以嘗試用Fiber去做一些比較有趣的事情。這一節將講述如何使用Fiber來實現類似unix系統中的管道功能。在unix系統中,可以通過管道將多個命令組合起來做一些強大的功能,最常用的例如查找所有的java進程:
          ps aux|grep java
          通過組合ps和grep命令來實現,ps的輸出作為grep的輸入,如果有更多的命令就形成了一條過濾鏈。過濾器本質上還是生產者和消費者模型,前一個過濾器產生結果,后一個過濾器消費這個結果并產生新的結果給下一個過濾器消費。因此我們就從最簡單的生產者消費者模型實現說起。
          我們要展示的這個例子場景是這樣:生產者從標準輸入讀入用戶輸入并發送給消費者,消費者打印這個輸入,整個程序是由消費者驅動的,消費者喚醒生存者去讀用戶輸入,生產者讀到輸入后讓出執行權給消費者去打印,整個過程通過生產者和消費者的協作完成。
          生產者發送是通過yield返回用戶輸入給消費者(還記的上一節嗎?):

          def send(x)
            Fiber.
          yield(x)
          end

          而消費者的接收則是通過喚醒生產者去生產:
          def receive(prod)
            prod.resume
          end

          生產者是一個Fiber,它的任務就是等待用戶輸入并發送結果給消費者:
          def producer()
            Fiber.new do
              
          while true
                x
          =readline.chomp
                send(x)
              end
            end
          end

          消費者負責驅動生產者,并且在接收到結果的時候打印,消費者是root fiber:
          def consumer(producer)
            
          while true
              x
          =receive(producer)
              
          break if x=='quit'
              puts x
            end
          end

              最終的調用如下:
          consumer(producer())
             完整的程序如下:

          #生產者消費者
          require 'fiber'

          def send(x)
            Fiber.
          yield(x)
          end

          def receive(prod)
            prod.resume
          end

          def producer()
            Fiber.new do
              
          while true
                x
          =readline.chomp
                send(x)
              end
            end
          end


          def consumer(producer)
            
          while true
              x
          =receive(producer)
              
          break if x=='quit'
              puts x
            end
          end
          if $0==__FILE__
            consumer(producer())
          end

             讀者可以嘗試在ruby1.9下運行這個程序,每次程序都由消費者驅動生產者去等待用戶輸入,用戶輸入任何東西之后回車,生產者開始運行并將讀到的結果發送給消費者并讓出執行權(通過yield),消費者在接收到yield返回的結果后打印這個結果,因此整個交互過程是一個echo的例子。

          最終的調用consumer(producer())已經有過濾器的影子在了,如果我們希望在producer和consumer之間插入其他過程對用戶的輸入做處理,也就是安插過濾器,那么新的過濾器也將作為fiber存在,新的fiber消費producer的輸出,并輸出新的結果給消費者,例如我們希望將用戶的輸入結果加上行號再打印,那么就插入一個稱為filter的fiber:
          def filter(prod)
            
          return Fiber.new do
              line
          =1
              
          while true
                value
          =receive(prod)
                value
          =sprintf("%5d %s",line,value)
                send(value)
                line
          =line.succ
              end
            end
          end

              最終組合的調用如下:
           consumer(filter(producer()))
             類似unix系統那樣,簡單的加入新的fiber組合起來就可以為打印結果添加行號。

          類似consumer(filter(producer()))的調用方式盡管已經很直觀,但是我們還是希望能像unix系統那樣調用,也就是通過豎線作為管道操作符:
          producer | filter | consumer
          這樣的調用方式更將透明直觀,清楚地表明整個過濾器鏈的運行過程。幸運的是在Ruby中支持對|方法符的重載,因此要實現這樣的操作符并非難事,只要對Fiber做一層封裝即可,下面給出的代碼來自《Programming ruby》的作者Dave Thomas的blog

          class PipelineElement
             attr_accessor :source
             
          def initialize
                @fiber_delegate 
          = Fiber.new do
                   process
                end
             end

             
          def |(other)
                other.source 
          = self
                other
             end

             
          def resume
                @fiber_delegate.resume
             end

             
          def process
                
          while value = input
                   handle_value(value)
                end
             end

             
          def handle_value(value)
                output(value)
             end

             
          def input
                @source.resume
             end

             
          def output(value)
                Fiber.
          yield(value)
             end
          end

          這段代碼非常巧妙,將Fiber和Ruby的功能展示的淋漓盡致。大致解說下,PipelineElement作為任何一個過濾器的父類,其中封裝了一個fiber,這個fiber默認執行process,在process方法中可以看到上面生產者和消費者例子的影子,input類似receive方法調用前置過濾器(source),output則將本過濾器處理的結果作為參數傳遞給yield并讓出執行權,讓這個過濾器的調用者(也就是后續過濾器)得到結果并繼續處理。PipelineElement實現了“|”方法,用于組合過濾器,將下一個過濾器的前置過濾器設置為本過濾器,并返回下一個過濾器。整個過濾鏈的驅動者是最后一個過濾器。

          有了這個封裝,那么上面生產者消費者的例子可以改寫為:
          class Producer < PipelineElement
             
          def process
               
          while true
                 value
          =readline.chomp
                 handle_value(value)
               end
             end
          end

          class Filter < PipelineElement
            
          def initialize
              @line
          =1
              super()
            end
            
          def handle_value(value)
              value
          =sprintf("%5d %s",@line,value)
              output(value)
              @line
          =@line.succ
            end
          end

          class Consumer < PipelineElement
            
          def handle_value(value)
              puts value
            end
          end

             現在的調用方式可以跟unix的管道很像了:
          producer=Producer.new
          filter
          =Filter.new
          consumer
          =Consumer.new

          pipeline 
          = producer | filter | consumer
          pipeline.resume
          如果你打印pipeline對象,你將看到一條清晰的過濾鏈,
          #<Consumer:0x8f08bf4 @fiber_delegate=#<Fiber:0x8f08a88>, @source=#<Filter:0x8f08db4 @line=1, @fiber_delegate=#<Fiber:0x8f08d60>, @source=#<Producer:0x8f09054 @fiber_delegate=#<Fiber:0x8f09038>>>>


          posted @ 2010-03-11 23:49 dennis 閱讀(4540) | 評論 (1)編輯 收藏


              Ruby Fiber指南(一)基礎
              Ruby Fiber指南(二)參數傳遞
              Ruby Fiber指南(三)過濾器
              Ruby Fiber指南(四)迭代器
              Ruby Actor指南(五)實現Actor

              這一篇其實也算是Fiber編程的基礎篇,只不過參數傳遞算是一個比較重要的主題,因此獨立一節。參數傳遞發生在兩個Fiber之間,作為Fiber之間通訊的一個主要手段。

              首先,我們可以通過resume調用給Fiber的block傳遞參數:
          1 #resume傳遞參數給fiber
          2 f=Fiber.new do |a,b,c|
          3    p a,b,c
          4 end
          5 
          6 f.resume(1,2,3)
          7 

          這個例子展示了怎么向fiber的block傳遞參數,f這個fiber簡單地將傳入的參數打印出來并終止。

              其次,Fiber#yield也可以傳遞參數給調用resume作為返回結果,猜猜下面的代碼打印什么?

          1 #yield傳遞參數給resume
          2 f=Fiber.new do |a,b|
          3   Fiber.yield a+b,a-b
              p a,b
          4 end
          5 
          6 p f.resume(10,10)
          7 p f.resume(3,4)
          8 
          正確的答案是:
          [20, 0]
          10
          10
          [
          1010]
          讓我們分析下代碼的執行過程:
          1、第6行第一次調用resume,傳入10,10兩個參數
          2、f開始執行任務,它的任務是調用Fiber#yield,并將參數相加和相減的結果作為參數給yield,也就是執行Fiber.yield 20,10
          3、f調用yield之后掛起,返回root fiber,yield的兩個參數10、20作為返回結果打印。
          4、第7行代碼,root fiber再次調用resume并傳入參數,f被切入并執行代碼p a,b,打印a、b,a和b仍然是上次調用保存的10,而非resume傳入的3和4。
          5、f執行完畢,返回p a,b的結果作為resume結果,也就是[10,10]

              剛才看到上面yield向resume傳遞參數的例子中第二次調用resume的參數3和4被忽略了,事實上如果還存在一次yield調用,那么3和4將被作為yield的返回結果使用,這就是我們接下來將看到的,通過resume調用傳遞參數作為fiber中yield的返回結果:

          1 #resume傳遞參數給yield
          2 f=Fiber.new do
          3    1 + Fiber.yield
          4 end
          5 
          6 p f.resume(1)
          7 p f.resume(2)
          8 

          這次的打印結果將是:

          nil
          3
             第一次調用resume傳入的1將被忽略,因為f的block不需要參數,然后f執行1 + Fiber.yield,在yield的掛起,加法運算沒有繼續,因為yield的調用沒有參數,因此第一次resume返回nil;第二次resume調用傳入2,這時候2將作為Fiber#yield的調用結果跟1相加,完成加法運算,得到的結果就是3,這個結果作為fiber的返回值返回給調用者。

              總結下上面我們談到的四種傳遞參數的情形:通過resume向fiber的block傳遞參數、通過yield向調用者傳遞參數、通過resume向yield傳遞參數、fiber返回值傳遞給調用者。
             

          posted @ 2010-03-11 18:41 dennis 閱讀(3733) | 評論 (0)編輯 收藏


              Ruby Fiber指南(一)基礎
              Ruby Fiber指南(二)參數傳遞
              Ruby Fiber指南(三)過濾器
              Ruby Fiber指南(四)迭代器
              Ruby Actor指南(五)實現Actor
             
              這是一個Ruby Fiber的教程,基本是按照《Programming in lua》中講述協程章節的順序來介紹Ruby Fiber的,初步分為5節:基礎、參數傳遞、過濾器、迭代器、應用。這是第一節,介紹下Ruby Fiber的基礎知識。

              Ruby 1.9引入了Fiber,通常稱為纖程,事實上跟傳統的coroutine——協程是一個概念,一種非搶占式的多線程模型。所謂非搶占式就是當一個協程運行的時候,你不能在外部終止它,而只能等待這個協程主動(一般是yield)讓出執行權給其他協程,通過協作來達到多任務并發的目的。協程的優點在于由于全部都是用戶空間內的操作,因此它是非常輕量級的,占用的資源很小,并且context的切換效率也非常高效(可以看看這個測試),在編程模型上能簡化對阻塞操作或者異步調用的使用,使得涉及到此類操作的代碼變的非常直觀和優雅;缺點在于容錯和健壯性上需要做更多工作,如果某個協程阻塞了,可能導致整個系統掛住,無法充分利用多核優勢,有一定的學習使用曲線。
             上面都是場面話,先看看代碼怎么寫吧,比如我們寫一個打印hello的協程:
           1 require 'fiber'
           2 f=Fiber.new do
           3   p "hello"
           4 end
           5 
           6 p f.alive?
           7 f.resume
           8 p f.alive?
           9 
          10 f.resume
          11
              附注:這里的代碼都在ruby1.9.1-p378測試通過。

               第一行先引入fiber庫,事實上fiber庫并不是必須的,這里是為了調用Fiber#alive?方法才引入。然后通過Fiber#new創建一個Fiber,Fiber#new接受一個block,block里就是這個Fiber將要執行的任務。Fiber#alive?用來判斷Fiber是否存活,一個Fiber有三種狀態:Created、Running、Terminated,分別表示創建完成、執行、終止,處于Created或者Running狀態的時候Fiber#alive?都返回true。啟動Fiber是通過Fiber#resume方法,這個Fiber將進入Running狀態,打印"hello"并終止。當一個Fiber終止后,如果你再次調用resume將拋出異常,告訴你這個Fiber已經壽終正寢了。因此上面的程序輸出是:
          0
          "hello"
          false
          fiber1.rb:
          10:in `resume': dead fiber called (FiberError)
              from fiber1.rb:10:in `<main>'

               眼尖的已經注意到了,這里alive?返回是0,而不是true,這是1.9.1這個版本的一個BUG,1.9.2返回的就是true。不過在Ruby里,除了nil和false,其他都是true。

              剛才提到,我們為了調用Fiber#alive?而引入了fiber庫,Fiber其實是內置于語言的,并不需要引入額外的庫,fiber庫對Fiber的功能做了增強,具體可以先看看它的文檔,主要是引入了幾個方法:Fiber#current返回當前協程,Fiber#alive?判斷Fiber是否存活,最重要的是Fiber#transfer方法,這個方法使得Ruby的Fiber支持所謂全對稱協程(symmetric coroutines),默認的resume/yield(yield后面會看到)是半對稱的協程(asymmetric coroutines),這兩種模型的區別在于掛起一個正在執行的協同函數”與“使一個被掛起的協同再次執行的函數”是不是同一個。在這里就是Fiber#transfer一個方法做了resume/yield兩個方法所做的事情。全對稱協程就可以從一個協程切換到任意其他協程,而半對稱則要通過調用者來中轉。但是Ruby Fiber的調用不能跨線程(thread,注意跟fiber區分),只能在同一個thread內進行切換,看下面代碼:
          1 = nil
          2 Thread.new do
          3   f = Fiber.new{}
          4 end.join
          5 f.resume

          f在線程內創建,在線程外調用,這樣的調用在Ruby 1.9里是不允許的,執行的結果將拋出異常
          fiber_thread.rb:5:in `resume': fiber called across threads (FiberError)
              from fiber_thread.rb:5:in `<main>'

              剛才我們僅僅使用了resume,那么yield是干什么的呢?resume是使一個掛起的協程執行,那么yield就是讓一個正在執行的Fiber掛起并將執行權交給它的調用者,yield只能在某個Fiber任務內調用,不能在root Fiber調用,程序的主進程就是一個root fiber,如果你在root fiber執行一個Fiber.yield,也將拋出異常:
           Fiber.yield
          FiberError: can
          't yield from root fiber
            
              看一個resume結合yield的例子:
           1 f=Fiber.new do
           2   p 1
           3   Fiber.yield
           4   p 2
           5   Fiber.yield
           6   p 3
           7 end
           8 
           9 f.resume # =>打印1
          10 f.resume # => 打印2
          11 f.resume # =>打印3

             f是一個Fiber,它的任務就是打印1,2,3,第一次調用resume時,f在打印1之后調用了Fiber.yield,f將讓出執行權給它的調用者(這里就是root fiber)并掛起,然后root fiber再次調用f.resume,那么將從上次掛起的地方繼續執行——打印2,又調用Fiber.yield再次掛起,最后一次f.resume執行后續的打印任務并終止f。

              Fiber#yield跟語言中的yield關鍵字是不同的,block中的yield也有“讓出”的意思,但是這是在同一個context里,而Fiber#yield讓出就切換到另一個context去了,這是完全不同的。block的yield其實是匿名函數的語法糖衣,它是切換context的,跟Fiber不同的是,它不保留上一次調用的context,這個可以通過一個例子來區分:
          1 def test
          2    yield
          3    yield
          4    yield
          5 end
          6 test{x ||= 0; puts x+= 1}
          7 
          這里的test方法接受一個block,三次調用yield讓block執行,block里先是初始化x=0,然后每次調用加1,你期望打印什么?
          答案是:
          1
          1
          1
          這個結果剛好證明了yield是不保留上一次調用的context,每次x都是重新初始化為0并加上1,因此打印的都是1。讓我們使用Fiber寫同一個例子:
           1 fiber=Fiber.new do
           2    x||=0
           3    puts x+=1
           4    Fiber.yield
           5    puts x+=1
           6    Fiber.yield
           7    puts x+=1
           8    Fiber.yield
           9 end
          10 
          11 fiber.resume
          12 fiber.resume
          13 fiber.resume
          14 
          執行的結果是:
          1
          2
          3
          這次能符合預期地打印1,2,3,說明Fiber的每次掛起都將當前的context保存起來,留待下次resume的時候恢復執行。因此關鍵字yield是無法實現Fiber的,fiber其實跟continuation相關,在底層fiber跟callcc的實現是一致的(cont.c)。

              Fiber#current返回當前執行的fiber,如果你在root fiber中調用Fiber.current返回的就是當前的root fiber,一個小例子:
          1 require 'fiber'
          2 f=Fiber.new do
          3    p Fiber.current
          4 end
          5 
          6 p Fiber.current
          7 f.resume

          這是一次輸出:
          #<Fiber:0x9bf89f4>
          #
          <Fiber:0x9bf8a2c>
          表明root fiber跟f是兩個不同的Fiber。
              
               基礎的東西基本講完了,最后看看Fiber#transfer的簡單例子,兩個協程協作來打印“hello world”:
           1 require 'fiber'
           2 
           3 f1=Fiber.new do |other|
           4     print "hello"
           5     other.transfer
           6 end
           7 
           8 f2=Fiber.new do
           9     print " world\n"
          10 end
          11 
          12 f1.resume(f2)

          通過這個例子還可以學到一點,resume可以傳遞參數,參數將作為Fiber的block的參數,參數傳遞將是下一節的主題。


             

          posted @ 2010-03-11 12:53 dennis 閱讀(8653) | 評論 (2)編輯 收藏

          最近重讀了《Programming Lua》,對協程做了重點復習。眾所周知,Ruby1.9引入了Fiber,同樣是coroutine,不過Ruby Fiber支持全對稱協程(通過fiber庫),而Lua只支持所謂半對稱協程。

          ??? 這里將對Lua、LuaJIT和Ruby Fiber的切換效率做個對比測試,測試場景很簡單:兩個coroutine相互切換達到5000萬次,統計每秒切換的次數,各測試多次取最佳。

          ??? lua的程序如下:
          ???
          c1=coroutine.create(function?()
          ?????????????????????
          while?true?do
          ???????????????????????coroutine
          .yield()
          ?????????????????????end
          ????????????????????end)

          c2
          =coroutine.create(function?()
          ?????????????????????
          while?true?do
          ???????????????????????coroutine
          .yield()
          ?????????????????????end
          ????????????????????end)

          local?start=os.clock()
          local?count=50000000

          for?i=1,count?do
          ?coroutine
          .resume(c1)
          ?coroutine
          .resume(c2)
          end

          print(4*count/(os.clock()-start))

          ??? 考慮到在循環中事實上發生了四次切換:main->c1,c1->main,main->c2,c2->main,因此乘以4。

          ??? Ruby Fiber的測試分兩種,采用transfer的例程如下:

          require?'fiber'
          require?'benchmark'

          Benchmark
          .bm?do?|x|
          ??MAX_COUNT
          =50000000
          ??f1
          =Fiber.new?do?|other,count|
          ?????
          while?count<MAX_COUNT
          ??????other
          ,count=other.transfer(Fiber.current,count.succ)
          ?????end
          ??end

          ??f2
          =Fiber.new?do?|other,count|
          ????
          while?count<MAX_COUNT
          ??????other
          ,count=other.transfer(Fiber.current,count.succ)
          ????end
          ??end

          ??x
          .report{
          ????f1
          .resume(f2,0)
          ??}
          end
          ???? Ruby Fiber采用resume/yield的例程如下:
          require?'benchmark'
          f1
          =Fiber.new?do
          ??
          while?true
          ????Fiber
          .yield
          ??end
          end
          f2
          =Fiber.new?do
          ??
          while?true
          ????Fiber
          .yield
          ??end
          end

          COUNT
          =50000000

          Benchmark
          .bm?do?|x|
          ??x
          .report{
          ?????COUNT
          .times?do
          ?????????f1
          .resume
          ?????????f2
          .resume
          ?????end
          ??}
          end



          ???? 測試環境:
          ????????? CPU :??? Intel(R) Core(TM)2 Duo CPU???? P8600? @ 2.40GHz
          ????????? Memory:? 3GB
          ????????? System :? Linux dennis-laptop 2.6.31-14-generic #48-Ubuntu SMP
          ????????? Lua??? : 5.1.4
          ????????? ruby? :? 1.9.1p378
          ????????? LuaJIT:? 1.1.5和2.0.0-beta2

          ????? 測試結果如下:
          ????
          ??Lua?LuaJIT 1.1.5
          ?LuaJIT 2.0.0-beta2
          ?ruby-transfer
          ?ruby-resume/yield
          ?次數?6123698?9354536?24875620?825491?969649


          ????? 結論:
          ????? 1、lua的協程切換效率都是百萬級別,luaJIT 2.0的性能更是牛叉,切換效率是原生lua的4倍,達到千萬級別。
          ????? 2、相形之下,Ruby Fiber的效率比較差了,十萬級別。
          ????? 3、Ruby使用transfer的效率比之resume/yield略差那么一點,排除一些測試誤差,兩者應該是差不多的,從ruby源碼上看resume/yield和transfer的調用是一樣的,resume還多了幾條指令。
          ????? 4、額外信息,Ruby Fiber和lua coroutine都只能跑在一個cpu上,這個測試肯定能跑滿一個cpu,內存占用上,lua也比ruby小很多。
          ?

          posted @ 2010-03-02 11:50 dennis 閱讀(7430) | 評論 (2)編輯 收藏

              題外:從老家從早到晚總算折騰回了杭州,進站太早,火車晚點,提包帶斷,什么倒霉事也遇上了,先發個已經整理好的部分,后續仍待整理。

          多道程序設計:分離進程為獨立的功能

          無論在協作進程還是在同一進程的協作子過程層面上,Unix設計風格都運用“做單件事并做好的方法“,強調用定義良好的進程間通信或共享文件來連通小型進程,提倡將程序分解為更簡單的子進程,并專注考慮這些子進程間的接口,這至少需要通過以下三種方法來實現:

          1降低進程生成的開銷(思考下Erlangprocess)

          2、提供方法(shelloutIO重定向、管道、消息傳遞和socket)簡化進程間通信

          3、提倡使用能由管道和socket傳遞的簡單、透明的文本數據格式

          Unix IPC方法的分類:

          1、將任務轉給專門程序,如system(3)popen調用等,稱為shellout

          2Pipe重定向和過濾器,如bcdc

          3包裝器,隱藏shell管線的復雜細節。

          4安全包裝器和Bernstein

          5/進程

          6對等進程間通信:

          (1)臨時文件

          (2)信號

          (3)系統守護程序和常規信號

          (4)socket

          (5)共享內存,mmap

          遠程過程調用(RPC)的缺憾:

          1RPC接口很難做到可顯,如果不編寫和被監控程序同樣復雜的專用工具,也難以監控程序的行為。RPC接口和庫一樣具有版本不兼容的問題,由于是分布式的,因此更難被追查。

          2、類型標記越豐富的接口往往越復雜,因而越脆弱。隨著時間的推移,由于在接口之間傳遞的類型總量逐漸變大,單個類型越來越復雜,這些接口往往產生類型本體蠕變問題。這是因為接口比字符串更容易失配;如果兩端程序的本體不能正確匹配,要讓它們通信肯定很難,糾錯更是難上加難。

          3、支持RPC的常見理由是它比文本流方法允許“更豐富”的接口,但是接口的功能之一就是充當阻隔點,防止模塊的實現細節彼此泄漏,因此,支持RPC的主要理由同時恰恰證明了RPC增加而不是降低了程序的全局復雜度

          Unix傳統強烈贊成透明、可顯的接口,這是unix文化不斷堅持文本協議IPC的動力。

          ESR在這里還談到XML-RPCSOAP等協議,認為是RPCunix對文本流支持的一種融合,遺憾的是SOAP本身也成為一種重量級、不那么透明的協議了,盡管它也是文本協議。

          線程是有害的:

          線程是那些進程生成昂貴、IPC功能薄弱的操作系統所特有的概念。

          盡管線程通常具有獨立的局部變量棧,它們卻共享同一個全局內存,在這個共享地址空間管理競爭臨界區的任務相當困難,而且成為增加整體復雜度和滋生bug的溫床。除了普通的競爭問題之外,還產生了一類新問題:時序依賴

          當工具的作用不是控制而是增加復雜度的時候,最好扔掉從零開始。

          微型語言:尋找歌唱的樂符

          (注,這里談的微型語言,就是現在比較熱門的詞匯DSL

          對軟件錯誤模式進行的大量研究得出的一個最一致的結論是,程序員每百行程序出錯率和所使用的編程語言在很大程度上是無關的。更高級的語言可以用更少的行數完成更多的任務,也意味著更少的bug

          防御設計不良微型語言的唯一方法是知道如何設計一個好的微型語言。

          語言分類法:


          對微型語言的功能測試:不讀手冊可以編寫嗎

          現代微型語言,要么就完全通用而不緊湊,要么就非常不通用而緊湊;不通用也不緊湊的語言則完全沒有競爭力。

          一些引申想法:我認為這個評判標準也可以用在任何編程語言上,以此來判斷一些語言,C語言既通用又緊湊,Java是通用而不緊湊,rubyPython之類的腳本語言也是如此,正則表達式(如果也算語言的話)是不通用而緊湊,Erlang也是通用而緊湊,awk卻是既不通用也不緊湊,XSLT也可以歸入不通用不緊湊的行列;Javascript是個另類,按理說它也是不通用不緊湊,說它不通用是因為它的主要應用范圍還是局限在web開發的UI上,實際上Javascript也是門通用語言,但是很少會有人會用javascript去寫批處理腳本,Javascript顯然是不緊湊的,太多的邊邊角角甚至奇奇怪怪的東西需要你去注意,然而就是這樣一門不通用不緊湊的語言現在卻非常有前途,只能說時勢所然。

          設計微型語言:

          1、選擇正確的復雜度,要的是數據文件格式,還是微型語言?

          2擴展嵌入語言

          3、編寫自定義語法yacclex

          4慎用宏,宏的主要問題是濫用帶來的奇怪、不透明的代碼,以及對錯誤診斷的擾亂。

          5語言還是應用協議



          posted @ 2010-02-22 00:46 dennis 閱讀(2993) | 評論 (1)編輯 收藏

          模塊性:保持清晰,保持簡潔

          軟件設計有兩種方式:一種是設計得極為簡潔沒有看得到的缺陷;另一種是設計得極為復雜有缺陷也看不出來。第一種方式的難度要大得多。

          模塊化代碼的首要特質就是封裝API在模塊間扮演雙重角色,實現層面作為模塊之間的滯塞點阻止各自的內部細節被相鄰模塊知曉;在設計層面,正是API真正定義了整個體系。

          養成在編碼前為API編寫一段非正式書面描述的習慣,是一個非常好的方法。

          模塊的最佳大小,邏輯行200-400行,物理行在400-800之間

          緊湊性就是一個設計能否裝入人腦中的特性。測試軟件緊湊性的一個簡單方法是:一個有經驗的用戶通常需要用戶手冊嗎?如果不需要,那么這個設計是緊湊的。

          理解緊湊性可以從它的“反面”來理解,緊湊性不等于“薄弱”,如果一個設計構建在易于理解且利于組合的抽象概念上,則 這個系統能在具有非常強大、靈活的功能的同時保持緊湊。緊湊也不等同于“容易學習”:對于某些緊湊 設計而言,在掌握其精妙的內在基礎概念模型之前,要理解這個設計相當困難;但一旦理解了這個概念模型,整個視角就會改變,緊湊的奧妙也就十分簡單了。緊湊也不意味著“小巧”。即使一個設計良好的系統,對有經驗的用戶來說沒什么特異之處、“一眼”就能看懂,但仍然可能包含很多部分。

          評測一個API緊湊性的經驗法則是:API入口點通常在7左右,或者按《代碼大全2》的說法,7+27-2的范圍內。

          重構技術中的很多壞味道,特別是重復代碼,是違反正交性的明顯例子,“重構的原則性目標就是提高正交性”。

          DRY原則,或者稱為SPOT原則(single point of truth)——真理的單點性。重復的不僅僅是代碼,還包括數據結構,數據結構模型應該最小化,提倡尋找一種數據結構,使得模型中的狀態跟真實世界系統的狀態能夠一一對應

          要提高設計的緊湊性,有一個精妙但強大的方法,就是圍繞“解決一個定義明確的問題”的強核心算法組織設計,避免臆斷和捏造,將任務的核心形式化,建立明確的模型。


          文本化:好協議產生好實踐

          文本流是非常有用的通用格式,無需專門工具就可以很容易地讀寫和編輯文本流,這些格式是透明的。如果擔心性能問題,就在協議層面之上或之下壓縮文本協議流,最終產生的設計會比二進制協議更干凈,性能可能更好。使用二進制協議的唯一正當理由是:如果要處理大批量的數據,因而確實關注能否在介質上獲得最大位密度,或者是非常關心將數據轉化為芯片核心結構所必須的時間或指令開銷

          數據文件元格式:

          1DSV風格DElimiter-Seperated Values

          使用分隔符來分隔值,例如/etc/passwd

          適合場景:數據為列表,名稱(首個字段)為關鍵字,而且記錄通常很短(小于80個字符)

          2RFC 822格式

          互聯網電子郵件信息采用的文本格式,使用屬性名+冒號+值的形式,記錄屬性每行存放一個,如HTTP 1.1協議。

          適合場景:任何帶屬性的或者與電子郵件類似的信息,非常適合具有不同字段集合而字段中數據層次又扁平的記錄。

          3Cookie-jar格式。簡單使用跟隨%%的新行符(或者有時只有一個%)作為記錄分隔符,很適用于記錄非結構化文本的情況。

          適合場景:詞以上結構沒有自然順序,而且結構不易區別的文本段,或適用于搜索關鍵字而不是文本上下文的文本段。

          4Record-jar格式cookie-jarRFC-822的結合,形如

          name:dennis
          age:
          21
          %%
          name:catty
          age:
          22
          %%
          name:green
          age:
          10

          這樣的格式。

          適合場景:那些類似DSV文件,但又有可變字段數據而且可能伴隨無結構文本的字段屬性關系集合。

          5XML格式,適合復雜遞歸和嵌套數據結構的格式,并且經常可以在無需知道數據語義的情況下僅通過語法檢查就能發現形式不良損壞或錯誤生成的數據。缺點在于無法跟傳統unix工具協作。

          6Windows INI格式,形如

          [DEFAULT]
          account
          =esr

          [python]
          directory
          =/home/ers/cvs/python
          developer
          =1

          [sng]
          directory
          =/home/esr/WWW/sng
          numeric_id
          =1001
          developer
          =1

          [fetchmail]
          numeric_id
          =4928492

          這樣的格式

          適合場景:數據圍繞指定的記錄或部分能夠自然分成“名稱-屬性對”兩層組織結構。

          Unix文本文件格式的約定:

          1、如果可能,以新行符結束的每一行只存一個記錄

          2、如果可能,每行不超過80字符

          3、使用”“引入注釋

          4、支持反斜杠約定

          5、在每行一條記錄的格式中,使用冒號或連續的空白作為字段分隔符。

          6、不要過分區分tabwhitespace

          7、優先使用十六進制而不是八進制。

          8、對于復雜的記錄,使用“節(stanza)”格式,要么讓記錄格式和RFC 822電子郵件頭類似,用冒號終止的字段名關鍵字作為引導字段。

          9、在節格式中,支持連續行,多個邏輯行折疊成一個物理行

          10、要么包含一個版本號,要么將格式設計成相互獨立的的自描述字節塊。

          11、注意浮點數取整。

          12不要僅對文件的一部分進行壓縮或者二進制編碼。

          應用協議元格式

          1、經典的互聯網應用元協議 RFC 3117《論應用協議的設計》,如SMTPPOP3IMAP等協議

          2、作為通用應用協議的HTTP,在HTTP上構建專用協議,如互聯網打印協議(IPP

          3BEEP:塊可擴展交換協議,既支持C/S模式,又支持P2P模式

          4XMP-RPCSOAPJabber,基于XML的協議。


          透明性:來點光

          美在計算科學中的地位,要比在其他任何技術中的地位都重要,因為軟件是太復雜了。美是抵御復雜的終極武器

          如果沒有陰暗的角落和隱藏的深度,軟件系統就是透明的。透明性是一種被動品質。如果實際上能預測到程序行為的全部或大部分情況,并能建立簡單的心理模型,這個程序就是透明的,因為可以看透機器究竟在干什么。

          如果軟件系統所包含的功能是為了幫助人們對軟件建立正確的“做什么、怎樣做”的心理模型而設計,這個軟件系統就是可顯的。

          不要讓調試工具僅僅成為一種事后追加或者用過就束之高閣的東西。它們是通往代碼的窗口:不要只在墻上鑿出粗糙的洞,要修整這些洞并裝上窗。如果打算讓代碼一直可被維護,就始終必須讓光照進去。例如fetchmail-v選項將處理SMTPPOP的處理過程打印到標準輸出,使得fetchmail行為具有可顯性。

          在“這個設計能行嗎?”之后要提出的頭幾個問題就是“別人能讀懂這個設計嗎?這個設計優雅嗎?”我們希望,此時大家已經很清楚,這些問題不是廢話,優雅不是一種奢侈。在人類對軟件的反映中,這些品質對于減少軟件bug和提高軟件長期維護性是最基本的。

          要追求代碼的透明性,最有效的方法是很簡單,就是不要在具體操作的代碼上疊放太多的抽象層。

          OO語言使得抽象變得容易——也許是太容易了。OO語言鼓勵“具有厚重的膠合和復雜層次”的體系。當問題領域真的很復雜,確實需要大量抽象時,這可能是好事,但如果coder到頭來用復雜的辦法做簡單的事情——僅僅是為他們能夠這樣做,結果便適得其反。

          所有的OO語言都顯示出某種使程序員陷入過度分層陷阱的傾向。對象框架和對象瀏覽器并不能代替良好的設計和文檔,但卻常常被混為一談。過多的層次破壞了透明性:我們很難看清這些層次,無法在頭腦中理清代碼到底是怎樣運行的。簡潔、清晰和透明原則通通被破壞了,結果代碼中充滿了晦澀的bug,始終存在維護問題。

          膠合層中的“智能數據”卻經常不代表任何程序處理的自然實體——僅僅只是膠合物而已(典型現象就是抽象類和混入(mixin)的不斷擴散)

          OO抽象的另一個副作用就是程序往往喪失了優化的機會。

          OO在其取得成功的領域(GUI、仿真和圖形)之所以能成功,主要原因之一可能是因為在這些領域里很難弄錯類型的本體問題。例如,在GUI和圖形系統中,類和可操作的可見對象之間有相當自然的映射關系

          Unix風格程序設計所面臨的主要挑戰就是如何將分離法的優點(將問題從原始的場景中簡化、歸納)同代碼和設計的薄膠合、淺平透層次結構的優點相組合。

          太多的OO設計就像是意大利空心粉一樣,把“is-a”have a的關系弄得一團糟,或者以厚膠合層為特征,在這個膠合層中,許多對象的存在似乎只不過是為了在陡峭的抽象金字塔上占個位置罷了。這些設計都不透明,它們晦澀難懂并且難以調試。

          為透明性和可顯性而編碼:

          1、程序調用層次中(不考慮遞歸)最大的靜態深度是多少?如果大于,就要當心。

          2、代碼是否具有強大、明顯的不變性質(約束)?不變性質幫助人們推演代碼和發現有問題的情況。

          3、每個API中各個函數調用是否正交?或者是否存在太多的magic flags或者模式位?

          4、是否存在一些順手可用的關鍵數據結構或全局唯一的記錄器,捕獲系統的高層次狀態?這個狀態是否容易被形象化和檢驗,還是分布在數目眾多的各個全局變量或對象中而難以找到?

          5、程序的數據結構或分類和它們所代表的外部實體之間,是否存在清晰的一對一映射。

          6、是否容易找到給定函數的代碼部分?不僅單個函數、模塊,還有整個代碼,需要花多少精力才能讀懂

          7、代碼增加了特殊情況還是避免了特殊情況?每一個特殊情況可能對任何其他特殊情況產生影響:所有隱含的沖突都是bug滋生的溫床。然而更重要的是,特殊情況使得代碼更難理解。

          8、代碼中有多少個magic number?通過審查是否很容易查出代碼中的限制(比如關鍵緩沖區的大小)?

          隱藏細節無法訪問細節有著重要區別。不要過度保護

          無論何時碰到涉及編輯某類復雜二進制對象的設計問題時,unix傳統都提倡首先考慮,是否能夠編寫一個能夠在可編輯的文本格式和二進制格式之間來回進行無損轉換的工具?這類工具可稱為文本化器(textualizer).

          寧愿拋棄、重建代碼也不愿修補那些蹩腳的代碼。

          “代碼是活代碼、睡代碼還是死代碼?”活代碼周圍存在一個非常活躍的開發社團。睡代碼之所以“睡著”,經常是因為對作者而言,維護代碼的痛苦超過了代碼本身的效用。死代碼則是睡得太久,重新實現一段等價代碼更容易

          posted @ 2010-02-19 19:25 dennis 閱讀(3406) | 評論 (1)編輯 收藏

             Unix哲學是自下而上,而不是自上而下的,注重實效,立足于豐富的經驗,你不會在正規方法學和標準中找到它。

             Unix管道的發明人Doug McIlroy曾經說過:

          1、讓每個程序就做好一件事,如果有新任務就重新開始,不要往新程序中加入功能而搞的復雜。

          2、假定每個程序的輸出都會成為另一個程序的輸入,哪怕那個程序是未知的。輸出中不要有無關的信息干擾,避免使用嚴格的分欄格式和二進制格式輸入。不要堅持使用交互式輸入。

          3、盡可能早將設計和編譯的軟件投入試用,哪怕是操作系統也不例外,理想情況下應該是幾星期內,對抽劣的代碼別猶豫,扔掉重寫

          4、優先使用工具,而非拙劣的幫助來減輕編程任務的負擔,工欲善其事,必先利其器。


          Rob Pike在《Notes on C programming》中提到:

          原則1:你無法斷行程序會在什么地方耗費運行時間。瓶頸經常出現在想不到的地方,所以別急于胡亂找個地方改代碼,除非你已經證實那兒就是瓶頸所在。

          原則2:估量。在你沒對代碼進行估量,特別是沒找到最耗時的那部分之前,別去優化速度。

          原則3:花哨的算法,在n很小的適合通常很慢,而n通常很小。花哨算法的常數復雜度很大,除非你確定n一直很大,否則不要用花哨算法(即使n很大,也要優先考慮原則2)。

          原則4:花哨的算法比簡單的算法更容易出bug ,更難實現。盡量使用簡單的算法配合簡單的數據結構。

          原則5數據壓倒一切。如果已經選擇了正確的數據結構并且把一切組織得井井有條,正確的算法也就不言自明,編程的核心是數據結構,而不是算法。

          原則6:沒有原則6.


          Ken Thompson對原則4做了強調:

          拿不準就窮舉。


          Unix哲學的17條原則:

          1、模塊原則:簡潔的接口拼合簡單的部件。

          2、清晰原則:清晰勝于機巧。

          3、組合原則:設計時考慮拼接組合。

          4、分離原則:策略同機制分離,接口同引擎分離。

          5、簡潔原則:設計要簡潔,復雜度能低則低。

          6、吝嗇原則:除非卻無他法,不要編寫龐大的程序。

          7、透明性原則:設計要可見,以便審查和調試。

          8、健壯原則:健壯源于透明與簡潔。

          9、表示原則:把知識疊入數據,以求邏輯質樸而健壯。

          10、通俗原則:接口設計避免標新立異。

          11、緘默原則:如果一個程序沒什么好說的,就沉默。

          12、補救原則:出現異常時,馬上退出,并給出足夠錯誤信息。

          13、經濟原則:寧花機器一分,不花程序員一秒。

          14、生成原則:避免手工hack,盡量編寫程序去生成程序。

          15、優化原則:雕琢前先要有原型,跑之前先學會走。

          16、多樣原則:絕不相信所謂“不二法門”的斷言。

          17、擴展原則:設計著眼未來,未來總是比預想來得快。


          Unix哲學之一言以蔽之:KISS

          Keep it simple,stupid!

          應用unix哲學:

          1、只要可行,一切都應該做成與來源和目標無關的過濾器

          2、數據流應盡可能的文本化(這樣可以用標準工具來查看和過濾)。

          3、數據庫部署和應用協議應盡可能文本化(讓人閱讀和編輯)。

          4、復雜的前端(用戶界面)和后端應該涇渭分明

          5、如果可能,用c編寫前,先用解釋性語言搭建原型

          6、當且僅當只用一門編程語言會提高程序復雜度時,混用語言編程才比單一語言編程來得好。

          7寬收嚴發(對接收的東西要包容,對輸出的東西要嚴格)

          8、過濾時,不需要丟棄的消息絕不丟。

          9小就是美。在確保完成任務的基礎上,程序功能盡可能的少。


          最后強調的是態度:

          要良好地運用unix哲學,你就應該不斷地追求卓越,你必須相信,程序設計是一門技藝,值得你付出所有的智慧、創造力和激情。否則,你的視線就不會超越那些簡單、老套的設計和實現;你就會在應該思考的時候急急忙忙跑去編程。你就會在該無情刪繁就簡的時候反而把問題復雜化——然后你還會反過來奇怪你的代碼怎么會那么臃腫,那么難以調試。

          要良好地運用unix哲學,你應該珍惜你的時間絕不浪費。一旦某人已經解決了某個問題,就直接拿來利用,不要讓驕傲或偏見拽住你又去重做一遍。永遠不要蠻干;要多用巧勁,省下力氣在需要的時候用,好鋼用到刀刃上。善用工具,盡可能將一切自動化

          軟件設計和實現是一門充滿快樂的藝術,一種高水平的游戲。如果這種態度對你來說聽起來有些荒謬,或者令你隱約感到有些困窘,那么請停下來,想一想,問問自己是不是已經把什么給遺忘了。如果只是為了賺錢或者打發時間,你為什么要搞軟件設計,而不是別的什么呢?你肯定曾經也認為軟件設計值得你付出激情……

          要良好地運用unix哲學,你需要具備(或者找回)這種態度。你需要用心。你需要去游戲。你需要樂于探索。


          操作系統的風格元素:

          1、什么是操作系統的統一性理念

          2、多任務能力

          3、協作進程(IPC

          4、內部邊界

          5、文件屬性和記錄結構

          6、二進制文件格式

          7、首選用戶界面風格

          8、目標受眾

          9、開發的門坎

          posted @ 2010-02-18 19:23 dennis 閱讀(7852) | 評論 (7)編輯 收藏

              經常在china-pub上買書,我的賬號早已經到五星,再加上china-pub上很多新書首發,因此盡管當當網有時候更便宜,還是經常在china-pub買。不過這次我要出離憤怒了,同樣是上個月29號下的單,當當在周一就送到了,而china-pub到今天5號竟然還沒有送到,看訂單信息是貨已出庫,并且發貨時間在1月31號,從北京到杭州走了5天竟然還沒到,選的什么快遞公司。
              不到也還罷了,更可惡的是售后服務,我在china-pub的客服論壇發帖,他們自己承諾工作時間內60分鐘回復,回復個P啊,從昨天到今天沒見一個人點擊我的帖子,更何談回復。OK,論壇不行,那么我發郵件吧,從china-pub的客服服務頁的客服email進去,填寫表單,OK,提交失敗?為什么,沒有驗證碼,可是你TMD根本沒顯示驗證碼啊,這是什么狗屁程序,見下圖



           你看到驗證碼在哪里嗎?刷新N遍愣是沒出來,多牛B的客服email啊。

                線上不行,那么我打電話可以吧,這電話是普通長途也還罷了,我自己掏錢沒事,可總得有人接吧,事實是我早上打了3個電話,兩個查詢訂單,等了N久沒人接,靠,那我投訴吧,轉投訴,一樣沒人接,我只能說china-pub你們真牛氣,你們的客服是擺設不成?你們這么牛氣,我也不敢買了,惹不起我還躲不起啊。




          posted @ 2010-02-05 09:53 dennis 閱讀(3138) | 評論 (16)編輯 收藏

          僅列出標題
          共56頁: First 上一頁 10 11 12 13 14 15 16 17 18 下一頁 Last 
          主站蜘蛛池模板: 五莲县| 英德市| 溧阳市| 滕州市| 临武县| 乌兰察布市| 花垣县| 宜兴市| 延安市| 霍山县| 渑池县| 兴海县| 绩溪县| 抚州市| 溧阳市| 芜湖县| 平定县| 岢岚县| 昆山市| 南平市| 板桥市| 古浪县| 桂林市| 恩施市| 汉川市| 万安县| 广河县| 南陵县| 隆子县| 姜堰市| 河北省| 安远县| 肃宁县| 宝应县| 洪江市| 永平县| 时尚| 邳州市| 平度市| 永修县| 将乐县|