-
Notifications
You must be signed in to change notification settings - Fork 140
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Lazier unlines #477
Lazier unlines #477
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be good to have a test for the laziness property or at least a comment that points out this requirement for the implementation.
Should I also change the strict version? It won't benefit from the strictness changes and a similar benchmark doesn't show very significant changes, but it does make sense to implement both functions similarly and this new implementation is simpler. |
@sjakobi I've added a test that checks the laziness of unlines. It fails with the old implementation. |
It's not directly related to the motivation for this patch, but does performance of lazy |
@noughtmare What's the performance impact on the strict version? @clyring Good question. I think it would be best to address it separately from this PR though, to keep the size of this one more manageable and to avoid a long-standing branch that might acquire merge conflicts. Feel free to record the idea on the issue tracker. |
Before:
After:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
unlines [] = empty | ||
unlines ss = concat (List.intersperse nl ss) `append` nl -- half as much space | ||
where nl = singleton '\n' | ||
unlines = concat . List.concatMap (\x -> [x, singleton '\n']) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is better than before, because it avoids copying the full output twice (with and without final nl
).
Ideally unlines
should be implemented in a style similar to intercalate
, avoiding concat
(which is quite ineffective):
Lines 1209 to 1227 in 5efd6b5
intercalate :: ByteString -> [ByteString] -> ByteString | |
intercalate _ [] = mempty | |
intercalate _ [x] = x -- This branch exists for laziness, not speed | |
intercalate (BS fSepPtr sepLen) (BS fhPtr hLen : t) = | |
unsafeCreate totalLen $ \dstPtr0 -> | |
unsafeWithForeignPtr fSepPtr $ \sepPtr -> do | |
unsafeWithForeignPtr fhPtr $ \hPtr -> | |
memcpy dstPtr0 hPtr hLen | |
let go _ [] = pure () | |
go dstPtr (BS fChunkPtr chunkLen : chunks) = do | |
memcpy dstPtr sepPtr sepLen | |
let destPtr' = dstPtr `plusPtr` sepLen | |
unsafeWithForeignPtr fChunkPtr $ \chunkPtr -> | |
memcpy destPtr' chunkPtr chunkLen | |
go (destPtr' `plusPtr` chunkLen) chunks | |
go (dstPtr0 `plusPtr` hLen) t | |
where | |
totalLen = List.foldl' (\acc chunk -> acc +! sepLen +! length chunk) hLen t | |
(+!) = checkedAdd "intercalate" |
Thanks @noughtmare! |
* Add benchmark for lazy unlines * Make unlines lazier * Add strict unlines benchmark * Simplify strict unlines * Test laziness of unlines
Fixes #476
Benchmark results:
before:
after: