-
Notifications
You must be signed in to change notification settings - Fork 3.1k
add first element of empty queue to out list / return explicit empty queue #7873
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
Conversation
…ue if last element is dequeued
|
Thanks, I signed the CLA ! |
NthPortal
left a comment
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.
tail (the method/part of the usage pattern listed in the linked ticket) also needs to be changed for this, as well as a bunch of other methods.
| */ | ||
| def enqueue[B >: A](elem: B): Queue[B] = new Queue(elem :: in, out) | ||
| def enqueue[B >: A](elem: B): Queue[B] = | ||
| if (isEmpty) new Queue(Nil, out :+ elem) |
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.
We generally don't want to append things (:+) to Lists - it is very slow. In this case however, we actually know the whole Queue is empty, so it can be elem :: Nil.
| def dequeue: (A, Queue[A]) = out match { | ||
| case Nil if !in.isEmpty => val rev = in.reverse ; (rev.head, new Queue(Nil, rev.tail)) | ||
| case Nil if !in.isEmpty => in match { | ||
| case x :: Nil => (x, new Queue(Nil, Nil)) |
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.
I'm not sure how much this line adds. The cost of reversing a List with a single element is pretty low, and ideally doesn't happen very often (whereas this check has to be done for all reversals). Without benchmarks showing it's an improvement for near-empty Queues and not a significant cost for larger ones, I'm not convinced this check improves things.
Additionally, in order to fulfill the optimisation requested by the linked ticket, there should be a check for out having a single element, in which case in is eagerly reversed (slightly before it's needed, but presumably it's going to be removed eventually anyway - it's a queue after all).
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.
(@gi114 is new to the repo, so might need pointers on running benchmarks if we decide that's needed here)
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.
Thanks for your comments, I shall try to adjust the PR. And yes, this is my first ticket so let me know if I there is anything relevant I should do. Also, do you usually run tests suites or do you test your changes separately on a .gitignore file?
|
Hi, I updated my PR with some other changes as it is shown above Rationale behind changes is the following: The fist element added to an empty queue is added to 'out', as specified by the ticket, and 'out' should never be forced empty. Because of that, dequeuing elements involves 'in' eagerly reversing before the 'out' list is ever empty ( one step before, so when 'out' has only one element ). |
…ase of empty queue, the first iterable input is added to out. This is in line with previous commits on enqueue and dequeue methods
|
Fixed a test which was failing. Everything seems fine, the PR follows a specific logic. Let me know if agree or see anything wrong. Also, as am new, I wanted to ask you if you could point at how to run some code performance analysis. Thanks again |
NthPortal
left a comment
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.
This looks great and takes care of all the obvious methods.
However, I am concerned that there may be some other method that doesn't comply with the new invariant "if out is empty then in must also be empty." These implementations currently assume/require the invariant to hold, and if it does not, they will do the wrong thing.
It would certainly be a good idea to have someone run collection-laws on this; additionally, I will attempt to comb through all the other methods to check that they maintain the invariant.
I think it would also be good to add a comment somewhere (I'm not sure exactly where, tbh) stating the invariant that all methods must maintain.
if it's empty, in and out are both Nil Co-Authored-By: Nth <7505383+NthPortal@users.noreply.github.com>
|
Hi, Would you think this pull request is ready to be merged ? Thanks! |
|
Unfortunately, this cannot be merged until 2.13.0 is released, as it is not a blocker for the release. Once it is and we move on to 2.13.1 however, we will be able to look at this PR again |
|
Ok, I see. Thanks a lot for your clarification
Il giorno ven 31 mag 2019 alle ore 11:14 Nth <notifications@github.com> ha
scritto:
… Unfortunately, this cannot be merged until 2.13.0 is released, as it is
not a blocker for the release. Once it is and we move on to 2.13.1 however,
we will be able to look at this PR again
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#7873?email_source=notifications&email_token=AEGS2Z4BWIHQJINTHDA5XGTPYE6EPA5CNFSM4G6RQFW2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODWVP7ZA#issuecomment-497745892>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AEGS2Z2H4BZREFDX5POGX7TPYE6EPANCNFSM4G6RQFWQ>
.
|
|
@gi114 sorry for the wait, but we'll get there :) |
|
Any progress on this? Feel free to reschedule to 2.13.2 if it will take longer. |
|
I'll try to put it on my weekend TODO list |
|
Hi, let me know if there is anything I can add/change from a code perspective on this one. Would you be able to rerun the tests to see if anything is failing? Thanks and let me know! |
|
I'm so sorry that I can't get to this right now. if someone else would like to take over getting this over the finish line, that would be great |
|
Hi,
I will be happy to try. Would you let me know if there is any test I should
perform on this one?
Thanks
Giulia
Il giorno sab 28 set 2019 alle ore 04:41 NthPortal <notifications@github.com>
ha scritto:
… I'm so sorry that I can't get to this right now. if someone else would
like to take over getting this over the finish line, that would be great
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#7873?email_source=notifications&email_token=AEGS2Z4NBG6QTOQTBGOG7YDQL4KCDA5CNFSM4G6RQFW2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD72UCVA#issuecomment-536166740>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AEGS2ZYJLGMUOPB6WZKBCTDQL4KCDANCNFSM4G6RQFWQ>
.
|
| def enqueue[B >: A](elem: B): Queue[B] = new Queue(elem :: in, out) | ||
| def enqueue[B >: A](elem: B): Queue[B] = | ||
| if (isEmpty) new Queue(Nil, elem :: Nil) | ||
| else new Queue(elem :: in, out) |
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.
This seems like an easy win. I wonder if there is a good way to test it. Maybe a benchmark is the only test that really matters. If a test that just continually enqueues and dequeues a single element at a time doesn't get a bit faster, would anything?
| override def head: A = | ||
| if (out.nonEmpty) out.head | ||
| else if (in.nonEmpty) in.last | ||
| if (nonEmpty) out.head |
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.
I'm dubious, that is, I think this is dubious.
| */ | ||
| def dequeue: (A, Queue[A]) = out match { | ||
| case Nil if !in.isEmpty => val rev = in.reverse ; (rev.head, new Queue(Nil, rev.tail)) | ||
| case x :: Nil if in.nonEmpty => (x, new Queue(Nil, in.reverse)) |
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.
I'm not sure incurring the reverse eagerly is worth the goal of supporting dubious head/tail pattern described in the ticket. As an aside, there is ongoing conversation about whether to always prefer !isEmpty to nonEmpty for performance reasons (because cost is not zero).
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.
Queue is a LinearSeq so there is an expectation that head/tail should have decent performance.
I didn't look at the generated code but going by the List implementation I would definitely prefer to call isEmpty when the type is known (like it is in this case). The Scala optimizer should be able to inline the method so you don't even have to rely on HotSpot.
|
I don't know if encouraging the head/tail anti-pattern is desirable, but I'll comment about that on the ticket. I did spot a potential improvement at #8449 which I guess is desirable, but it is late here. I touched the junit test for this class there. Do you know the local customs to squash commits and I see the previous question whether it's easy to show that However, always putting the first element in |
|
Hi,
Would you point me at the resources you used for writing benchmarks and
other examples. It is a new topic for me but would be useful to see the
performance improvement, if any
Thanks
Il giorno mar 1 ott 2019 alle ore 03:12 som-snytt <notifications@github.com>
ha scritto:
… I don't know if encouraging the head/tail anti-pattern is desirable, but
I'll comment about that on the ticket.
I did spot a potential improvement at #8449
<#8449> which I guess is desirable,
but it is late here. I touched the junit test for this class there.
Do you know how to squash commits and run sbt junit/test?
I see the previous question whether it's easy to show that out.nonEmpty
always holds. (I prefer nonEmpty, of course.) I would suggest that if
it's not easy to show that much, then it's not worth any complexity.
However, always putting the first element in out seems good to me.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#7873?email_source=notifications&email_token=AEGS2Z3LFBD55LUZMZU52Y3QMLZ6FA5CNFSM4G6RQFW2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEAAHSCI#issuecomment-536901897>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AEGS2Z2AJGUUU4SSCB77CN3QMLZ6FANCNFSM4G6RQFWQ>
.
|
|
anyone else have thoughts on @som-snytt's comment about whether or not this is a desirable change? |
|
@gi114 See https://github.com/scala/scala/blob/2.13.x/test/benchmarks/README.md for benchmarking. There's already a number of benchmarks for other collections that would make a good starting point. One thing the readme doesn't mention (but really should) is that you need to turn on optimization for the standard library (e.g. |
|
This still looks like a worthwhile improvement to me. There were some changes in the 2.13.x implementation in the meantime so this would need to be rebased. In particular, the code for As long as There should still be some basic benchmarks to ensure we're not regressing in more normal use cases though. |
|
is anyone interested in picking this one up? |
|
I would like to, but I don't have the capacity right now. if no one picks it up in a month, I'll see if I have the capacity for it |
|
Closing for inactivity. Happy to reopen any time, by Nth or anyone else. |
…ue if last element is dequeued
This is response to the request of optimization found here: scala/bug#11396
"immutable.Queue's methods should be optimised so that when dequeueing elements, it does not leave out empty (unless in is also empty), and when enqueueing elements, it puts at least one element in out if both in and out are empty (although once you're checking, might as well put everything in out)."