Skip to content

Conversation

@mosche
Copy link
Member

@mosche mosche commented Mar 24, 2022

Push field type based logic in RowWithGetters down into FieldValueGetters to remove any branching and allow for better inlining and switch to TreeMap for caching to minimize memory footprint. See benchmark results below.

To not modify the behavior of getValues(), related calls are delegated to the original getter as is.


Thank you for your contribution! Follow this checklist to help us incorporate your contribution quickly and easily:

  • Choose reviewer(s) and mention them in a comment (R: @username).
  • Format the pull request title like [BEAM-XXX] Fixes bug in ApproximateQuantiles, where you replace BEAM-XXX with the appropriate JIRA issue, if applicable. This will automatically link the pull request to the issue.
  • Update CHANGES.md with noteworthy changes.
  • If this contribution is large, please file an Apache Individual Contributor License Agreement.

See the Contributor Guide for more tips on how to make review process smoother.

To check the build health, please visit https://github.com/apache/beam/blob/master/.test-infra/BUILD_STATUS.md

GitHub Actions Tests Status (on master branch)

Build python source distribution and wheels
Python tests
Java tests

See CI.md for more information about GitHub Actions CI.

@github-actions github-actions bot added the java label Mar 24, 2022
@mosche
Copy link
Member Author

mosche commented Mar 24, 2022

R: @TheNeuralBit
R: @reuvenlax
R: @kennknowles

@reuvenlax
Copy link
Contributor

By default, getValue results in generated bytecode to fetch the value. Is this cache really more efficient than the generated bytecode?

@TheNeuralBit
Copy link
Member

By default, getValue results in generated bytecode to fetch the value. Is this cache really more efficient than the generated bytecode?

Maybe some microbenchmarks could help here

@mosche
Copy link
Member Author

mosche commented Mar 24, 2022

@reuvenlax certainly a valid question and I'm happy to discuss what's worth caching.

Though, the key point here is, that there's already a cache in place for array, iterable and map types. Processing for these is currently not ideal. For instance, looking at an array field the following steps happen on getValue:

  1. Invoke getter, the generated byte code wraps the array in a list
  2. Then lazily transform the list elements (Lists.transform) and cache the result

The problem is really on the 2nd access. Step 1) is repeated, but step 2) uses the cache discarding all the work of step 1).

This PR mostly addresses some of the inconsistencies using the existing cache and reduces the overhead of the cache by choosing better fitted data structures to store cached values.

@mosche
Copy link
Member Author

mosche commented Mar 24, 2022

Considering that elements of ARRAY types and ITERABLE types are lazily transformed, the value of caching is rather small (questionable?) as it happens on demand.

For MAP types it's a different story as the entire map gets rewritten on transform.

And if a cache is used, then any composite ROW type should be cached. Otherwise, if re-created every time, the cache is obviously pointless.

@mosche
Copy link
Member Author

mosche commented Mar 25, 2022

I started doing some jmh benchmarks for RowWithGetter. I'll follow up with next week on this...

@reuvenlax
Copy link
Contributor

Benchmarks of this code are tricky to do, since the cost of codegen tends to dominate microbenchmarks.

@mosche
Copy link
Member Author

mosche commented Mar 29, 2022

I've pushed my benchmark code for reference, let me know if you have any suggestions. To prevent the issue you've mentioned @reuvenlax, I'm setting up a relatively large number of rows as JMH state before the actual benchmark invocation.

To establish a baseline, I'm looking at master first. Here are some initial results with some minimal changes to RowWithGetters to make these benchmarks meaningful:

  • Disable caching for the first run.
  • Change initialisation of the cache data structure to lazy init so associated costs are considered in the benchmark.

These numbers are certainly not very much in favour of the status quo.
I'm still iterating on improvements, but from what I've seen so far far there's lots that can be done.

@mosche
Copy link
Member Author

mosche commented Apr 1, 2022

I've drilled down into this a bit and I think I've got some interesting finding's to share @reuvenlax & @TheNeuralBit.

Investigating a few approaches, I would to suggest to push field type based logic in RowWithGetters down into FieldValueGetters to remove any branching and allow for better inlining, see code & benchmark.

I also looked a bit into costs of caching, the picture isn't as clear there. The costs of initializing any data structure facilitating a cache is certainly high compared to the costs of calling getters. One finding though was that TreeMap didn't perform any worse than HashMap. Given the much lower memory footprint that might be a good pick then. Also, using materialized Pojo lists helped to improve the performance gain from caching (compared to lazy transforms using Lists.transform).

On the other hand, I'm not sure what the original motivation for adding a field value cache in RowWithGetters was. Is it just about performance?

Some visualizations for a few selected runs:

Screenshot 2022-04-01 at 14 49 46

@mosche mosche force-pushed the BEAM-14166-RowWithGetter branch from 9ddc834 to c06f88b Compare April 1, 2022 15:41
@mosche
Copy link
Member Author

mosche commented Apr 11, 2022

Run Java PreCommit

…reeMap to reduce memory footprint of field value cache.
@mosche mosche force-pushed the BEAM-14166-RowWithGetter branch from c06f88b to f57b9f2 Compare April 11, 2022 18:05
@mosche
Copy link
Member Author

mosche commented Apr 12, 2022

Run Java PreCommit

1 similar comment
@mosche
Copy link
Member Author

mosche commented Apr 12, 2022

Run Java PreCommit

@mosche
Copy link
Member Author

mosche commented Apr 12, 2022

@TheNeuralBit @reuvenlax Please let me know how / if you wanna proceed here, so I can wrap up my initial PR #16947 accordingly. In any case, it was a very interesting dive into Beam Schemas. Thanks for all the pointers.

@mosche
Copy link
Member Author

mosche commented Apr 19, 2022

@TheNeuralBit @reuvenlax ping

@aromanenko-dev
Copy link
Contributor

@TheNeuralBit @reuvenlax
Kind ping on this

@aaltay aaltay requested review from TheNeuralBit and reuvenlax May 5, 2022 14:56
@TheNeuralBit
Copy link
Member

Run SQL PostCommit

@TheNeuralBit
Copy link
Member

Run Java PreCommit

Copy link
Member

@TheNeuralBit TheNeuralBit left a comment

Choose a reason for hiding this comment

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

Thanks @mosche for the extensive benchmarking, and apologies for the atrocious review latency!

I'm ok with this if it passes current tests, given the extensive benchmarking. A couple of things:

  • I'd still like to get some final feedback on the approach from @reuvenlax, but I acn go ahead and merge next Monday if that doesn't happen this week.
  • It could be interesting to run your benchmarks continuously to 1) monitor for regressions, and 2) give us a convenient way to test other potential improvements. Maybe file a jira for that if you don't have time now?

@Nullable
ValueT get(ObjectT object);

default @Nullable Object getRaw(ObjectT object) {
Copy link
Member

Choose a reason for hiding this comment

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

nit: maybe getUntyped is a more appropriate name here?

Also could you clarify why we need this?

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks so much for having a look @TheNeuralBit 🙏

getRaw() was based on a conversation with @reuvenlax.

getValues() is maybe poorly named - might be better called getRawValues. What you're looking for is probably the getBaseValues() method.
getValues is mostly used in code that knows exactly what it's doing for optimization purposes. It goes along with the attachValues method, which is similarly tricky to use. It's there to enable 0-copy code, but not necessarily intended for general consumption.

RowWithGetters.getValues() returns the "raw" unmodified result of the getters:

public List<Object> getValues() {
  return getters.stream().map(g -> g.get(getterTarget)).collect(Collectors.toList());
}

As I am pushing down the transformation of the getter result into the getter itself, I needed a way to bypass that in order to maintain the current semantics of getValues(). Let me know if the name makes sense given that context.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah that makes sense, thanks. Could you add some of this context in a comment there?

Copy link
Member Author

Choose a reason for hiding this comment

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

@TheNeuralBit I've opened a new PR to add the missing comment, sorry for the delay.
#21982

@reuvenlax
Copy link
Contributor

reuvenlax commented May 11, 2022 via email

@mosche
Copy link
Member Author

mosche commented May 17, 2022

@reuvenlax Kind ping, did you already get the chance to have another look at this? Just to make it clear, this is not about caching ... that was just a minor almost irrelevant part of this

@mosche mosche changed the title [BEAM-14166] Performance / Cache improvements to RowWithGetter [BEAM-14166] Performance improvements for RowWithGetter May 17, 2022
@aaltay
Copy link
Member

aaltay commented May 27, 2022

@reuvenlax - Would you be able to take another look?

@TheNeuralBit
Copy link
Member

I think this is fine to go ahead and merge. From what I can tell Reuven's original concern was that this would start caching more types than were already being cached in RowWithGetters, but this is just moving around the caching logic.

@TheNeuralBit TheNeuralBit merged commit 7c47893 into apache:master May 28, 2022
@aromanenko-dev
Copy link
Contributor

@TheNeuralBit Thanks for moving forward and finally merge it!

@mosche
Copy link
Member Author

mosche commented May 30, 2022

@TheNeuralBit Thanks a lot 🙏 I still owe you the comment on FieldValueGetter.getRaw, i'll follow up shortly!

@mosche
Copy link
Member Author

mosche commented Jun 7, 2022

Resolves #21634

@mosche mosche deleted the BEAM-14166-RowWithGetter branch June 7, 2022 12:24
@reuvenlax
Copy link
Contributor

reuvenlax commented Oct 11, 2022 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants