Skip to content
Please note that GitHub no longer supports Internet Explorer.

We recommend upgrading to the latest Microsoft Edge, Google Chrome, or Firefox.

Learn more
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

Optimise isReserved #46

Merged
merged 1 commit into from Jan 27, 2020
Merged

Optimise isReserved #46

merged 1 commit into from Jan 27, 2020

Conversation

@mpickering
Copy link
Contributor

mpickering commented Jan 26, 2020

This function is called many thousands of times by ghcide and was a
hotspot in profiling.

This patch reduced time taken on one benchmark which called hover 1000
times from 259 seconds to 170 seconds.

See digital-asset/ghcide#101

This function is called many thousands of times by ghcide and was a
hotspot in profiling.

This patch reduced time taken on one benchmark which called hover 1000
times from 259 seconds to 170 seconds.
@fendor

This comment has been minimized.

Copy link

fendor commented Jan 26, 2020

Maybe it makes sense to add this information as a comment? So that feature devs understand why this optimisation has been done?

@AndreasPK

This comment has been minimized.

Copy link

AndreasPK commented Jan 27, 2020

mpickering showed this patch off to me so I look at the code a bit.

isReserved is really the meat of what this patches optimizes.

Before this patch it compiled to this core:

  = \ (c1_abyn :: Char) ->
      case GHC.List.elem
             @ Char
             ghc-prim-0.5.3:GHC.Classes.$fEqChar
             c1_abyn
             Network.URI.isAllowedInURI5
      of {
        False ->
          GHC.List.elem
            @ Char
            ghc-prim-0.5.3:GHC.Classes.$fEqChar
            c1_abyn
            Network.URI.isAllowedInURI3;
        True -> ghc-prim-0.5.3:GHC.Types.True
      }

essentially elem c foo || elem c bar

With this patch isReserved compiles to this:

Network.URI.$wisReserved
  = \ (ww_sq86 :: ghc-prim-0.5.3:GHC.Prim.Char#) ->
      case ww_sq86 of {
        __DEFAULT -> ghc-prim-0.5.3:GHC.Types.False;
        '!'# -> ghc-prim-0.5.3:GHC.Types.True;
        '#'# -> ghc-prim-0.5.3:GHC.Types.True;
        '$'# -> ghc-prim-0.5.3:GHC.Types.True;
        '&'# -> ghc-prim-0.5.3:GHC.Types.True;
        '\''# -> ghc-prim-0.5.3:GHC.Types.True;
        '('# -> ghc-prim-0.5.3:GHC.Types.True;
        ')'# -> ghc-prim-0.5.3:GHC.Types.True;
        '*'# -> ghc-prim-0.5.3:GHC.Types.True;
        '+'# -> ghc-prim-0.5.3:GHC.Types.True;
        ','# -> ghc-prim-0.5.3:GHC.Types.True;
        '/'# -> ghc-prim-0.5.3:GHC.Types.True;
        ':'# -> ghc-prim-0.5.3:GHC.Types.True;
        ';'# -> ghc-prim-0.5.3:GHC.Types.True;
        '='# -> ghc-prim-0.5.3:GHC.Types.True;
        '?'# -> ghc-prim-0.5.3:GHC.Types.True;
        '@'# -> ghc-prim-0.5.3:GHC.Types.True;
        '['# -> ghc-prim-0.5.3:GHC.Types.True;
        ']'# -> ghc-prim-0.5.3:GHC.Types.True
      }

The former has overhead of two function calls, and the actual checking loops.

The later has no overhead + results in a binary search for the pattern match.

As a result the later is about 10 times as fast in a simple benchmark like this:

      env (return "http://ex.it/foo/bar/baz/bap") $ \u1 ->
        bench "u1" $ nf (map (isReserved)) u1,

Currently processing one character like this takes somewhere between 100 and 150ns.

After this patch it takes 10ns and 15ns.
That's only about 60 cycles and includes traversing one element of the list so not bad.

@osa1

This comment has been minimized.

Copy link

osa1 commented Jan 27, 2020

Amazing.

Maybe it makes sense to add this information as a comment?

Agreed, someone in a few months will look at this code and say "why not use elem?" and revert this change.

',' -> True
';' -> True
'=' -> True
_ -> False

subDelims :: URIParser String
subDelims = (:[]) <$> oneOf "!$&'()*+,;="

This comment has been minimized.

Copy link
@ndmitchell

ndmitchell Jan 27, 2020

Why is this not satisfy isSubDelims?

@osa1

This comment has been minimized.

Copy link

osa1 commented Jan 27, 2020

This may be a bug in elem/build rule in GHC, see https://gitlab.haskell.org/ghc/ghc/issues/17752#note_250120

@ezrakilty

This comment has been minimized.

Copy link
Contributor

ezrakilty commented Jan 27, 2020

It would be great if the compiler did the right thing for the elem version. In the meantime, though, I don't mind this being written out as a case match. I'll add a comment explaining why we've done so and linking back to here.

@ezrakilty ezrakilty merged commit 5901443 into haskell:master Jan 27, 2020
1 check failed
1 check failed
continuous-integration/travis-ci/pr The Travis CI build could not complete due to an error
Details
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
6 participants
You can’t perform that action at this time.