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

Massively optimizing TaskDriver performance #264

Merged
merged 4 commits into from
Jun 12, 2023
Merged

Conversation

jkeon
Copy link
Member

@jkeon jkeon commented Jun 2, 2023

Optimized TaskDrivers so they won't schedule jobs if no one has written to them.

What is the current behaviour?

TaskDrivers always run their jobs even if there is no data to operate on. This leads to a lot of scheduling overhead that adds up.

What is the new behaviour?

TaskDriverManagementSystem forces a sync after consolidation. This is to prevent issues with circular writing patterns such as:

  • Write Pending
  • Consolidate to Active (which gets a write handle to Pending again so we can clear it even though we're not actually writing)
  • Which will cause the Consolidation to happen again next frame for 0 elements.

By forcing the consolidation, we can also check the length to ensure we actually have data when scheduling.

AbstractData exposes a boolean to let callers know if its data could have been invalidated. If so, it's worth scheduling the job. Otherwise, no one has touched it.

AbstractJobConfig will only schedule jobs if the scheduling data has data to work on.
AbstractDataSource will only consolidate data if there has data to work on.

What issues does this resolve?

  • None

What PRs does this depend on?

Does this introduce a breaking change?

  • Yes
  • No

@jkeon jkeon requested a review from mbaker3 June 2, 2023 18:48
Comment on lines 61 to 64
if (!m_ProgressLookupData.IsDataInvalidated && m_ParentProgressLookupData is
{
IsDataInvalidated: false
})
Copy link
Member

Choose a reason for hiding this comment

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

just to double check, this is just syntax sugar. It doesn't resolve to some sort of reflection.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah it's just Rider merging the following into a pattern.

if (!m_ProgressLookupData.IsDataInvalidated && (m_ParentProgressLookupData != null && !m_ParentProgressLookupData.IsDataInvalidated))

I've never seen it do it like that before but there isn't any reflection or allocations going on.

Copy link
Member

Choose a reason for hiding this comment

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

OK, just wanted to double check because I have seen that pattern matching style produce less efficient code

Comment on lines 37 to 47
/// <summary>
/// Whether the underlying data has potentially been updated by something getting write access to it.
/// </summary>
public virtual bool IsDataInvalidated
{
//If the current Read dependency has changed from what we last stored, then someone has written here
get => m_AccessController
.GetDependencyFor(AccessType.SharedRead)
.Equals_NoBox(m_LastSharedReadAccessJobHandle);
}

Copy link
Member

Choose a reason for hiding this comment

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

This only works if nobody has called AcquireAsync(AccessType.SharedRead) since write access was gained.

I think we'd be better off having the caller pass in the read handle at the last time they requested read because data invalidation is relative to the perspective of the data consumer not the provider.

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'm not sure I fully understand. I've written out a flow below. Am I missing something?

I think we are handling this where AbstractData is the consumer and handles storing the read handle for the last time it requested read.

TDA tries to read from AbstractData

  • LastSharedReadAccess set to the Data's Read Dependency (1)

TDB tries to read from same AbstractData

  • LastSharedReadAccess set to the Data's Read Dependency (still 1)

TDC tries to write to the same AbstractData

  • Data's dependencies move up the read is now (2)

TDD tries to read from the same AbstractData

  • Data is invalid because the AbstractData's LastSharedReadAccess that was stored is not the same anymore.
  • LastSharedReadAccess set to the Data's Read Dependency (now 2)

Copy link
Member

Choose a reason for hiding this comment

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

After your last point what if TDA then tries to read from AbstractData?

It doesn't know that the data has changed since its last read and will think that IsDataInvalidated

From what I'm understanding, this results in one member's read operation hiding the data change from all other potential readers. It's only the first member that reads after a write that gets to know that the data is invalidated.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ugh... you're right!

Good point. I've moved the storage and checking of the read handles to where the caller for the jobs are which makes sense. We really want to know if we've been written to since the last time we ran this specific job. If we have, then our job needs to run again. If we haven't, then nothing has changed and so we don't need to run our job.

Base automatically changed from entity-spawner-revamp to main June 7, 2023 20:10
# Conflicts:
#	Scripts/Runtime/Entities/TaskDriver/TaskSet/TaskData/DataStream/DataSource/Data/ActiveArrayData.cs
@jkeon jkeon requested a review from mbaker3 June 8, 2023 12:54
@jkeon
Copy link
Member Author

jkeon commented Jun 12, 2023

@mbaker3 Ready for re-review

Copy link
Member

@mbaker3 mbaker3 left a comment

Choose a reason for hiding this comment

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

Looks good.
One concern about an extra unnecessary stack alloc but feel free to skip if I'm wrong.

@jkeon jkeon merged commit 37c8439 into main Jun 12, 2023
@jkeon jkeon deleted the task-driver-optimizations branch June 12, 2023 18:05
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.

2 participants