-
-
Notifications
You must be signed in to change notification settings - Fork 757
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
Rewrite record updates to record constructors #3773
Rewrite record updates to record constructors #3773
Conversation
Love this! Improving this area would benefit everyone. Monomorphisation sounds like a good strategy to me. How about we go all the way and monomorphise it fully to a regular constructor call? That way there would be no runtime reflection at all, and the generate code size would possibly even go down. If we implement it by converting an update to a call during analysis we could get this one for free also: #745 The main thing we'd need to do is to ensure we don't sacrifice good error messages. |
Hi, thank you!! I went for this change instead of emitting constructor calls at the call site for these reasons:
I'm not sure fully replacing everything with contructor calls reduces code size; If we assume large enough records where only a few fields update every time (like e.g. a lustre component model), Type-checking them as if they where record constructor calls would probably be a nice way to implement #745 . |
There's no issue here, we frequently do this.
How can this be given the new function is a regular constructor call with some additional runtime reflection? We'd need to benchmark this further to identify the cause of the slowdown.
We would not remove it, we would just not use it. It's a 3 line function so there's no real cost to having it. |
b7c5e52
to
00aeeae
Compare
Hi again! I now change the AST during type inference to make the following transformation: let updated = Wibble(..old_record(), a: 5)
// -->
let updated = {
let _record = old_record()
Wibble(a: 5, b: _record.b, c: _record.c)
} This now also means that record updates are type-checked similarly to the I still kept most of the old This also implicitly fixes the same bug as #3795, since I have to keep track of which fields I have to add from the base record. I haven't added that to the changelog though or copied that test, because Gears was faster than me 😄 Right now, this doesn't detect use of that feature. This turned out to be quite hard, since I would have to somehow check that the old way of typechecking would have still succeeded; basically I would have to check that my Oh and also I think this is even faster and works for both targets now, I was just being too lazy in my benchmarks and did not eliminate the extra function call. |
I also fixed some typos I noticed along the way, I hope that's ok :) |
Ah cool this is ready sooner than I thought. If this get merged, I'll close my PR |
Let's leave it in this case. I don't want to sacrifice compilation performance for this.
Do you have benchmarks? It would be great to be able to boast about this! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you! I'm really excited for this one! I've left a few minor notes inline
59258b7
to
345ba12
Compare
Basically record updates are now always as fast as constructing new records (which means they are very fast), no matter what. The extra IIFE/block does not make a measurable difference. Javascript previously was absolutely terrible, and the benchmarks from my first post should give you a good idea. The runtime there seems to scale linearly with the number of fields defined in the record constructor. With the benchmark above, 10 fields take ~1 minute and 20 fields take ~2 minutes, even if the fields are just copied over. 20 fields with the new implementation take 6 seconds for me. Erlang was less bad because of their optimistic mutation optimisation, but all those nested
|
Wow! That's very surprising. I wonder why that is. I will review and merge this once the release is done! |
Ohh ok merge commits are not allowed, I just clicked through the Github UI because I had an ADHD moment at work ^^ I'll rebase again once 1.6 is released! |
…ation for custom type records
3074048
to
d8ab00e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Amazing! Thank you!!
Vastly improve performance for record updates on the JavaScript target, making them basically as fast as constructing a new record. The trade-off here is that every custom type class definition that can be used with the record update syntax doubles in size.
Benchmarks:
for these functions; every function is called 10000 times on a list containing random integers:
I noticed this in practice while implementing my string-width layout functions; those use a list.fold-like apparatus to loop through strings in a special way that would lead to lots of code duplication when inlined to recursive functions. limit for example has an internal state of 9 record fields, but only updates 2/3 each iteration. Removing the record updates improved performance on javascript by 70% in practice for this function, which I found was really surprising.