-
Notifications
You must be signed in to change notification settings - Fork 4.9k
Created performance tests for ROS First and TryGet methods #28760
Conversation
…suggest more clear and fast code for TryGetBuffer method.
|
@AlexRadch, going forward, please make sure to label your PRs with "area-System.Memory" if you are modifying the code in this assembly. It notifies the area owners and makes relevant issues/PRs easier to sort and find. Thanks! |
Can you please share the results of the new performance tests that were added? Before vs after? See example comparison data like dotnet/coreclr#17370 / dotnet/coreclr#17391 |
| int endIndex = 0; | ||
| GetTypeAndIndices(start.GetInteger(), end.GetInteger(), out SequenceType type, out int startIndex, out int endIndex); | ||
| object startObject = start.GetObject(); | ||
| if (startObject != null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why don't we need to check startObject != null anymore?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the last segment TryGet method return true and return Position with GetObject() equal to null. It is used as flag that all segments was processed. So when TryGet called with Position with GetObject() == nul means that all segments was processed and we return false. It is only one condition to return false and stop loop processing.
| int localInt = 0; | ||
| using (iteration.StartMeasurement()) | ||
| { | ||
| for (int i = 0; i < Benchmark.InnerIterationCount; i++) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why two loops over Benchmark.InnerIterationCount?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One loop was too fast. I added second loop.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If a single loop is too fast, increase the value of InnerCount (private const int InnerCount = 1000;)
| { | ||
| for (int i = 0; i < Benchmark.InnerIterationCount; i++) | ||
| { | ||
| for (int j = 0; j < size / 10; j++) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why size/10?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MemoryManager is slow so I make less loops to reduce test time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In that case, change the value of Benchmark.InnerIterationCount for this test.
[Benchmark(InnerIterationCount = InnerCount / 10)]| object endObject = end.GetObject(); | ||
|
|
||
| if (type != SequenceType.MultiSegment && type != SequenceType.Empty && startObject != endObject) | ||
| ThrowHelper.ThrowInvalidOperationException_EndPositionNotReached(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now that we removed this check, do we still use this ThrowInvalidOperationException_EndPositionNotReached anywhere? If not, can we remove it from ThrowHelper?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ThrowInvalidOperationException_EndPositionNotReached is used in CheckEndReachable method also. This method is slow and I will remove it later when I will create performance tests for GetPosition and Slice methods and I will suggest more performance code for Seek method.
| [InlineData(1000, 100, 800)] | ||
| [InlineData(1000 * 1000, 0, 1000 * 1000)] | ||
| [InlineData(1000 * 1000, 1000, 998 * 1000)] | ||
| private static void Byte_SingleSegment(int size, int start, int lenght) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SingleSegment -> FullArray, others are single segments too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Others uses direct casting to different objects (array, string or MemoryManager). Single segment uses casting to BufferSegment. It is perfomance tests for different internal cases.
| // See the LICENSE file in the project root for more information. | ||
|
|
||
| using System.Memory.Tests; | ||
| using System.MemoryTests; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What helpers/etc are we using from this namespace?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We using BufferSegment and CustomMemoryForTest from usual tests.
| } | ||
|
|
||
| [Benchmark(InnerIterationCount = InnerCount)] | ||
| [InlineData(1000, 0, 1000)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are all these modifications useful? Do they show different use cases/performance?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will remove duplicates
| int length = endIndex - startIndex; | ||
| object endObject = end.GetObject(); | ||
|
|
||
| if (type != SequenceType.MultiSegment && type != SequenceType.Empty && startObject != endObject) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why was this check removed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It slow down performance and moved to Debug.Asset(startObject == endObject)
| [InlineData(1000, 100, 800)] | ||
| [InlineData(1000 * 1000, 0, 1000 * 1000)] | ||
| [InlineData(1000 * 1000, 1000, 998 * 1000)] | ||
| private static void Byte_Array(int size, int start, int lenght) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo: lenght, here and elsewhere
| [InlineData(1000, 100, 800)] | ||
| [InlineData(1000 * 1000, 0, 1000 * 1000)] | ||
| [InlineData(1000 * 1000, 1000, 998 * 1000)] | ||
| private static void Byte_Memory(int size, int start, int lenght) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think there is much utility in a performance test like this one. It should be identical to the Byte_Array test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is test for ROS with MemoryManager inside.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any difference in performance between the Byte_Array and Byte_Memory tests?
Run the performance tests and share the results. In the System.Memory/tests/Performance directory, run this command:
msbuild /t:BuildAndTest /p:Performance=true /p:ConfigurationGroup=Release /p:TargetOS=Windows_NT
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any difference in performance between the Byte_Array and Byte_Memory tests?
~ 2.5 times slow.
| { | ||
| for (int i = 0; i < Benchmark.InnerIterationCount; i++) | ||
| { | ||
| for (int j = 0; j < Benchmark.InnerIterationCount; j++) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As @pakrym mentioned, we shouldn't have two loops going over InnerIterationCount (here and elsewhere)
|
I updated tests and created logs files for old and new code. How to create comparison diagram? Is there some tools? |
| { | ||
| Debug.Assert(startObject is ReadOnlySequenceSegment<T>); | ||
| ReadOnlySequenceSegment<T> startSegment = Unsafe.As<ReadOnlySequenceSegment<T>>(startObject); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Revert.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What revert?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
empty newlines
| { | ||
| length = memory.Length - startIndex; | ||
| } | ||
| if (startObject != end.GetObject()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
don't remove braces
| memory = new ReadOnlyMemory<T>(Unsafe.As<T[]>(startObject)); | ||
| } | ||
| else if (type == SequenceType.MemoryManager) | ||
| else if (typeof(T) == typeof(char) && type == SequenceType.String) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why reorder?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because this section can be optimized by compiler if T is not char.
The last section is optimized by code (comparition is commented).
So now 1 or 2 sections can be optimized.
If makes this section last then only one section will be optimized always.
|
How stable are perf results? Any idea why multisegment became slower? (That's the case we care about the most) |
| { | ||
| for (int i = 0; i < Benchmark.InnerIterationCount; i++) | ||
| { | ||
| var p = buffer.Start; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: use of var
| { | ||
| for (int i = 0; i < Benchmark.InnerIterationCount; i++) | ||
| { | ||
| var p = buffer.Start; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: same here.
| { | ||
| // We take high order bits of two indexes index and move them | ||
| // to a first and second position to convert to BufferType | ||
| return _sequenceStart.GetObject() == null ? SequenceType.Empty : (SequenceType)((((uint)_sequenceStart.GetInteger() & ReadOnlySequence.FlagBitMask) >> 30) | (uint)_sequenceEnd.GetInteger() >> 31); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see two strange xor at left. But it is compiler! At right I see many hacks with negative values. Is it correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is works https://dotnetfiddle.net/kJjenR
| if (type != SequenceType.MultiSegment && type != SequenceType.Empty && startObject != endObject) | ||
| ThrowHelper.ThrowInvalidOperationException_EndPositionNotReached(); | ||
| int endIndex = GetIndex(_sequenceEnd); | ||
| //int length = GetIndex(_sequenceEnd) - positionIndex; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove commented out code
|
|
||
| data = startSegment.Memory; | ||
| // Bounds check | ||
| #if DEBUG |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These checks need to be in Release as well (since we need to throw an exception that would be visible to the user if they passed in invalid input).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Old code does not make correct Bounds checks. Do make bounds check in production code? It will slow down performance.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we check in production next ?
Debug.Assert(positionObject is ReadOnlySequenceSegment<T>);
Debug.Assert(startSegment.RunningIndex - positionSegment.RunningIndex <= GetIndex(position) - GetIndex(_sequenceStart) ||
GetIndex(position) - GetIndex(_sequenceEnd) <= endSegment.RunningIndex - positionSegment.RunningIndex);
Debug.Assert(nextSegment != null);Debug.Assert(positionObject == _sequenceEnd.GetObject())Debug.Assert(GetIndex(_sequenceStart) <= GetIndex(position) && GetIndex(position) <= GetIndex(_sequenceEnd));
| if (nextSegment == null && startSegment != endObject) | ||
| ThrowHelper.ThrowInvalidOperationException_EndPositionNotReached(); | ||
| ReadOnlySequenceSegment<T> nextSegment = positionSegment.Next; | ||
| Debug.Assert(nextSegment != null); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This check cannot be in debug only.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The tests shouldn't be passing. There is a test gap here. We should have tests verifying that we throw if if (nextSegment == null && nextSegment != _sequenceEnd.GetObject())
cc @pakrym
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here (nextSegment == null && nextSegment != _sequenceEnd.GetObject()) second part is always true because _sequenceEnd.GetObject() is never == null
| next = new SequencePosition(nextSegment, 0); | ||
| length = data.Length - startIndex; | ||
| endIndex = memory.Length; | ||
| //length = memory.Length - positionIndex; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove commented out code
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
| private SequenceType GetSequenceType() | ||
| { | ||
| // We take high order bits of two indexes index and move them |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo in the comment: ...two indexes index...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I copied it from other method ;)
|
|
||
| namespace System.Buffers.Tests | ||
| { | ||
| public class Rerf_ReadOnlySequence_First |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo: Rerf_ReadOnlySequence_First -> Perf... (same with TryGet)
| else if (typeof(T) == typeof(char) && type == SequenceType.String) | ||
| { | ||
| Debug.Assert(startObject is string); | ||
| memory = new ReadOnlyMemory<T>(Unsafe.As<T[]>(positionObject)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't need Unsafe code here and we can afford to take the performance hit of casting it (to get type safety).
If we re-write the TryGetBuffer method as follows, we get type safety, and we are still faster than the original:
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool TryGetBuffer(in SequencePosition position, out ReadOnlyMemory<T> memory, out SequencePosition next)
{
object positionObject = position.GetObject();
next = default;
if (positionObject == null)
{
memory = default;
return false;
}
SequenceType type = GetSequenceType(position);
object endObject = _sequenceEnd.GetObject();
int startIndex = GetIndex(position);
int endIndex = GetIndex(_sequenceEnd);
if (type == SequenceType.MultiSegment)
{
Debug.Assert(positionObject is ReadOnlySequenceSegment<T>);
//ReadOnlySequenceSegment<T> startSegment = Unsafe.As<ReadOnlySequenceSegment<T>>(positionObject);
ReadOnlySequenceSegment<T> startSegment = (ReadOnlySequenceSegment<T>)positionObject;
if (startSegment != endObject)
{
ReadOnlySequenceSegment<T> nextSegment = startSegment.Next;
if (nextSegment == null)
ThrowHelper.ThrowInvalidOperationException_EndPositionNotReached();
next = new SequencePosition(nextSegment, 0);
memory = startSegment.Memory.Slice(startIndex);
}
else
{
memory = startSegment.Memory.Slice(startIndex, endIndex - startIndex);
}
}
else if (type == SequenceType.Array)
{
if (positionObject != endObject)
ThrowHelper.ThrowInvalidOperationException_EndPositionNotReached();
Debug.Assert(positionObject is T[]);
//memory = new ReadOnlyMemory<T>(Unsafe.As<T[]>(positionObject), startIndex, endIndex - startIndex);
memory = new ReadOnlyMemory<T>((T[])positionObject, startIndex, endIndex - startIndex);
}
else if (typeof(T) == typeof(char) && type == SequenceType.String)
{
if (positionObject != endObject)
ThrowHelper.ThrowInvalidOperationException_EndPositionNotReached();
Debug.Assert(positionObject is string);
//memory = (ReadOnlyMemory<T>)(object)(Unsafe.As<string>(positionObject)).AsMemory(startIndex, endIndex - startIndex);
memory = (ReadOnlyMemory<T>)(object)((string)positionObject).AsMemory(startIndex, endIndex - startIndex);
}
else // type == SequenceType.MemoryManager
{
if (positionObject != endObject)
ThrowHelper.ThrowInvalidOperationException_EndPositionNotReached();
Debug.Assert(type == SequenceType.MemoryManager);
Debug.Assert(positionObject is MemoryManager<T>);
//memory = Unsafe.As<MemoryManager<T>>(positionObject).Memory.Slice(startIndex, endIndex - startIndex);
memory = ((MemoryManager<T>)positionObject).Memory.Slice(startIndex, endIndex - startIndex);
}
return true;
}There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we check that positionObject == endObject means that unsafe code will be correct always.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In single segment we can just ignore positionObject and get memory directly from endObject with unsafe code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we check that positionObject == endObject means that unsafe code will be correct always.
That is true. Good point.
In single segment we can just ignore positionObject and get memory directly from endObject with unsafe code.
I am not completely convinced of this. Some tests will boost my confidence in that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI, @stephentoub, brought up an interesting issue the unsafe type casting can cause due to struct tearing: https://github.com/dotnet/corefx/issues/28920
| sequenceType = Start.GetObject() == null ? SequenceType.Empty : (SequenceType)((((uint)Start.GetInteger() >> 30) & 2) | (uint)End.GetInteger() >> 31); | ||
| // We take high order bits of two indexes and move them | ||
| // to a first and second position to convert to SequenceType | ||
| sequenceType = _sequenceStart.GetObject() == null |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Call the GetSequenceType instead, to avoid code duplication
sequenceType = GetSequenceType();
| ThrowHelper.ThrowInvalidOperationException_EndPositionNotReached(); | ||
| int endIndex = GetIndex(_sequenceEnd); | ||
|
|
||
| SequenceType type = GetSequenceType(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't GetSequenceType() take position as a parameter here instead of using _sequenceStart?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't GetSequenceType() take position as a parameter here instead of using _sequenceStart?
Sequance type is stored as sing bits in _sequenceStart.GetInteger() and _sequenceEnd.GetInteger() except default (_sequenceStart.GetObject == null).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I get that, but there is no guarantee that the SequencePosition that the user passes in would have the same sign bits as _sequenceStart.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can't validate the type by one sequence position. It might be .Start or .End so it might or might not have the flag.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I get that, but there is no guarantee that the SequencePosition that the user passes in would have the same sign bits as _sequenceStart.
We ignore position flag because it can contain any flag, from Start or from End or does not contain flag at all.
We story flags in _sequenceStart.GetInteger() and _sequenceEnd.GetInteger(), so we get flags from _sequenceStart.GetInteger() and _sequenceEnd.GetInteger().
|
|
||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
| internal ReadOnlyMemory<T> GetFirstBuffer(in SequencePosition start, in SequencePosition end) | ||
| private ReadOnlyMemory<T> GetFirstBuffer() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Try something like this:
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private ReadOnlyMemory<T> GetFirstBuffer()
{
object startObject = _sequenceStart.GetObject();
if (startObject == null)
{
return default;
}
SequenceType type = GetSequenceType();
object endObject = _sequenceEnd.GetObject();
int startIndex = GetIndex(_sequenceStart);
int endIndex = GetIndex(_sequenceEnd);
if (type == SequenceType.MultiSegment)
{
Debug.Assert(startObject is ReadOnlySequenceSegment<T>);
ReadOnlySequenceSegment<T> startSegment = Unsafe.As<ReadOnlySequenceSegment<T>>(startObject);
if (startObject != endObject)
{
return startSegment.Memory.Slice(startIndex);
}
return startSegment.Memory.Slice(startIndex, endIndex - startIndex);
}
if (type == SequenceType.Array)
{
if (startObject != endObject)
ThrowHelper.ThrowInvalidOperationException_EndPositionNotReached();
Debug.Assert(startObject is T[]);
return new ReadOnlyMemory<T>(Unsafe.As<T[]>(startObject), startIndex, endIndex - startIndex);
}
if (typeof(T) == typeof(char) && type == SequenceType.String)
{
if (startObject != endObject)
ThrowHelper.ThrowInvalidOperationException_EndPositionNotReached();
Debug.Assert(startObject is string);
return (ReadOnlyMemory<T>)(object)(Unsafe.As<string>(startObject)).AsMemory(startIndex, endIndex - startIndex);
}
Debug.Assert(type == SequenceType.MemoryManager);
if (startObject != endObject)
ThrowHelper.ThrowInvalidOperationException_EndPositionNotReached();
Debug.Assert(type == SequenceType.MemoryManager);
Debug.Assert(startObject is MemoryManager<T>);
return Unsafe.As<MemoryManager<T>>(startObject).Memory.Slice(startIndex, endIndex - startIndex);
} [MethodImpl(MethodImplOptions.AggressiveInlining)]
private SequenceType GetSequenceType()
{
return (SequenceType)(-(2 * (_sequenceStart.GetInteger() >> 31) + (_sequenceEnd.GetInteger() >> 31)));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private SequenceType GetSequenceType(in SequencePosition position)
{
return (SequenceType)(-(2 * (position.GetInteger() >> 31) + (_sequenceEnd.GetInteger() >> 31)));
}| Debug.Assert(startObject is ReadOnlySequenceSegment<T>); | ||
| ReadOnlySequenceSegment<T> startSegment = Unsafe.As<ReadOnlySequenceSegment<T>>(startObject); | ||
| Debug.Assert(positionObject is ReadOnlySequenceSegment<T>); | ||
| ReadOnlySequenceSegment<T> positionSegment = Unsafe.As<ReadOnlySequenceSegment<T>>(positionObject); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not think we should be using Unsafe.As here at all, for the same reason why we are not using Unsafe.As in System.Memory<T>. I think that using Unsafe.As here is striking a wrong balance between security and performance.
|
@AlexRadch, can you please split up the performance tests and the optimizations into separate PRs? Let's get the performance tests checked in (since they are not contentious) and then discuss the optimizations separately. I would recommend updating this PR to only include the performance tests that you added. Take the perf optimizations and recommendations and open up a new PR. Thanks! |
|
I added all @ahsonkhan optimizations and all bounds checks. The main delay is made by a safe type conversion in a line https://github.com/dotnet/corefx/pull/28760/files#diff-6a3b2bb4fe535f3ac47622b764877747R66 |
| } | ||
|
|
||
| // Start or Middle segment | ||
| { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't create blocks without flow control statements.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why? I want local variables.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using these blocks to create separate scopes makes the code harder to read and maintain (especially with local variable name re-use and gotos). This method is already tricky as is so I would try to avoid additional complexity. I would recommend simplifying it. Also, I wouldn't use the trusted flag either (those checks should always happen).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Goto used to throw exception or exit. So it did not make method more complex to understand.
| { | ||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
| internal bool TryGetBuffer(in SequencePosition start, in SequencePosition end, out ReadOnlyMemory<T> data, out SequencePosition next) | ||
| internal bool TryGetBuffer(in SequencePosition position, out ReadOnlyMemory<T> memory, out SequencePosition next, bool tructed = false) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo: tructed (also I would get rid of this and always check/throw).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also I would get rid of this and always check/throw
Trusted true is called from internal tested code where position is always correct. So all checks will not throw any exception. As result this method will be about two times faster on multi segments for internal methods.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public TryGet method use this method with trusted = false.
|
@AlexRadch, the changes in this PR are getting harder to review and keep up with, especially with some iterations of code churn. I suggest isolating the change (and limiting it to only what is essential) rather than re-working it too much. My suggestion:
Otherwise, getting anything merged would take too long and the code review becomes cumbersome. Thanks a lot for your efforts and contribution on this! If you prefer, I could take the changes you made (and the intent of this PR), starting at your branch, and take it forward on your behalf by submitting a PR on top of your contribution. Let me know if you would prefer that. |
|
@ahsonkhan I am making such changes but I do not finish Slice and others perf tests yet. I will finish all perf tests and then publish them. I see all messages about this and working on it. |






I created performance tests for ROS First and TryGet methods. Also I suggest more clear and fast code for TryGetBuffer and GetFirstBuffer methods.