-
Notifications
You must be signed in to change notification settings - Fork 94
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
More performance optimizations #136
Comments
…and arg is large Set issue hamstergem#136
Great work, thanks! |
One more possible optimization: [ ] A quick benchmark shows that |
Similar one:
and maybe similar for |
@dubek, thanks for this suggestion. Please have a look at the latest |
@alexdowad Looks good. I think you should return Aside: Might be a matter of taste, but I think we would have enjoyed a way to add assertions or invariants to our code (checked during testing). So after each call, verify for example that |
@dubek, the value of Do you need assertions in the "middle" of methods? Or would checking assertions on method entry/exit be enough? If so, I can think of a way to do it with no performance impact in "production". (Write a class macro which "hooks" a method with pre/post assertions only when the tests are running.) |
PS. Just pushed a fix for the problem you noticed with |
Sorry, still buggy (unless I'm missing something) on 435e4ca :
|
OK, now I see why we need the |
I think that:
|
OK. You are absolutely right about that. |
One more possible optimization: For SortedSet it should be faster to determine The problem with this suggested optimization is that it is only valid if the two SortedSets have the same order (comparator). Anyway we can check that? |
Whoops, my last comment is regarding |
@dubek, right now there is no way to check if 2 |
We need C++ templates ;-) I found that |
Pushed to master: fix for the |
I just experimented with loosening the balance requirements for Retrospect: Since our AVL trees are immutable, each insertion means copying all the nodes to the root, at the very least. Allocating a couple more nodes here and there to rebalance the trees doesn't add much overhead on top of that. And keeping the trees in very strict balance means you don't have to traverse down as far for the next insertion. |
I've checked off the proposed optimization for |
While working on one of these optimizations, I just discovered and fixed 2 bugs in |
SortedSet
balance less frequently (or even moving to RB trees rather than AVL trees)SortedSet
sometimes copies more nodes than is necessary. Fix this.SortedSet#delete_at
traverses the tree twice. Add a method toAVLNode
so it just needs to traverse the tree once.SortedSet
is created with a 1-param block (to derive sort keys), should the sort keys for each node be cached? (In some cases, the computation to derive the sort key might be expensive.)Set#union
, check ifother
is aHamster::Set
which is larger thanself
. If so, flip aroundself
andother
. (It will be cheaper to build up the result using the larger trie.)Set#intersection
, ifother
is significantly smaller thanself
, but is not aHamster::Set
, then create an emptyTrie
, iterate throughother
, and add all the elements which are also inself
to the newTrie
.Set#subset?
andSet#proper_subset?
, ifother
is not a::Set
orHamster::Set
(i.e.#include?
may be slow), and ifother
is large, it may be faster to build a temporary::Set
out ofother
first, and use that for testing inclusion. (Don't bother using aHamster::Set
for this purpose, it will be slower.) Another option would be to iterate throughother
, count the number of elements which are inself
, and see if the number is equal toself.size
.Set#disjoint?
: ifself.size < other.size
butother
is not a::Set
orHamster::Set
, testingother.include?(item)
may be slow (and may lead to O(n^2) run time). In this case, it may be better to build a temporary::Set
for testing inclusion, or just use the 2nd branch so the inclusion test is done onself
, notother
.Trie
, add methods for bulk insertion and deletion (which copy less nodes than repeated adding/deletion of single elements). Use these for operations likeSet#union
and so on.Vector
of using an implementation strategy more like thePersistent::Vector
library. That is, allVector
tries would always be composed of full, 32-item nodes. Then, eachVector
would have a "tail" array of 0-31 additional items at the end.Vector#push
would only have to modify the trie once for every 32 pushes; the rest of the time, it would just be copying the "tail" array. Likewise forVector#pop
. If this proves worthwhile, consider having both "head" and "tail" arrays for odd numbers of items at the front and back. This could also makeVector#shift
and#unshift
fast. Retrieving an item from aVector
by index would require checking whether it falls within the "head", the "tail", or the vector trie itself.Vector
s a "head" and "tail" proves to be a worthwhile and successful strategy, compare its performance as a stack/queue withDeque
. IfVector
can be made almost as efficient asDeque
for that purpose, dropDeque
and allowVector
to replace it.Deque
is necessary, try to mitigate the nasty worst-case scenario which happens when a largeDeque
is built up, and then items are alternately popped from the front and back. (It will keep reversing the list, and reversing it back again.) If, say, we are reversing@rear
and moving the items to@front
, I would suggest: copy the first 1/3 of the items and keep them on@rear
. Reverse the remaining 2/3 and put them on@front
.#first
or#last
item from aDeque
with all the items in a long@front
or@rear
. Perhaps eachDeque
can keep the last item in@front
and@rear
in instance variables, so#first
and#last
can always be returned quickly.The text was updated successfully, but these errors were encountered: