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

Bucket enhancements to allow property-only update #2812

Closed
wants to merge 22 commits into from

Conversation

anandthakker
Copy link
Contributor

@lucaswoj I've been experimenting a bit with the "join new properties to existing geometries" problem. As you've suggested when we've discussed this, a couple of key enhancements to Bucket will be needed:

  • Track a mapping from feature index to vertex array index range -- this would allow a source to know which parts of the paint-property-related vertex arrays to update for which features.
  • Accept optional end indexes in populatePaintArrays -- currently, this method only takes a starting index and assumes that it should update from that index to the end of the current arrays, which makes it only suitable for populating the arrays the first time. Adding an end index parameter would make it straightforward to make updates to an already-populated array.

This PR includes draft versions of those changes, along with:

  • A proof of concept updateProperties method, which I stuck onto WorkerTile just for demo/prototyping purposes (but which should in reality probably go into a custom source type that encapsulates the data-joining/updating)
  • A pair of benchmarks -- dds_parse_bucket and dds_update_bucket -- to get a quick-and-dirty estimate of the perf gains from just updating versus a full parse. Results are pretty good at time of posting this = approx 43% gain:

screen shot 2016-07-01 at 10 52 31 am

screen shot 2016-07-01 at 10 53 08 am

var group = groups[g];
var length = group.layout.vertex.length;
var vertexArray = group.paint[layer.id];
vertexArray.resize(length);

var start = g === startGroupIndex ? startVertexIndex : 0;
var end = (endVertexIndex && g === endGroupIndex) ? endVertexIndex : length - 1;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

With this method now accepting ending indexes, maybe it would be simpler require the end index params and move the responsibility for choosing group.layout.vertex.length - 1 out to the callers?

Copy link
Contributor

Choose a reason for hiding this comment

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

That makes sense 👍

@mtirwin
Copy link
Contributor

mtirwin commented Jul 4, 2016

👀

@mourner
Copy link
Member

mourner commented Jul 5, 2016

Nice! What's up with the build failure — needs test updates as well?

@anandthakker
Copy link
Contributor Author

@mourner Yep, tests needed small tweak. Just fixed in a556c97 along with the change to populatePaintArrays mentioned above

@anandthakker
Copy link
Contributor Author

Ah, looks like some rendering tests are still broken. Will check those out soon.

@anandthakker
Copy link
Contributor Author

@lucaswoj @mourner ^ the last few commits clean things up as follows:

  • Accept an "array range" object ({startGroup, startVertex, endGroup, endVertex}) rather than individual indexes in populatePaintArrays.
  • Rename _featureIndexToArrayIndex => featureIndexToArrayRange; updateBuffers => updatePaintArrays
  • Contain _featureIndexToArrayRange wholly within bucket.js, by moving the responsibility for updating it into populatePaintArrays, because that seems like the most natural common code path for various bucket types to add paint data for a single feature.
  • Fix tests, and add a simple test for updatePaintArrays

@anandthakker
Copy link
Contributor Author

Removing "WIP" from the title now -- let me know if you think the changes in *bucket*.js look okay, and if so I will revert the temporary changes to worker_tile to make it mergeable

@anandthakker anandthakker changed the title WIP - Bucket enhancements to allow property-only update Bucket enhancements to allow property-only update Jul 7, 2016
// source-layer-name: [{ /* feature 0 properties */}, {/* feature 1 properties */}, ...],
// another-layer-name: [...]
// }
// where feature 0, 1, 2 are the features, in order, in this vector tile.
Copy link
Member

Choose a reason for hiding this comment

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

Should this comment be reformatted into JSDoc like rest of the code, while moving the note to a ticket or GH comment? It looks a bit like a TODO.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah @mourner sorry I wasn't clear in the comment--my intention was actually to revert this change entirely, excluding it from the PR and keeping the changes scoped to the Bucket classes. That's what I meant by "this might make more sense as part of a custom source type".

I guess a downside to that is that the dds_update_bucket benchmark would also have to go.

