Skip to content

Conversation

@jverral
Copy link
Contributor

@jverral jverral commented Nov 11, 2021

Overview

Greatly reduce allocations related to focus/cursors, and MaterialInstance, which was calling renderer.sharedMaterials every frame (which allocates).

Also brings in a keyboard change from the MRTK 2.6 branch into the 2.7 branch.

@keveleigh keveleigh changed the title Porting over Robert Sonnino's optimizations Porting over some optimizations Nov 11, 2021
get
{
Debug.Assert(isMarkedDestroyed.HasValue, $"{GetType()} has not set a value for IsMarkedDestroyed, returning false.");
if (!isMarkedDestroyed.HasValue)
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this need the if check if it's also being converted from Debug.Assert to Debug.AssertFormat without an interpolated string? The allocation would be happening because of the interpolated string, but the method call without it will no-op instead.

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we just need to change anywhere we use interpolated strings in Debug.Assert calls into Debug.AssertFormat calls?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well, there's also GetType() that would still get called. Though we also changed GetType to cache it's result so it's not as expensive, but I use it as an example of Debug.AssertFormat not necessarily being a perfect solution.

Copy link
Contributor

Choose a reason for hiding this comment

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

True! A combination of caching the type and using AssertFormat seems good though.

Copy link
Contributor

Choose a reason for hiding this comment

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

Looked into this more...unfortunately since AssertFormat uses params object[] as a parameter, it allocs an array every call :( the if might be our only path forward @davidkline-ms

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh! Caching the type name as an array might get around this...digging in!

image

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, that dropped me to 0 allocs without the if:
image
I also refactored the strings out into const strings, since we're no longer interpolating them

switch (state)
{
case KeyboardState.Showing:
if (TouchScreenKeyboard.visible == true)
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this might bust some OS versions where the .visible state isn't properly reported. That's a main reason why we wrote all this custom keyboard code in the first place. @MaxWang-MS might be able to elaborate.

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for pointing this out Kurtis! It is a known Unity issue that the .visible state can be inaccurate under certain circumstances, even on the latest HL2 OS. So we deliberately decided to track the state ourselves in this class instead of relying on that Unity property.

if (pointersList[i].Pointer is T typePointer && !typePointer.IsNull())
{
typePointers.Add(typePointer);
yield return typePointer;
Copy link
Contributor

Choose a reason for hiding this comment

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

We discussed this change a while back in #8118. Any chance you did any perf measurements to verify that this isn't trading garbage for perf?

Copy link
Contributor

Choose a reason for hiding this comment

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

Honestly, the best solution would be to have an overload where you can pass a list in, but that would be a breaking change. The comparison in the article is not exactly valid because here you were creating a List, iterating on it with foreach and returning the IEnumerable anyway.

@keveleigh
Copy link
Contributor

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 2 pipeline(s).

@keveleigh
Copy link
Contributor

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 2 pipeline(s).

Copy link

@david-c-kline david-c-kline left a comment

Choose a reason for hiding this comment

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

there are some issues in this pr that should be addressed (ex: replace assert with logerror, fix calling of AssetsContainsShaders)

if (!AssetsContainsShaders(packageShaderFolder))
DirectoryInfo packageShaderFolder = FindShaderFolderInPackage();

if (!AssetsContainsShaders(packageShaderFolder, sentinelPath, ignoreFile))

Choose a reason for hiding this comment

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

I dont see changes to AssetsContainsShaders() that reflect the new parameter list.

Copy link
Contributor

Choose a reason for hiding this comment

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

Just pushed this change!

get
{
Debug.Assert(isMarkedDestroyed.HasValue, $"{GetType()} has not set a value for IsMarkedDestroyed, returning false.");
if (!isMarkedDestroyed.HasValue)
Copy link
Contributor

Choose a reason for hiding this comment

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

Looked into this more...unfortunately since AssertFormat uses params object[] as a parameter, it allocs an array every call :( the if might be our only path forward @davidkline-ms

get
{
Debug.Assert(isMarkedDestroyed.HasValue, $"{GetType()} has not set a value for IsMarkedDestroyed, returning false.");
if (!isMarkedDestroyed.HasValue)
Copy link
Contributor

Choose a reason for hiding this comment

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

Oh! Caching the type name as an array might get around this...digging in!

image

get
{
Debug.Assert(isMarkedDestroyed.HasValue, $"{GetType()} has not set a value for IsMarkedDestroyed, returning false.");
if (!isMarkedDestroyed.HasValue)
Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, that dropped me to 0 allocs without the if:
image
I also refactored the strings out into const strings, since we're no longer interpolating them

/// <typeparam name="T">The type of pointers to request. Use IMixedRealityPointer to access all pointers.</typeparam>
/// <param name="predicate">A function that returns true when the parameter satisfies a condition.</param>
/// <returns>The first pointer in the collection that satisfies the specified condition.</returns>
T GetFirstPointerWhere<T>(System.Predicate<T> predicate) where T : class, IMixedRealityPointer;
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if we can approach these as extension methods instead. Changing this interface would be a breaking change for anybody who has rolled their own focus provider, and I know of a handful of people who have.

{
var spherePointers = focusProvider.GetPointers<SpherePointer>();
foreach (var spherePointer in spherePointers)
var spherePointer = focusProvider.GetFirstPointerWhere<SpherePointer>(p => p.Controller == Pointer.Controller);
Copy link
Contributor

@keveleigh keveleigh Nov 11, 2021

Choose a reason for hiding this comment

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

I'm not sure this change does a whole ton to the allocations here. It seems to only drop it from 144B to 112B (which is some decrease for sure!). I'm pretty sure the new allocs are from the predicate being passed into GetFirstPointerWhere. I'll investigate.

Before this change:
image

After:
image

Copy link
Contributor

Choose a reason for hiding this comment

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

Okay I reworked this to avoid the alloc!
image
It's a kinda-weird static pattern that I've used in the past, but it should work!

@keveleigh
Copy link
Contributor

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 2 pipeline(s).

keveleigh added a commit to keveleigh/HoloToolkit-Unity that referenced this pull request Nov 13, 2021
This is semi-ported out of microsoft#10310
keveleigh added a commit to keveleigh/HoloToolkit-Unity that referenced this pull request Nov 13, 2021
This is semi-ported out of microsoft#10310
keveleigh added a commit to keveleigh/HoloToolkit-Unity that referenced this pull request Nov 13, 2021
This is semi-ported out of microsoft#10310
@david-c-kline
Copy link

@keveleigh, assigning to you to continue cherry picking the key fixes

@david-c-kline
Copy link

@keveleigh is reviewing this PR. he will work with @jverral

@david-c-kline
Copy link

Closing as the key improvements have been ingested.

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.

5 participants