Skip to content
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

FindIndices optimized using findIndex and inlining #270

Merged
merged 1 commit into from
Sep 10, 2020

Conversation

archaephyrryx
Copy link
Contributor

Reimplements findIndices to iteratively call findIndex, which
yields an approximate 2x improvement over original implementation
for a simple equality predicate

Adds inline pragma for findIndices, which yields an approximate 10x
improvement for a simple equality predicate

Additionally inlines findIndices functions in
Data.ByteString{.Lazy,}{.Char8,}

resolves #269

@Bodigrim
Copy link
Contributor

Bodigrim commented Aug 26, 2020

Impressive! Could you please add benchmarks?

@Bodigrim
Copy link
Contributor

Could we possibly also add rewrite rules?

{-# RULES
"ByteString specialise findIndex (x ==)" forall x. findIndex (x ==) = elemIndex x
"ByteString specialise findIndex (== x)" forall x. findIndex (== x) = elemIndex x
"ByteString specialise findIndices (x ==)" forall x. findIndices (x ==) = elemIndices x
"ByteString specialise findIndices (== x)" forall x. findIndices (== x) = elemIndices x
  #-}

This would fix haskell-streaming/streaming-bytestring#15 for free.

Data/ByteString.hs Outdated Show resolved Hide resolved
@archaephyrryx
Copy link
Contributor Author

Could we possibly also add rewrite rules?

{-# RULES
"ByteString specialise findIndex (x ==)" forall x. findIndex (x ==) = elemIndex x
"ByteString specialise findIndex (== x)" forall x. findIndex (== x) = elemIndex x
"ByteString specialise findIndices (x ==)" forall x. findIndices (x ==) = elemIndices x
"ByteString specialise findIndices (== x)" forall x. findIndices (== x) = elemIndices x
  #-}

I haven't really explored the RULES pragma deeply enough to know for certain, but I am reasonably sure that we would have to be careful to specify an appropriate phase number so that the rule can fire before == is inlined to a type-determined monomorphic equality function. I could at the very least try to see what I can come up with that compiles without "rule may not fire" warnings that produces the appropriate behavior.

I did end up benchmarking the relative performance of findIndex and findIndices before and after the changes (both the reimplementation and the inlining, as separate comparisons), but any benchmark I write will only reflect the current relative performance, and won't indicate a diachronic benchmark of the previous implementation of findIndices against the reimplemented one. I can still include it, but it is perhaps only useful for posterity if the implementation changes again. If you let me know exactly what kind of benchmark you had in mind, I can try to include something appropriately illustrative of the comparison you wanted to make.

@Bodigrim
Copy link
Contributor

Yes, you can use

-- For Data.ByteString.Char8
{-# INLINE [1] findIndices #-}
{-# RULES "findIndices (== x)" forall x. findIndices (`eqChar` x) = elemIndices x #-}

-- For Data.ByteString
{-# INLINE [1] findIndices #-}
{-# RULES "findIndices (== x)" forall x. findIndices (`eqWord8` x) = elemIndices x #-}

Cf.

{-# RULES
"ByteString specialise break (x ==)" forall x.
break (x `eqWord8`) = breakByte x
and
{-# RULES
"ByteString specialise break (x==)" forall x.
break (x `eqChar`) = breakChar x
.


Just add a single benchmark for findIndices in a separate commit, ideally before the commit changing its implementation.

Data/ByteString/Char8.hs Outdated Show resolved Hide resolved
Data/ByteString/Char8.hs Outdated Show resolved Hide resolved
Reimplements findIndices to iteratively call findIndex, which
yields an approximate 2x improvement over original implementation
for a simple equality predicate

Adds inline pragma for findIndices, which yields an approximate 10x
improvement for a simple equality predicate

Adds rewrite rules that optimize calls of `findIndex (==x)` with `elemIndex x`
and `findIndices (==x)`->`elemIndices x` (both left- and right-sections)
for Data.ByteString and Data.ByteString.Char8

Adds phase number [1] for inline rules on `findIndex` and `findIndices` to
allow said rules to fire properly
@Bodigrim
Copy link
Contributor

Bodigrim commented Sep 8, 2020

While I have stylistic suggestions for benchmarks in #272, I succesfully used them to verify performance improvements claimed in this PR. All looks good to me, and since this is a massive improvement I do not want it to be blocked by bikeshedding in #272.

@Bodigrim Bodigrim added this to the 0.11.0.0 milestone Sep 8, 2020
@Bodigrim Bodigrim merged commit 900f636 into haskell:master Sep 10, 2020
@Bodigrim
Copy link
Contributor

Thanks, @archaephyrryx!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Strict ByteString findIndices very costly
4 participants