Ответ 1
Похоже, что вывод: это ошибка.
В процессе выполнения простого бенчмаркинга я наткнулся на то, что меня удивило. Возьмите этот фрагмент из Network.Socket.Splice:
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)
Можно было бы ожидать, что здесь hGetBufSome
и hPutBuf
не потребуется выделять память, поскольку они записывают и считывают из предварительно выделенного буфера. docs, похоже, поддерживает эту интуицию... Но, увы:
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
Я должен предположить, что это специально... но я понятия не имею, что это за цель. Еще хуже: я просто достаточно умен, чтобы получить этот профиль, но не достаточно умный, чтобы точно определить, что выделяется.
Любая помощь по этим линиям будет оценена.
ОБНОВЛЕНИЕ: Я сделал еще несколько профилирования с двумя сильно упрощенными тестовыми окнами. Первый тестовый файл напрямую использует операции чтения/записи из System.Posix.Internals:
echo :: Ptr Word8 -> IO ()
echo buf = forever $ do
threadWaitRead $ Fd 0
len <- c_read 0 buf 1
c_write 1 buf (fromIntegral len)
yield
Как вы могли бы надеяться, каждый раз через цикл он не выделяет память в куче. Второй тест использует операции чтения/записи из GHC.IO.FD:
echo :: Ptr Word8 -> IO ()
echo buf = forever $ do
len <- readRawBufferPtr "read" stdin buf 0 1
writeRawBufferPtr "write" stdout buf 0 (fromIntegral len)
ОБНОВЛЕНИЕ # 2: Мне было предложено записать это как ошибку в GHC Trac... Я все еще не уверен, что на самом деле это ошибка (в отличие от преднамеренного поведения, известного ограничения, или что-то еще), но вот он: https://ghc.haskell.org/trac/ghc/ticket/9696
Похоже, что вывод: это ошибка.
Я попытаюсь угадать, основываясь на code
Время выполнения пытается оптимизировать небольшие чтения и записи, поэтому он поддерживает внутренний буфер. Если ваш буфер длится 1 байт, он будет неэффективным, чтобы использовать его по-разному. Таким образом, внутренний буфер используется для чтения большего количества данных. Это, вероятно, ~ 32 КБ. Плюс что-то подобное для написания. Плюс ваш собственный буфер.
В коде есть оптимизация - если вы указываете буфер больше, чем внутренний, а затем пуст, он будет использовать ваш буфер правильно. Но внутренний буфер уже выделен, поэтому он будет не меньше использования памяти. Я не знаю, как распустить внутренний буфер, но вы можете открыть запрос функции, если это важно для вас.
(Я понимаю, что моя догадка может быть абсолютно неправильной.)
ADD:
Этот, кажется, выделяет, но я до сих пор не знаю почему.
В чем вы нуждаетесь, максимальное использование памяти или количество выделенных байтов?
c_read
- это функция C, она не выделяется на кучу haskell (но может выделяться на кучу C).
readRawBufferPtr
является функцией Haskell, и обычно для функций haskell выделяется много памяти, что быстро становится мусором. Просто из-за неизменности. Обычно программа haskell выделяет, например, 100 ГБ, в то время как использование памяти составляет менее 1 МБ.