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

[Plinth] Optimize 'unionValue' #6590

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

effectfully
Copy link
Contributor

Addresses:

unionValue is inefficient, because unionWith is, because Map.union is, because of all the These extra work that it does. If unionValue is important enough to be considered as a builtin, it is important enough to be optimized in Plinth

from #5967.

That b -> f 0 b
These a b -> f a b
in Value (fmap (fmap unThese) combined)
unionWith f = coerce (Map.unionWith @CurrencySymbol (Map.unionWith @TokenName f))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zliu41 I don't really know much about Value stuff, is this actually correct?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that's right. In the original, whenever there isn't a collision, the input function f is applied with the respective value and a 0. I don't see that happening in this new version, unless I'm reading the code wrong.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, semantics is different. But isn't it correct now? It was quite weird before, I'd argue.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. It should be documented that it treats [] and [(token, 0)] as different Values (so does the old implementation, but in a slightly different way).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So what is the expected behaviour of unionWith? What does it mean that this version is correct?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, I by default expect the standard behavior, i.e. the one in containers.

On the other hand, maybe I'm wrong, maybe the old behavior of unionValue (-) is the correct one. But in that case we should introduce

unionWithDef :: forall k a. Eq k => a -> (a -> a -> a) -> Map k a -> Map k a -> Map k a

which feed the first argument into the second whenever a key is missing in one of the maps.

So yeah, I think you've convinced me that the old behavior was correct and my new one isn't. @zliu41 opinion?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The old behavior doesn't treat a token with zero quantity and a token that doesn't exist the same way either. e.g.,

unionWith (\_ _ -> 42) [] [] == []
unionWith (\_ _ -> 42) [(token, 0)], [] == [(token, 42)]

This doesn't look any more correct than the new behavior.

So what is the expected behaviour of unionWith? What does it mean that this version is correct?

I think we can document that the expected behavior is exactly how the new implementation behaves, and move on. I don't think this really makes a difference on anyone, since nobody is going to actually use something like \_ _ -> 42 as the combining function.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If possible, it's also good to add a test which demonstrates this corner case.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes some tests would be nice.

({cpu: 4700174302334
| mem: 4700000857208})
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

9% CPU speedup and 12% MEM speedup for such a low-hanging fruit.

({cpu: 3700153208683
| mem: 3700000726876})
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

11.5% CPU speedup and 15% MEM speedup.

({cpu: 4100167271084
| mem: 4100000808938})
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

13.5% CPU speedup and 16.5% MEM speedup.

({cpu: 4100161979547
| mem: 4100000772592})
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

15% CPU speedup and 18% MEM speedup.

Copy link
Member

@zliu41 zliu41 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good.

That b -> f 0 b
These a b -> f a b
in Value (fmap (fmap unThese) combined)
unionWith f = coerce (Map.unionWith @CurrencySymbol (Map.unionWith @TokenName f))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that's right. In the original, whenever there isn't a collision, the input function f is applied with the respective value and a 0. I don't see that happening in this new version, unless I'm reading the code wrong.

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.

3 participants