-
Notifications
You must be signed in to change notification settings - Fork 93
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
attoparsec now seems to be slower than joyent http parser #153
Comments
What is the joyent HTTP parser? Regardless, I'll have a look at the Core tonight. Sounds like a fun puzzle. |
Hi @bgamari when you look at the blog post, there is a link "the hand-written C code that powers Node.js’s HTTP parser". This link now forwards to https://github.com/nodejs/http-parser/blob/master/http_parser.c I believe node.js used to be a joyent product and now has been moved into it's own foundation. I'd love to know how you go about reading core. I've been having difficulties with it. I was trying to read some core to see why WAI/WARP is slower than other languages in framework benchmarks. But i just don't have the skills to make much sense of it ... |
@flip111, yes, I've been meaning to write a bit about how I read Core for a long time now. In general I start by moving the bits that I'm interested in benchmarking into a new module. In the case of the attoparsec benchmark that you linked to I moved all of the parse :: ByteString -> Either String [(Request, [Header])]
parse = P.parseOnly allRequests This ensures that I'm just looking at the Core of the bindings of interest; these are otherwise often drowned out by other clutter (especially when using a benchmarking framework like Next I work out how to compile the program using GHC alone; when working on a performance issue I don't like working through a build tool like Next I dump the Core of the module of interest. When looking at a performance issue the GHC command line will almost always look something like:
Another useful trick is to enable debug information with Once I have the Core I'll jump into a good editor; syntax highlighting is important since Core tends to be quite large. Typically just using the Haskell lexer is good enough but When browsing the Core the usual thing I look for is allocation. This is represented in Core by lifted let {
x :: Int
x = I# y
} ... Corresponds to an allocation of two words (one word for let {
x :: Int#
x = +# y 1#
} ... Will be lowered to computing Aside: Since we are interested primarily in allocations, you might think that we would be better off looking at the Ticky profilingAnother trick that is often helpful in identify allocation (particularly in programs which should have very little allocation) is GHC's
Then run the program with
Focus on the
|
Hi @bgamari thanks for the writeup So far i looked into refactoring warp's code into a small snippet. Then went on to obtaining the "package environment file". I always used stack, not cabal or ghc. From what i could find stack does not generate the package environment file and does also not install cabal-install. cabal-install is best installed globally through After that i found a flag on cabal for the I guess now this file has to be loaded with
So maybe there is another way to load the package environment file. Now i'm left with the question if i got the right file ( |
@flip111 TL;DR is:
|
Right it was hidden so that's why i didn't see it :P thanks @llelf |
I ended up calling ghc through stack like this
which takes care of getting me the right ghc version. I assume also the right packages now. The binary gets written out into the source directory instead of something like I installed spacemaces but i didn't get the main :: IO ()
main = Main.main1 `cast` <Co:3> not sure from which module this Main.main1
:: GHC.Prim.State# GHC.Prim.RealWorld
-> (# GHC.Prim.State# GHC.Prim.RealWorld, () #)
Main.main1
= \ (s_a3NY :: GHC.Prim.State# GHC.Prim.RealWorld) ->
case Control.Concurrent.runInUnboundThread1
(Main.main2 `cast` <Co:3>) s_a3NY
of
{ (# ipv_a3O1, ipv1_a3O2 #) ->
(# ipv_a3O1, GHC.Tuple.() #)
} The There isn't a let binding in the core output, so nothing to optimize there i guess? Flamegraph shows a lot of time being taken in the ticky file shows
Those two functions are: -- RHS size: {terms: 2, types: 0, coercions: 0, joins: 0/0}
Main.main5
:: forall r.
Data.ByteString.Builder.Internal.BuildStep r
-> Data.ByteString.Builder.Internal.BufferRange
-> GHC.Prim.State# GHC.Prim.RealWorld
-> (# GHC.Prim.State# GHC.Prim.RealWorld,
Data.ByteString.Builder.Internal.BuildSignal r #)
Main.main5 = Network.Wai.responseLBS1 Main.main6
-- RHS size: {terms: 4, types: 4, coercions: 0, joins: 0/0}
Main.main3
:: Network.Wai.Internal.Request
-> (Network.Wai.Internal.Response
-> IO Network.Wai.Internal.ResponseReceived)
-> IO Network.Wai.Internal.ResponseReceived
Main.main3
= \ _
(respond_a2I6
:: Network.Wai.Internal.Response
-> IO Network.Wai.Internal.ResponseReceived) ->
respond_a2I6 Main.main4 I don't see that I googled and asked around a bit, so far i have not found out how to get core/ticky files from dependencies. |
Keep in mind that the http parser used to be faster because it didn’t work correctly. |
Since the blog post 2014 http://www.serpentine.com/blog/2014/05/31/attoparsec/ things seem to have changed. For another project in benchmarked a few http parsers are posted the results here rust-bakery/parser_benchmarks#25
I get
99290 / 64687 = 1.53x
more slowdown on one test and759000 / 282021 = 2.69x
on the other test (measuring in ns) compared to joyent more recent version of http parser.Perhaps there is some more room for improvement? I've been looking at core, but with my untrained eye i couldn't make much of it. All i could find was potentially #152
The text was updated successfully, but these errors were encountered: