Skip to content

Make MetadataTypeName non-copyable#46031

Merged
sharwell merged 1 commit intodotnet:masterfrom
sharwell:non-copyable-name
Jul 17, 2020
Merged

Make MetadataTypeName non-copyable#46031
sharwell merged 1 commit intodotnet:masterfrom
sharwell:non-copyable-name

Conversation

@sharwell
Copy link
Contributor

This mutable value type caches computed values to improve performance. Marking it as non-copyable helps ensure it is only used in a manner that preserves optimum performance.

This mutable value type caches computed values to improve performance.
Marking it as non-copyable helps ensure it is only used in a manner that
preserves optimum performance.
@sharwell sharwell requested a review from a team as a code owner July 16, 2020 02:26
public readonly Key ToKey()
{
return new Key(this);
return new Key(in this);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

📝 This use was flagged by the analyzer.

@AlekseyTs
Copy link
Contributor

Could you please elaborate what exactly is wrong with copying the structure? Is copy or original value going to misbehave in some way?

/// Also, allows us to stop using strings in the APIs that accept only metadata names,
/// making usage of them less bug prone.
/// </summary>
[NonCopyable]
Copy link
Contributor

Choose a reason for hiding this comment

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

[NonCopyable] [](start = 3, length = 14)

What effect is this going to have?

@AlekseyTs
Copy link
Contributor

AlekseyTs commented Jul 16, 2020

I am fine with the changes made inside the MetadataTypeName type. However, I don't want to be prevented from making copies of this structure or having to deal with build breaks caused by an analyzer complaining about it.

@AlekseyTs
Copy link
Contributor

Done with review pass (iteration 1)

@sharwell
Copy link
Contributor Author

sharwell commented Jul 16, 2020

Could you please elaborate what exactly is wrong with copying the structure?

We recently did a performance investigation which revealed a large number of allocations (10+GB) in calls to MetadataTypeName.NamespaceSegments. This is a lazily-computed value which is persisted to a mutable value type, so part of the investigation time involved determining if the code was operating on the "original" instance of this type or a copy (where the latter would fail to persist the lazily-computed value such that subsequent calls return the cached copy).

Usage of the type in the current code base suggests that copies of this value type have been carefully avoided, likely for this reason. If copies are being carefully avoided, analyzer assistance may help developers use this type in the expected manner over time.

What effect is this going to have?

An analyzer will report a warning if a copy of an instance of this type is made (as opposed to moving the instance, which is safe). The attribute eliminates the need to perform the investigation described previously (in fact, the evaluation was actually performed by applying this attribute and seeing what happened).

Current usage of this type produced one true positive report from this analyzer, and zero false positives. The analyzer is good, but not perfect, so it is possible that some future use would report a false positive that needs to be suppressed or fixed.

@AlekseyTs
Copy link
Contributor

Is my observation correct that the changes made didn't actually eliminate any allocations?

@sharwell
Copy link
Contributor Author

@AlekseyTs That is correct. The changes only provide compile-time verification of a condition which was already occurring.

Copy link
Contributor

@AlekseyTs AlekseyTs left a comment

Choose a reason for hiding this comment

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

LGTM (iteration 1)

@sharwell
Copy link
Contributor Author

sharwell commented Jul 17, 2020

This change is completely optional. The attribute helped with the investigation I was doing, but that was true even when the attribute wasn't already present (I added it, and then reviewed the results). If you feel it would be valuable for maintaining a condition we can merge it, otherwise it remains something developers are responsible for.

If you think it might be valuable but are concerned about the maintainability over time, we can also merge it now and it can be reverted if it's ever a problem.

Copy link
Member

@gafter gafter left a comment

Choose a reason for hiding this comment

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

:shipit:

@sharwell
Copy link
Contributor Author

sharwell commented Jul 17, 2020

@dotnet/roslyn-compiler if you ever run into problems with this analyzer, please feel free to reach out for me to fix the analyzer and/or revert this change (or rather, the use of the attribute since the readonly modifiers were valid either way 😄 ).

@sharwell sharwell merged commit 25689c6 into dotnet:master Jul 17, 2020
@ghost ghost added this to the Next milestone Jul 17, 2020
@sharwell sharwell deleted the non-copyable-name branch July 17, 2020 19:28
@jaredpar
Copy link
Member

as opposed to moving the instance, which is safe

What does a move look like? Basically what code do we type to perform a move that will be recognized by the analyzer?

@sharwell
Copy link
Contributor Author

The most common moves are assigning a return value to a local, or returning a local from a method. These are already recognized. Flow analysis for other cases is high on my list of things I want for this analyzer. In the meantime, the following works if you end up needing it:

internal static class NonCopyableExtensions
{
    public static T Move<T>(this ref T value)
            where T : struct
    {
        T result = value;
#if DEBUG
        value = default;
#endif
        return result;
    }
}

@sharwell
Copy link
Contributor Author

The other case that's a bit weird is move to box. Boxing a value is treated as an allowed move, but once the value is in the box you can't move it back out since other code might be referencing the same location. For this case, you can use Unsafe.Unbox<T> to get a typed reference to the boxed value.

@JoeRobich JoeRobich modified the milestones: Next, 16.8.P1 Jul 20, 2020
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