-
Notifications
You must be signed in to change notification settings - Fork 8
Collision Performance #4
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
base: master
Are you sure you want to change the base?
Changes from all commits
6c55139
eb29aff
27cadfd
e2ef905
062dc68
8103909
2424b2b
b694d93
f634c2c
9275399
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,6 +21,7 @@ | |
| import java.io.IOException; | ||
| import java.io.PrintWriter; | ||
| import java.net.URL; | ||
| import java.util.ArrayList; | ||
| import java.util.HashMap; | ||
| import java.util.HashSet; | ||
| import java.util.LinkedList; | ||
|
|
@@ -2821,18 +2822,38 @@ private boolean overlap() { | |
| }*/ | ||
|
|
||
|
|
||
| // Set is O(n log(n)) to traverse over and doesn't benefit from cache locality. | ||
| // we're doing a lot of iterating over the same collection here, | ||
| // so it makes sense to use a temporary array for cache locality and O(n) traversal. | ||
| Element[] selectedArr = selected.toArray(Element[]::new); | ||
| ArrayList<Element> elementsArr = new ArrayList(circuit.getElements()); | ||
| // elementsArr.removeAll(selected); | ||
| { | ||
| int i = 0; | ||
| while (i < elementsArr.size()) { | ||
| if (selected.contains(elementsArr.get(i))) { | ||
| // "swap" remove - slightly cheaper because we don't care about order | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you're very worried about performance for this operation, a linked list has better cache locality.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm a bit confused how that can be true... An array has all of its elements next to each other in memory, so the entire surrounding chunk of memory can be pulled into memory at once and contain those adjacent elements. A linked list has pointers to adjacent elements, meaning it couldn't be cached without dereferencing those pointers--which in addition to being extra operations (and slow ones at that: loading data from RAM) may be unsafe to do speculatively since the CPU can't be certain those pointers belong to the program, or are even valid memory addresses. Am I incorrect?
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You'd be almost right if this were C. Less so for Java. In a traditional linked list and doubly linked list, each node has one to three pointers. One is for the next node, optionally for the previous node, and the value is either embedded or has a pointer to it. However, in optimized implementations these nodes are kept densely packed in an array or something more exotic. So, you're comparing something called a long jump to overcome 2+GB changes in virtual address or physical address (we're skipping the kernel considerations for now) or if correctly optimized in some circumstances a standard jump. Those jumps are, practically, cache-busters. Intel has some fancy stuff with their pre-fetcher, but relying on that from a programmer's perspective is faulty at best, meaning that you count on missing L1, L2, L3 (and sometimes L4) cache, hitting RAM, and killing the performance. Now, when working with swapping to the end of a dense array, you're busting this cache on every swap just by virtue of touching the end of the array while you were just working on the beginning. However, with Java's presumably optimized implementation, these removals are always touching bytes which are within one or two words away. This means that the cache characteristics are great. So even if you're performing 5X the operations, the fact this the CPU assumes that you're working with memory right next to each other allows for simpler algorithms to pre-fetch that memory and avoids the cache misses in the first place. And this is without the extremely optimized data structures which are actually probably being used which are going to have better characteristics. For these reasons, trust the standard library. Neither of us are going to out-smart it. Once you hit system organization, operating systems, and compilers you'll formally be introduced to these topics if your profs are good. One tip for compilers -- learn how to use stack machines. They aren't always covered but they allow you to entirely skip register management and it makes your life immensely easier.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see! I had completely not realized that accessing the end would be what causes the slowdown (and I had NO idea about any of those linked list optimizations), but it makes perfect sense after you explained it! I'll try benchmarking this and an alternative linked list implementation and keep whichever is faster. However, I'm not entirely sure how to benchmark this code without its speed being influenced by the speed of my own movements. As far as I can tell, there isn't a good way of isolating the overlap code and running it with a variety specific circuits--I would have to trigger the overlap method manually. Do you have any suggestions? |
||
| elementsArr.set(i, elementsArr.get(elementsArr.size() - 1)); | ||
| elementsArr.remove(elementsArr.size() - 1); // pop | ||
| // don't increment on erase because [i] now refers to a new element | ||
| } | ||
| else { | ||
| i++; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // check every element in the selected set | ||
| for (Element sel : selected) { | ||
| for (Element sel : selectedArr) { | ||
|
|
||
| // check against every element in the circuit | ||
| for (Element el : circuit.getElements()) { | ||
| boolean anyIntersection = false; | ||
|
|
||
| // ignore elements in the selected set | ||
| if (selected.contains(el)) | ||
| continue; | ||
| // check against every (unselected, bounds-overlapping) element in the circuit | ||
| for (Element el : elementsArr) { | ||
|
|
||
| // check simple overlap of areas | ||
| if (sel.intersects(el)) { | ||
| anyIntersection = true; | ||
|
|
||
| // no overlap if possible connection, | ||
| boolean ok = false; | ||
|
|
@@ -2910,42 +2931,42 @@ else if (el instanceof Wire) { | |
| // selected is not a wire end | ||
| else { | ||
|
|
||
| // put to wire end | ||
| for (Put put : sel.getAllPuts()) { | ||
|
|
||
| // if not a wire end, ignore | ||
| if (!(el instanceof WireEnd)) | ||
| continue; | ||
| // if element is a wire end, ... | ||
| if (el instanceof WireEnd) { | ||
| WireEnd end = (WireEnd)el; | ||
|
|
||
| // if don't line up, ignore | ||
| if (put.getX() != end.getX() || put.getY() != end.getY()) { | ||
| continue; | ||
| } | ||
| // put to wire end | ||
| for (Put put : sel.getAllPuts()) { | ||
|
|
||
| // if already attached to this wire end, ignore | ||
| if (end == put.getWireEnd()) { | ||
| ok = true; | ||
| continue; | ||
| } | ||
| // if don't line up, ignore | ||
| if (put.getX() != end.getX() || put.getY() != end.getY()) { | ||
| continue; | ||
| } | ||
|
|
||
| // if attached through a single wire, ignore | ||
| WireEnd putEnd = put.getWireEnd(); | ||
| if (putEnd != null && | ||
| putEnd.getOnlyWire().getOtherEnd(putEnd) == end) { | ||
| ok = true; | ||
| continue; | ||
| } | ||
| // if already attached to this wire end, ignore | ||
| if (end == put.getWireEnd()) { | ||
| ok = true; | ||
| continue; | ||
| } | ||
|
|
||
| // if cannot connect, return | ||
| if (!canConnect(end,put)) { | ||
| untouchAll(); | ||
| return true; | ||
| } | ||
| // if attached through a single wire, ignore | ||
| WireEnd putEnd = put.getWireEnd(); | ||
| if (putEnd != null && | ||
| putEnd.getOnlyWire().getOtherEnd(putEnd) == end) { | ||
| ok = true; | ||
| continue; | ||
| } | ||
|
|
||
| end.setTouching(true); | ||
| put.setTouching(true); | ||
| ok = true; | ||
| // if cannot connect, return | ||
| if (!canConnect(end,put)) { | ||
| untouchAll(); | ||
| return true; | ||
| } | ||
|
|
||
| end.setTouching(true); | ||
| put.setTouching(true); | ||
| ok = true; | ||
| } | ||
| } | ||
| } | ||
| if (!ok) { | ||
|
|
@@ -2954,52 +2975,50 @@ else if (el instanceof Wire) { | |
| return true; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // no intersection, but wires may be overlapping wire ends | ||
| // or puts might line up | ||
| else { | ||
| // no intersection, but wires may be overlapping wire ends | ||
| // or puts might line up | ||
| if (!anyIntersection) { | ||
|
|
||
| // see if wires connected to a wire end dragged onto wire ends | ||
| if (sel instanceof WireEnd) { | ||
| WireEnd end = (WireEnd)sel; | ||
| for (Wire wire : end.getWires()) { | ||
| for (Element elm : circuit.getElements()) { | ||
| if (sel == elm) | ||
| continue; | ||
| if (!(elm instanceof WireEnd)) { | ||
| continue; | ||
| } | ||
| WireEnd otherEnd = (WireEnd)elm; | ||
| if (wire.touches(otherEnd)) { | ||
| overlapMessage = "overlap"; | ||
| untouchAll(); | ||
| return true; | ||
| } | ||
| // see if wires connected to a wire end dragged onto wire ends | ||
| if (sel instanceof WireEnd) { | ||
| WireEnd end = (WireEnd)sel; | ||
| for (Wire wire : end.getWires()) { | ||
| for (Element el : elementsArr) { | ||
| if (!(el instanceof WireEnd)) { | ||
| continue; | ||
| } | ||
| WireEnd otherEnd = (WireEnd)el; | ||
| if (wire.touches(otherEnd)) { | ||
| overlapMessage = "overlap"; | ||
| untouchAll(); | ||
| return true; | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // see if wires connected to puts dragged onto wire ends | ||
| for (Put p : sel.getAllPuts()) { | ||
| if (p.isAttached()) { | ||
| Wire wire = p.getWireEnd().getOnlyWire(); | ||
| for (Element elm : circuit.getElements()) { | ||
| if (sel == elm) | ||
| continue; | ||
| if (!(elm instanceof WireEnd)) { | ||
| continue; | ||
| } | ||
| WireEnd otherEnd = (WireEnd)elm; | ||
| if (wire.touches(otherEnd)) { | ||
| overlapMessage = "overlap"; | ||
| untouchAll(); | ||
| return true; | ||
| } | ||
| // see if wires connected to puts dragged onto wire ends | ||
| for (Put p : sel.getAllPuts()) { | ||
| if (p.isAttached()) { | ||
| Wire wire = p.getWireEnd().getOnlyWire(); | ||
| for (Element el : elementsArr) { | ||
| if (!(el instanceof WireEnd)) { | ||
| continue; | ||
| } | ||
| WireEnd otherEnd = (WireEnd)el; | ||
| if (wire.touches(otherEnd)) { | ||
| overlapMessage = "overlap"; | ||
| untouchAll(); | ||
| return true; | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // check all put combinations | ||
| // check all put combinations | ||
| for (Element el : elementsArr) { | ||
| for (Put p1 : sel.getAllPuts()) { | ||
| for (Put p2 : el.getAllPuts()) { | ||
|
|
||
|
|
@@ -3011,7 +3030,7 @@ else if (el instanceof Wire) { | |
| // ignore overlaps on already connected puts | ||
| WireEnd end1 = p1.getWireEnd(); | ||
| WireEnd end2 = p2.getWireEnd(); | ||
| if (end1 != null && end2 != null && | ||
| if (end1 != null && end2 != null && | ||
| end1.getOnlyWire().getOtherEnd(end1) == end2) { | ||
| continue; | ||
| } | ||
|
|
@@ -3030,8 +3049,8 @@ else if (el instanceof Wire) { | |
| } | ||
| } | ||
| } | ||
| repaint(); | ||
| return false; | ||
| repaint(); | ||
| return false; | ||
| } // end of overlap method | ||
|
|
||
| /** | ||
|
|
||
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.
These top level functions often have significant hidden optimizations to them. I'd benchmark before doing this and claiming this is the part that improves performance.