2014-10-13 11 views
14

Basit bir kıyaslama yaparken, beni şaşırtan bir şeye rastladım. Network.Socket.Splice bu kod parçasını kaldırmak: içine yazmak ve önceden tahsis tampon okundu olarakNeden hGetBuf, hPutBuf, vb bellek ayırır?

hSplice :: Int -> Handle -> Handle -> IO() 
hSplice len s t = do 
    a <- mallocBytes len :: IO (Ptr Word8) 
    finally 
    (forever $! do 
     bytes <- hGetBufSome s a len 
     if bytes > 0 
     then hPutBuf t a bytes 
     else throwRecv0) 
    (free a) 

Bir, burada hGetBufSome ve hPutBuf bellek ayırmaya gerek olmaz beklenebilir. docs bu sezgi yedeklemek görünüyor ... Ama ne yazık ki:

         individual  inherited 
COST CENTRE       %time %alloc %time %alloc  bytes 

hSplice         0.5 0.0 38.1 61.1  3792 
    hPutBuf        0.4 1.0 19.8 29.9 12800000 
    hPutBuf'        0.4 0.4 19.4 28.9 4800000 
    wantWritableHandle     0.1 0.1 19.0 28.5 1600000 
    wantWritableHandle'     0.0 0.0 18.9 28.4   0 
     withHandle_'      0.0 0.1 18.9 28.4 1600000 
     withHandle'      1.0 3.8 18.8 28.3 48800000 
     do_operation      1.1 3.4 17.8 24.5 44000000 
     withHandle_'.\     0.3 1.1 16.7 21.0 14400000 
      checkWritableHandle   0.1 0.2 16.4 19.9 3200000 
      hPutBuf'.\     1.1 3.3 16.3 19.7 42400000 
      flushWriteBuffer    0.7 1.4 12.1 6.2 17600000 
      flushByteWriteBuffer  11.3 4.8 11.3 4.8 61600000 
      bufWrite      1.7 6.9  3.0 9.9 88000000 
      copyToRawBuffer    0.1 0.2  1.2 2.8 3200000 
       withRawBuffer    0.3 0.8  1.2 2.6 10400000 
       copyToRawBuffer.\   0.9 1.7  0.9 1.7 22400000 
      debugIO      0.1 0.2  0.1 0.2 3200000 
      debugIO      0.1 0.2  0.1 0.2 3200016 
    hGetBufSome       0.0 0.0 17.7 31.2   80 
    wantReadableHandle_     0.0 0.0 17.7 31.2   32 
    wantReadableHandle'     0.0 0.0 17.7 31.2   0 
    withHandle_'      0.0 0.0 17.7 31.2   32 
     withHandle'      1.6 2.4 17.7 31.2 30400976 
     do_operation      0.4 2.4 16.1 28.8 30400880 
     withHandle_'.\     0.5 1.1 15.8 26.4 14400288 
     checkReadableHandle    0.1 0.4 15.3 25.3 4800096 
      hGetBufSome.\     8.7 14.8 15.2 24.9 190153648 
      bufReadNBNonEmpty    2.6 4.4  6.1 8.0 56800000 
      bufReadNBNonEmpty.buf'  0.0 0.4  0.0 0.4 5600000 
      bufReadNBNonEmpty.so_far' 0.2 0.1  0.2 0.1 1600000 
      bufReadNBNonEmpty.remaining 0.2 0.1  0.2 0.1 1600000 
      copyFromRawBuffer   0.1 0.2  2.9 2.8 3200000 
      withRawBuffer    1.0 0.8  2.8 2.6 10400000 
       copyFromRawBuffer.\  1.8 1.7  1.8 1.7 22400000 
      bufReadNBNonEmpty.avail  0.2 0.1  0.2 0.1 1600000 
      flushCharReadBuffer   0.3 2.1  0.3 2.1 26400528 

heap profile by module heap profile by type

Bu amaçla üzerinde olduğunu varsaymak zorunda ... ama bu amaç ne olabileceğini hiçbir fikrim yok. Daha da kötüsü: Bu profili almak için yeterince akıllıca değilim, ama tam olarak neyin tahsis edildiğini anlamaya yetecek kadar zeki değil.

Bu hatlardaki herhangi bir yardım takdir edilecektir.


GÜNCELLEME: iki büyük ölçüde basitleştirilmiş testcases biraz daha profil yaptık. İlk testcase doğrudan System.Posix.Internals gelen okuma/yazma ops kullanır: umut edeceğiniz gibi

echo :: Ptr Word8 -> IO() 
echo buf = forever $ do 
    threadWaitRead $ Fd 0 
    len <- c_read 0 buf 1 
    c_write 1 buf (fromIntegral len) 
    yield 

, bu döngü içinde yığın her seferinde hiçbir bellek ayırır. İkinci testcase GHC.IO.FD gelen okuma/yazma ops kullanır:

echo :: Ptr Word8 -> IO() 
echo buf = forever $ do 
    len <- readRawBufferPtr "read" stdin buf 0 1 
    writeRawBufferPtr "write" stdout buf 0 (fromIntegral len) 

GÜNCELLEME # 2: Ben GHC Trac bir hata olarak bu dosyaya tavsiye edildi ... Hala emin değilim aslında (kasıtlı davranış, bilinen bir sınırlama ya da her türlü aksine) bir hata olduğunu ama işte burada: https://ghc.haskell.org/trac/ghc/ticket/9696

+2

Eğer 'bytes' oluşturmak için bellek ayrılırken muyuz? I + RTS -hy' 'ile profile zaman ... Sanmıyorum –

+0

@GabrielGonzalez, öbek üzerinde hakim tipi ARR_WORDS'' dır. "Bayt" türü "Int" olmalıdır (okunan bayt sayısı). – mergeconflict

+2

@mergeconflict https://stackoverflow.com/questions/7241470/what-is-arr-words-in-a-ghc-heap-profile#7241686 ARR_WORDS ByteArray # karşılık geldiğini belirtmektedir. Neden bu kadar çok tahsis edildiğini bilmiyorum ama profildeki güzel plato, programın sabit alanda çalıştığını gösteriyor. – cheecheeo

cevap

0

geçerli: it's a bug.

+0

Bu gerçekten bir hata değil, sadece optimizasyon için kaçırılmış bir fırsat. –

1

Ben code

012 dayanarak tahmin çalışacağımÇalışma zamanı, küçük okuma ve yazma işlemlerini optimize etmeye çalışır, bu nedenle dahili arabelleği korur. Arabellek 1 bayt uzunsa, doğru kullanmak için verimsiz olacaktır. Böylece dahili tampon, daha büyük miktarda veriyi okumak için kullanılır. Muhtemelen ~ 32Kb uzunluğundadır. Artı yazmak için benzer bir şey. Artı kendi tamponun.

Kod bir optimizasyona sahiptir - arabellekten daha büyük bir tampon sağlarsanız ve daha sonra boşsa, arabelleğinizi doğru kullanır. Ancak, dahili arabellek zaten ayrılmış olduğundan, daha az bellek kullanımı olmayacaktır. Dahili arabelleği nasıl çözebileceğimi bilmiyorum, ancak sizin için önemli olan özellik isteğini açabilirsiniz.

ADD (. Benim tahminim tamamen yanlış olabileceğini fark):

Bu seferki tahsis gibi görünüyor, ama hala neden bilmiyorum.

Endişeniz nedir, maksimum bellek kullanımı veya ayrılan bayt sayısı nedir?

c_read

readRawBufferPtr

Haskell işlevidir (. Ama C öbek üzerinde tahsis edebilir) o Haskell'ın öbek üzerinde tahsis etmez, bir C işlevidir ve haskell fonksiyonları çok fazla bellek tahsis etmek için olağandır, Bu hızla bir çöp haline gelir. Sadece değişmezlikten dolayı. Bellek kullanımı 1Mb'nin altında iken, 10000bb tahsis etmek için haskell programının yaygındır. Bu sonuç gibi görünüyor

+0

Benim endişem tahsisi ve GC değil maksimum bellek kullanımı CPU maliyetidir. Örneğin: "readRawBufferPtr" uygulamasının kazılması, "throwErrnoIfMinus1RetryMayBlock" çağrısında meydana gelen yığın ayırma gibi görünüyor; Bunu sıkı bir iç döngüde kullanırsam, bu aramanın mümkün olduğunca az döngü olmasını istiyorum. Bunun değişmezlik/saf fonksiyonel stil nedeniyle bir tahsis örneği olduğunu düşünmüyorum. – mergeconflict

+1

@mergeconflict: Tahsisatın, daha yüksek seviyeli fonksiyonların kullanılmasından kaynaklandığı görülüyor (sözünü ettiğim gibi, throwErrnoIfMinus1RetryMayBlock). Bununla birlikte, eğer CPU'nun tahsis maliyetinden endişe ediyorsanız, o zaman GHC çıktılarının CPU maliyetinden endişe etmelisiniz ve ölçmenin kolay olduğu için tahsisat üzerinde odaklanmamalısınız. Bir tahsis iki talimattır: bir işaretçi artırma ve onu yığın sınırına göre test etme. Çok sayıda GHC'nin çıkışı bundan daha kötüdür ve sistem çağrısının ('read') yapılmasıyla karşılaştırıldığında ihmal edilebilir olmalıdır. –

+0

@mergeconflict Yani endişe performans mı? Muhtemelen soru gövdesinde ve başlıkta bunu yansıtmalısınız. Ve neden bellek ayırmanın sizin durumunuzda performans ile ilgili bir şey olduğunu düşünüyorsunuz? Bu darboğaz olabilir, ancak bu argümanlar gerektirir. – Yuras

İlgili konular