-
Notifications
You must be signed in to change notification settings - Fork 178
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
Improve fromAscList and friends. #658
Conversation
|
You're probably right; I'll verify tomorrow. I forgot about the pitfall that functions that are used only once are still inlined, so my benchmark attempt for this scenario was flawed. |
As expected, you were right about the extra thunks. Interestingly that was not a regression... the previous I've sketched a test for such thunks in c88dc77... it's not yet properly split by module (Data.{,Int}Map{,.Strict}). I should probably split that out into a separate pull request, but I want to see if the current code passes the test for all relevant compilers first. |
The approach I was suggesting was more like fromMonoListWithKey f = \ xs0 -> go xs0
where
go [] = []
.... I'm not sure how that will compare with your way in the non-inlined case ( |
That's what I tried at first, but it didn't cure the creation of thunks by |
New benchmark result: for this particular benchmark inexplicably the code is 10% slower than before inlining
In contrast, running the code from https://gist.github.com/int-e/36578cb04d0a187252c366b0b45ddcb6#file-intmapfal2-hs the previous code was a tiny bit slower. Note that none of these benchmarks actually uses duplicate keys. I cooked up a benchmark with duplicate keys using the following generator for keys: keys = map ((sk*) . (`div` 3) . (*2)) (if sz < 0 then [2*sz `div` 3.. -sz `div` 3] else [0..sz]) In that case, avoiding the thunks actually results in a speedup, especially for larger map sizes:
where |
I keep wondering if we can do a bit better. In particular, I gather that |
Sorry about that; accidentally hit "close and comment" when I wasn't even finished writing my non-closing comment.... |
But also I realize my last substantive comment was all wrong..... Hrm.... Never mind. |
I suppose the trick from #653 (comment) is applicable here as well. |
While applicable, it doesn't seem to pay off here. Selected benchmarks... other sizes show a small slowdown as well: (the new versions are
The code is not pretty either: fromAscList2a :: [(Key,a)] -> IntMap a
fromAscList2a [] = Nil
fromAscList2a ((kx,vx) : zs1) = addAll' kx vx zs1
where
-- `addAll'` collects all keys equal to `kx` into a single value,
-- and then proceeds with `addAll`.
addAll' !kx vx [] = Tip kx vx
addAll' !kx vx ((ky,vy) : zs)
| kx == ky
= addAll' ky vy zs
| m <- branchMask ky kx
= case addMany' m ky vy zs of
InsertedNil ty -> link ky ty kx (Tip kx vx)
InsertedCons ty kz vz zs' -> addAll kx (link ky ty kx (Tip kx vx)) kz vz zs'
-- for `addAll` and `addMany`, kx is /a/ key inside the tree `tx`
-- `addAll` consumes the rest of the list, adding to the tree `tx`
addAll !kx !tx !ky vy zs
| m <- branchMask ky kx
= case addMany' m ky vy zs of
InsertedNil ty -> link ky ty kx tx
InsertedCons ty kz vz zs' -> addAll kx (link ky ty kx tx) kz vz zs'
-- `addMany'` is similar to `addAll'`, but proceeds with `addMany'`.
addMany' !m !kx vx [] = InsertedNil (Tip kx vx)
addMany' !m !kx vx ((ky,vy) : zs)
| kx == ky
= addMany' m ky vy zs
| mask kx m /= mask ky m
= InsertedCons (Tip kx vx) ky vy zs
| otherwise
= case addMany' (branchMask ky kx) ky vy zs of
InsertedNil ty -> InsertedNil (link ky ty kx (Tip kx vx))
InsertedCons ty kz vz zs' -> addMany m kx (link ky ty kx (Tip kx vx)) kz vz zs'
-- `addAll` adds to `tx` all keys whose prefix w.r.t. `m` agrees with `kx`.
addMany !m !kx tx !ky vy zs
| mask kx m /= mask ky m
= InsertedCons tx ky vy zs
| otherwise
= case addMany' (branchMask ky kx) ky vy zs of
InsertedNil ty -> InsertedNil (link ky ty kx tx)
InsertedCons ty kz vz zs' -> addMany m kx (link ky ty kx tx) kz vz zs'
data Inserted' a
= InsertedNil !(IntMap a)
| InsertedCons !(IntMap a) !Key a ![(Key,a)] |
Even if there's no reliably detectable advantage to the CSEed version, it should at least produce slightly smaller code, which is better. I'm a bit mystified about why the fancy |
Last action for tonight... I've pushed the CSE changes. (I'm making separate patches for each data structure because in the end I want to squash everything into the first three commits that I pushed.) I'm also mystified by what exactly the benefits and drawbacks of the |
Yeah, the semantics are weird all right. But I think that needs to be a
separate discussion.
…On Mon, Jul 8, 2019, 1:11 PM Bertram Felgenhauer ***@***.***> wrote:
***@***.**** commented on this pull request.
------------------------------
In containers/src/Data/IntMap/Strict/Internal.hs
<#658 (comment)>:
> + | m <- branchMask kx ky
+ , Inserted ty zs' <- addMany' m ky vy zs
+ = addAll kx (linkWithMask m ky ty kx (Tip kx vx)) zs'
+
+ -- for `addAll` and `addMany`, kx is /a/ key inside the tree `tx`
+ -- `addAll` consumes the rest of the list, adding to the tree `tx`
+ addAll !kx !tx []
+ = tx
+ addAll !kx !tx ((ky,!vy) : zs)
+ | m <- branchMask kx ky
+ , Inserted ty zs' <- addMany' m ky vy zs
+ = addAll kx (linkWithMask m ky ty kx tx) zs'
+
+ -- `addMany'` is similar to `addAll'`, but proceeds with `addMany'`.
+ addMany' !m !kx vx [] = Inserted (Tip kx vx) []
+ addMany' !m !kx vx zs0@((ky,!vy) : zs)
Good point. I was working under the assumption that all values are
supposed to be forced, but indeed we shouldn't make the function more
strict than it currently is without a very good reason.
For your amusement (the results may look a bit surprising, but given that
fromList{,With} works by inserting values into the map as it goes, the
bottoms are unavoidable:
(using containers-0.6.2.1)
Prelude Data.IntMap.Strict> fromList [(1,undefined),(1,2)] `seq` ()
*** Exception: Prelude.undefined
Prelude Data.IntMap.Strict> fromAscList [(1,undefined),(1,2)] `seq` ()
()
Prelude Data.IntMap.Strict> fromListWith (\_ _ -> 2) [(1,undefined),(1,1)] `seq` ()
*** Exception: Prelude.undefined
Prelude Data.IntMap.Strict> fromListWith (\_ _ -> 2) [(1,1),(1,undefined)] `seq` ()
()
Prelude Data.IntMap.Strict> fromAscListWith (\_ _ -> 2) [(1,undefined),(1,1)] `seq` ()
()
Prelude Data.IntMap.Strict> fromAscListWith (\_ _ -> 2) [(1,1),(1,undefined)] `seq` ()
()
—
You are receiving this because you modified the open/close state.
Reply to this email directly, view it on GitHub
<#658?email_source=notifications&email_token=AAOOF7MNNBO7XN5MNTQWWULP6NYNNA5CNFSM4H6UCBJ2YY3PNVWWK3TUL52HS4DFWFIHK3DMKJSXC5LFON2FEZLWNFSXPKTDN5WW2ZLOORPWSZGOB5YKUCA#discussion_r301206199>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAOOF7J22G2XW7QZXEZ7SU3P6NYNNANCNFSM4H6UCBJQ>
.
|
Do you consider this ready to merge? If so, could you squash whatever commits you want? |
benchmark summary: old: fromList mean 86.33 μs ( +- 557.8 ns ) fromAscList mean 42.01 μs ( +- 154.4 ns ) fromDistinctAscList mean 15.74 μs ( +- 90.03 ns ) new: fromList mean 83.00 μs ( +- 1.147 μs ) fromAscList mean 14.40 μs ( +- 367.6 ns ) fromDistinctAscList mean 14.56 μs ( +- 486.9 ns )
I think it's ready to go. |
One more request: could you use a custom 2-value type instead of |
benchmark summary: old: fromList mean 244.4 μs ( +- 7.088 μs ) fromAscList mean 178.6 μs ( +- 1.211 μs ) fromDistinctAscList mean 105.5 μs ( +- 1.048 μs ) new: fromList mean 225.2 μs ( +- 2.345 μs ) fromAscList mean 84.71 μs ( +- 1.011 μs ) fromDistinctAscList mean 84.20 μs ( +- 945.0 ns )
- no benchmarks, but the code is analogous to Data.IntMap.from*AscList*
Done. I struggled with the naming for a bit; in the end I went with |
It's not the best, but we can fix it later. At least it says something! |
Many thanks! |
The relevant ticket is #654.
I have some supporting material, including more benchmarks, here:
https://gist.github.com/int-e/36578cb04d0a187252c366b0b45ddcb6