If yall think it's worth keeping this here, though, I'd be happy to clean it up (including the comment, as you said), and also to add some tests for it.

@anandthakker
Copy link
Contributor Author

In preparation for a rebase, preserving some inline comments:

@mourner commented about fixing/removing the TODO-like comment above WorkerTile#updateProperties, and I responded:

sorry I wasn't clear in the comment--my intention was actually to revert this change entirely, excluding it from the PR and keeping the changes scoped to the Bucket classes. That's what I meant by "this might make more sense as part of a custom source type".

I guess a downside to that is that the dds_update_bucket benchmark would also have to go.

If yall think it's worth keeping this here, though, I'd be happy to clean it up (including the comment, as you said), and also to add some tests for it.

cc @lucaswoj for you to weigh in

@anandthakker anandthakker force-pushed the bucket-feature-vertex-indexes branch 2 times, most recently from 6bb6b8d to 0002bb3 Compare July 9, 2016 03:08
@anandthakker
Copy link
Contributor Author

Rebased (onto arrays-and-buffers branch, NOT master, so this PR is downstream of #2837 ). Previously branch was at 1f04efc

In other news, I guess rebasing doesn't mean losing the inline diff comments -- how nice!

@anandthakker
Copy link
Contributor Author

I'm finding that there are some problems here relating to the arrays getting "transferred" from the worker back to the main thread after the initial load/parse:

Transferring apparently leaves the array "neutered" in the worker thread, so that it can't be updated. Moreover, when the bucket is reconstructed on the main thread, the transferred/serialized arrayGroups is currently not being unserialized and reinstated -- rather, it's only used to create bufferGroups.

@mourner
Copy link
Member

mourner commented Jul 10, 2016

Transferring apparently leaves the array "neutered" in the worker thread

Yeah, that's the point of transferring — moving data fast between threads without copy, but at the expense of not being able to access it after it was moved.

@anandthakker
Copy link
Contributor Author

Yeah, makes sense. I'm thinking I'll play with either (a) transferring the
arraybuffers back to the worker for the property update task or (b) doing
the property update on the main thread.

I think either of these will require reconstructing the "arrayGroups"
object somehow -- for (a) I think I would have to replace the neutered
array buffers w the ones that I transferred back; for (b) I'd need to
create arrayGroups from the serialized arrays, similar to how
"arrayBuffers" is created, so that populatePaintArrays can be used.

On Sun, Jul 10, 2016 at 3:07 AM Vladimir Agafonkin [email protected]
wrote:

Transferring apparently leaves the array "neutered" in the worker thread

Yeah, that's the point of transferring — moving data fast between threads
without copy, but at the expense of not being able to access it after it
was moved.


You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
#2812 (comment),
or mute the thread
https://github.com/notifications/unsubscribe/AEvmRwtpN1qCtqCEgXikJ_wQ6w-OPaO-ks5qUJosgaJpZM4JDP-w
.

@anandthakker anandthakker force-pushed the bucket-feature-vertex-indexes branch from 0002bb3 to 239ef7a Compare July 14, 2016 02:31
@anandthakker
Copy link
Contributor Author

I went with option (a), implemented in 5cfc637, and it works! Demo:

dynamic-data

Those points are coming from a vector tile source; update() is hardcoded to send in random values for the dds property that's controlling circle-radius. (Code here -- relies on both this PR and #2667)


Bucket.prototype.populatePaintArrays = function(interfaceName, globalProperties, featureProperties, arrayRange, featureIndex) {
if (typeof featureIndex !== 'undefined') {
this._featureIndexToArrayRange[featureIndex] = arrayRange;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

It occurs to me that this map should actually be this._featureIndexToArrayRange[interfaceName][featureIndex]

@anandthakker
Copy link
Contributor Author

@lucaswoj Soon I will rebase to bring this up to date with the refactor in #2865; before I do, would love to get your 👍 / 👎 on each of the following changes that are currently included here:

  1. Updates populatePaintArrays to (a) require both starting and ending indexes (in a combined arrayRange param), and (b) if a feature index is provided, update the bucket's feature-index-to-array-range mapping.
  2. Add Bucket#updatePaintArrays which, given an array of feature properties, uses the feature-index-to-array-range map to correctly call populatePaintArrays for each of the new properties objects
  3. Add Bucket#setArrayGroupData to allow a worker-side bucket to update its ArrayBuffer references after they've been transferred to the main thread (and thus neutered) and then transferred back.
  4. Add WorkerTile#updateProperties, supported by items 1-3 above.

Number 4 raises a broader question: originally, I only included it so that yall could check out the benchmarks and such, assuming that in the end we would remove it and leave that logic as the responsibility of the dynamic-property-updating custom source implementation. But this does require that custom to reach pretty far into the GL JS's implementation details. Is this okay? Would it be better to perhaps include WorkerTile#updateProperties as part of the public api? Or even to include the dynamic-updating source as a core source type?

@lucaswoj
Copy link
Contributor

  1. Updates populatePaintArrays to (a) require both starting and ending indexes (in a combined arrayRange param), and (b) if a feature index is provided, update the bucket's feature-index-to-array-range mapping.

Sounds good 👍

  1. Add Bucket#updatePaintArrays which, given an array of feature properties, uses the feature-index-to-array-range map to correctly call populatePaintArrays for each of the new properties objects

Sounds good 👍

  1. Add Bucket#setArrayGroupData to allow a worker-side bucket to update its ArrayBuffer references after they've been transferred to the main thread (and thus neutered) and then transferred back.
/**
+ * Sets the underlying `arrayBuffer` instances for this bucket's array groups.
+ * When the arrayBuffers are transferred across threads with postMessage, the
+ * references to them on the sending thread become "neutered". This method
+ * allows for those arrayBuffers to be sent _back_ to the worker they came
+ * from.

This sounds potentially problematic. What happens if the main thread tries to render the bucket while its arrays are "missing"?

Is it possible to create a fresh paint array with the new data without transferring anything from the main thread back to the worker? I can't think of any reason why we'd need to read from the original paint arrays or why we have to use the same ArrayBuffer instance for the new paint array.

  1. Add WorkerTile#updateProperties, supported by items 1-3 above.

Number 4 raises a broader question: originally, I only included it so that yall could check out the benchmarks and such, assuming that in the end we would remove it and leave that logic as the responsibility of the dynamic-property-updating custom source implementation. But this does require that custom to reach pretty far into the GL JS's implementation details. Is this okay? Would it be better to perhaps include WorkerTile#updateProperties as part of the public api? Or even to include the dynamic-updating source as a core source type?

I would prefer to refrain from making this a public API. You may add this code and use it for your vector tile property updater but we do not plan to provide support for or make any external promises about the stability of this API.

@anandthakker
Copy link
Contributor Author

Thanks @lucaswoj !

This sounds potentially problematic. What happens if the main thread tries to render the bucket while its arrays are "missing"?

Doh! I hadn't thought of this.

Is it possible to create a fresh paint array with the new data without transferring anything from the main thread back to the worker?

Yeah, it does seem like this should be possible. Since we'd be talking about creating new paint arrays but not the corresponding "layout" arrays, doing this might involve some tweaks in a couple places, but pretty minor, I think.

I would prefer to refrain from making this a public API. You may add this code and use it for your vector tile property updater but we do not plan to provide support for or make any external promises about the stability of this API.

👍 this makes sense and sounds reasonable to me.

@anandthakker
Copy link
Contributor Author

anandthakker commented Jul 27, 2016

Rebased (previous: 5cfc637)

Is it possible to create a fresh paint array with the new data without transferring anything from the main thread back to the worker? I can't think of any reason why we'd need to read from the original paint arrays or why we have to use the same ArrayBuffer instance for the new paint array.

Also updated to reflect this suggestion, removing Bucket#setArrayGroupData in favor of Bucket#updatePaintBufferData, which, on the main-thread side, allows for updating with newly-created paint arrays.

Next steps:

  • Add unit tests to cover new methods in Bucket, BufferGroup, and WorkerTile
  • Fix benchmarks

@anandthakker
Copy link
Contributor Author

Add unit tests to cover new methods in Bucket, BufferGroup, and WorkerTile

The tests now in place for Bucket#updatePaintVertexArrays, Bucket#updatePaintVertexBuffers, and WorkerTile#updateProperties should have this covered.

@anandthakker anandthakker force-pushed the bucket-feature-vertex-indexes branch from 927c8d9 to a8fffd9 Compare September 2, 2016 12:49
@anandthakker
Copy link
Contributor Author

Rebasing to keep up with master. Previous head is 927c8d9

Branch

load-multiple-maps: 250 ms, loaded 6 maps.
buffer: 875 ms
dds-parse-bucket: 2,155 ms
dds-update-bucket: 1,203 ms
fps: 60 fps
frame-duration: 8.2 ms, 0% > 16ms
query-point: 0.77 ms
query-box: 52.55 ms
geojson-setdata-small: 10 ms
geojson-setdata-large: 102 ms

Master

load-multiple-maps: 864 ms, loaded 6 maps.
buffer: 874 ms
fps: 60 fps
frame-duration: 8.1 ms, 0% > 16ms
query-point: 0.76 ms
query-box: 51.31 ms
geojson-setdata-small: 11 ms
geojson-setdata-large: 104 ms

anandthakker pushed a commit to developmentseed/mapbox-gl-js that referenced this pull request Sep 2, 2016
Bucket enhancements to allow property-only update
@AndyMoreland
Copy link
Contributor

Hey folks,

I spoke with @ryanbaumann at my office a few days ago regarding some performance issues that I'm having with using mapbox-gl-js. He pointed me at this PR as a starting place for trying to improve hover / selection performance on maps with large numbers of geojson elements (40k is a reasonable target).

I've been doing some work based off of this PR, but it doesn't rebase cleanly against 0.24 anymore. I totally understand that this exact code probably won't be what gets merged into the mainline eventually, but is this a total fools errand? I'm primarily concerned about getting stuck permanently on my 0.22 fork if this gets abandoned.

Andy

@lucaswoj
Copy link
Contributor

I cannot speak to @anandthakker's plans to maintain this branch.

We have plans to enable functionality like the functionality implemented in this pull request through custom source and layer types (#281 #2982 #2982). I suspect the final form of this feature will involve simplification of GL JS's architecture and emphasize object composition as the preferred way to create custom sources.

If you are looking for a way to improve the performance of hovering and selection, I recommend looking at #2874, other Map#setFilter performance improvements, and Map#queryRenderedFeatures performance improvements.

@anandthakker
Copy link
Contributor Author

I cannot speak to @anandthakker's plans to maintain this branch.

Based on the most recent discussion in the PR here, I don't expect that this branch is going to make it into master. The work here was motivated by a project we're working on that requires joining changing property data with geometries provided by vector tiles. As discussed here, this is actually already possible via the 'custom source' interface, although that interface is likely to change as part of the architecture simplifications that @lucaswoj mentioned, and so isn't yet publicly documented.

@AndyMoreland I'll be open sourcing our approach to this, but it's going to take a few weeks due to constraints beyond my control.

@AndyMoreland
Copy link
Contributor

Thanks for the update, both of you. It sounds like I should hold off for a bit on this until you get the new APIs standardized.

@ryanbaumann
Copy link
Contributor

Linking to the categorical data-driven style method for performing property updates, which may be useful until vector tile property updates are merged into master.

https://www.mapbox.com/mapbox-gl-js/example/data-join/

@lucaswoj
Copy link
Contributor

Closing this as "stale." We are working on a slightly different approach, allowing custom sources which adhere to a specific interface, here: https://github.com/mapbox/mapbox-gl-js/projects/2.

@lucaswoj lucaswoj closed this Jan 19, 2017
@jfirebaugh jfirebaugh deleted the bucket-feature-vertex-indexes branch February 3, 2017 18:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants