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

Reduce and prevent unnecessary random-access to List #90705

Merged
merged 1 commit into from
May 7, 2024

Conversation

AThousandShips
Copy link
Member

@AThousandShips AThousandShips commented Apr 15, 2024

Random-access access to List when iterating is O(n^2) (O(n) when accessing a single element)

  • Removed subscript operator, in favor of a more explicit get
  • Added conversion from Iterator to ConstIterator
  • Remade existing operations into other solutions when applicable

Didn't (generally) replace uses of List with other structures except a few cases where it avoided a lot of code weirdness, it takes a lot more detailed evaluation of uses to handle that side, better left for other PRs by area maintainers

@@ -152,9 +152,10 @@ void DocData::method_doc_from_methodinfo(DocData::MethodDoc &p_method, const Met

return_doc_from_retinfo(p_method, p_methodinfo.return_val);

for (int i = 0; i < p_methodinfo.arguments.size(); i++) {
int i = 0;
Copy link
Member Author

Choose a reason for hiding this comment

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

These are to allow the use of the i variable, and unfortunately c++17 doesn't allow initializing multiple variables of different types in the for loop

Array arguments;
for (int i = (has_return ? -1 : 0); i < mi.arguments.size(); i++) {
PropertyInfo pinfo = i == -1 ? mi.return_val : mi.arguments[i];
if (has_return) {
Copy link
Member Author

Choose a reason for hiding this comment

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

Split this up because of the use of iterators

const PropertyInfo &prop = info.arguments[i];
if (!_is_exact_type(prop, p_arguments[i].type)) {
int i = 0;
for (List<PropertyInfo>::ConstIterator itr = info.arguments.begin(); itr != info.arguments.end(); ++itr, ++i) {
Copy link
Member

Choose a reason for hiding this comment

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

There's a few of these cases, seems a common paradigm.

I do wonder if using the for each simpler version and putting the i++ on the first line might end up being easier to read. Maybe there's a more elegant way.

Copy link
Member Author

Choose a reason for hiding this comment

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

Can look into them

Copy link
Member Author

Choose a reason for hiding this comment

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

I did these, as seen above, to avoid any risky cases to ensure the iteration is clean, keeping the iteration in one place feels safer to me given some errors I accidentally caused with some of these cases when I used for each syntax, so will keep these for now for a potential cleanup down the line to ensure they work correctly

Random-access access to `List` when iterating is `O(n^2)` (`O(n)` when
accessing a single element)

* Removed subscript operator, in favor of a more explicit `get`
* Added conversion from `Iterator` to `ConstIterator`
* Remade existing operations into other solutions when applicable
@lawnjelly
Copy link
Member

Overall this looks great.

I'd be tempted to take the plunge and use local variables (e.g. reference) in a few cases where multiple front()->get() etc are used, just in order to reduce the verbosity.

These weren't there originally granted, so I understand being wary of introducing bugs via duplicate variable names or something. Or could be left to another PR.

@AThousandShips
Copy link
Member Author

Fixed the unnecessary referencing in main, but left most of the other cases alone, as it's a bit of a larger careful refactor, and can do those in a follow-up

Copy link
Member

@lawnjelly lawnjelly left a comment

Choose a reason for hiding this comment

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

Looks okay from looking through as best I could (quite long PR), although not tested.

@Calinou
Copy link
Member

Calinou commented May 6, 2024

I've tried to compare results between master and this PR on https://github.com/godotengine/godot-benchmarks, but the benchmarks where it would be the most relevant tend to have high runtime variations.

Either way, I've gotten this from hyperfine by running all its GDScript benchmarks:

❯ hyperfine 'bin/godot.linuxbsd.editor.x86_64.master --path ~/Documents/Git/godotengine/godot-benchmarks -- --run-benchmarks --include-benchmarks="*gdscript*"' 'bin/godot.linuxbsd.editor.x86_64 --path ~/Documents/Git/godotengine/godot-benchmarks -- --run-benchmarks --include-benchmarks="*gdscript*"'              
Benchmark 1: bin/godot.linuxbsd.editor.x86_64.master --path ~/Documents/Git/godotengine/godot-benchmarks -- --run-benchmarks --include-benchmarks="*gdscript*"
  Time (mean ± σ):     41.086 s ±  0.164 s    [User: 39.285 s, System: 0.795 s]
  Range (min … max):   40.770 s … 41.294 s    10 runs
 
Benchmark 2: bin/godot.linuxbsd.editor.x86_64 --path ~/Documents/Git/godotengine/godot-benchmarks -- --run-benchmarks --include-benchmarks="*gdscript*"
  Time (mean ± σ):     41.053 s ±  0.176 s    [User: 39.270 s, System: 0.777 s]
  Range (min … max):   40.801 s … 41.325 s    10 runs
 
Summary
  bin/godot.linuxbsd.editor.x86_64 --path ~/Documents/Git/godotengine/godot-benchmarks -- --run-benchmarks --include-benchmarks="*gdscript*" ran
    1.00 ± 0.01 times faster than bin/godot.linuxbsd.editor.x86_64.master --path ~/Documents/Git/godotengine/godot-benchmarks -- --run-benchmarks --include-benchmarks="*gdscript*"

This PR seems to provide a tiny overall speedup (in terms of overall runtime, including startup + shutdown) across an average of 10 runs for each. Standard deviation is still quite high so take it with a grain of salt, but at least we can be assured this change doesn't harm performance.

@akien-mga akien-mga modified the milestones: 4.x, 4.3 May 7, 2024
@lawnjelly
Copy link
Member

This PR seems to provide a tiny overall speedup (in terms of overall runtime, including startup + shutdown) across an average of 10 runs for each. Standard deviation is still quite high so take it with a grain of salt, but at least we can be assured this change doesn't harm performance.

It is good to confirm there is no slowdown. 👍
In terms of performance increases though, bear in mind the primary intended effect here is protective.

Existing common path performance sapping cases will (hopefully) mostly already have been fixed. The aim here is to make it more difficult to inadvertently introduce performance hotspots in the future, which then need extra work to identify and fix.

There's a number of other data structures which tend to be more appropriate if elements are heavy, there is random insertions / deletions / moves, and indexed access is required, which is why indexed access to linked list is usually a bit of a "code smell".

@akien-mga akien-mga merged commit e63252b into godotengine:master May 7, 2024
16 checks passed
@akien-mga
Copy link
Member

Thanks!

@AThousandShips
Copy link
Member Author

Thank you!

If you or anyone come across things related to this just point me that direction if any help is needed for adjusting to these changes

@AThousandShips
Copy link
Member Author

In terms of performance increases though, bear in mind the primary intended effect here is protective.

Exactly, thank you

I am going to add a few follow-up improvements, like replacements with LocalVector, also gonna open a PR with a get_front/back now that this is merged, but some areas are more appropriate for area maintainers to look at for replacements, but they'll be much easier to identify now given the dedicated syntax

@AThousandShips

This comment was marked as outdated.

@aaronfranke
Copy link
Member

aaronfranke commented May 16, 2024

Removed subscript operator, in favor of a more explicit get

What is the replacement for setting at a given index?

Screenshot 2024-05-16 at 4 38 25 AM

@AThousandShips
Copy link
Member Author

AThousandShips commented May 16, 2024

Using foo.get(i) = x;, I chose not to add an explicit set for clutter, can do if it's needed for clarity, but the method is a drop in replacement for the operator

Bur you likely shouldn't be using List if you need to do this

@aaronfranke
Copy link
Member

I'm passing data to OS::get_singleton()->execute, which uses List. I have to use List because this is Godot's API.

@AThousandShips
Copy link
Member Author

I think editing the elements in this way isn't necessary, adding and removing from the end, or creating a new list might be better

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

Successfully merging this pull request may close these issues.

8 participants