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

Empty slice fix #107316

Merged
merged 5 commits into from
Sep 16, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -655,34 +655,70 @@ public ReadOnlyTensorSpan<T> Slice(params scoped ReadOnlySpan<NRange> ranges)
if (ranges.Length != Lengths.Length)
throw new ArgumentOutOfRangeException(nameof(ranges), "Number of dimensions to slice does not equal the number of dimensions in the span");

ReadOnlyTensorSpan<T> toReturn;
scoped Span<nint> lengths;
scoped Span<nint> offsets;
nint[]? lengthsArray;
nint[]? offsetsArray;
if (Rank > TensorShape.MaxInlineRank)
{
lengths = stackalloc nint[Rank];
offsets = stackalloc nint[Rank];
lengthsArray = ArrayPool<nint>.Shared.Rent(Rank);
lengths = lengthsArray;
lengths = lengths.Slice(0, Rank);
michaelgsharp marked this conversation as resolved.
Show resolved Hide resolved
lengthsArray = null;
michaelgsharp marked this conversation as resolved.
Show resolved Hide resolved

offsetsArray = ArrayPool<nint>.Shared.Rent(Rank);
offsets = offsetsArray;
offsets = offsets.Slice(0, Rank);
offsetsArray = null;
michaelgsharp marked this conversation as resolved.
Show resolved Hide resolved
}
else
{
lengths = new nint[Rank];
offsets = new nint[Rank];
lengths = stackalloc nint[Rank];
Copy link
Member

Choose a reason for hiding this comment

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

not something to fix in this PR, but dynamically lengthed stackalloc is "bad"

Rather we should be using MaxInlineRank and slicing down to Rank

Copy link
Member Author

Choose a reason for hiding this comment

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

Sounds good. Why is dynamically lengthed stackalloc bad?

Copy link
Member

@tannergooding tannergooding Sep 16, 2024

Choose a reason for hiding this comment

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

Because it results in a lot of additional codegen to handle the fact the length is "unknown". Since we know the maximum is small, its better to do that and slice to avoid the pessimization.

Otherwise, it typically ends up cheaper to just use the array pool; rather than stackalloc at all.

offsets = stackalloc nint[Rank];

lengthsArray = null;
offsetsArray = null;
}

for (int i = 0; i < ranges.Length; i++)
{
(offsets[i], lengths[i]) = ranges[i].GetOffsetAndLength(Lengths[i]);
}

nint index = 0;
for (int i = 0; i < offsets.Length; i++)
// When we have an empty Tensor and someone wants to slice all of it, we should return an empty Tensor.
if (TensorSpanHelpers.CalculateTotalLength(Lengths) == 0)
{
index += Strides[i] * (offsets[i]);
for (int i = 0; i < offsets.Length; i++)
{
if (offsets[i] != 0 || lengths[i] != 0)
michaelgsharp marked this conversation as resolved.
Show resolved Hide resolved
ThrowHelper.ThrowIndexOutOfRangeException();
}
toReturn = new ReadOnlyTensorSpan<T>(ref _reference, lengths, _shape.Strides, _shape._memoryLength);
michaelgsharp marked this conversation as resolved.
Show resolved Hide resolved
}
else
michaelgsharp marked this conversation as resolved.
Show resolved Hide resolved
{
nint index = 0;
for (int i = 0; i < offsets.Length; i++)
{
if (offsets[i] < 0 || offsets[i] >= Lengths[i])
michaelgsharp marked this conversation as resolved.
Show resolved Hide resolved
ThrowHelper.ThrowIndexOutOfRangeException();

index += Strides[i] * (offsets[i]);
}

if (index >= _shape._memoryLength || index < 0)
ThrowHelper.ThrowIndexOutOfRangeException();

toReturn = new ReadOnlyTensorSpan<T>(ref Unsafe.Add(ref _reference, index), lengths, _shape.Strides, _shape._memoryLength - index);
}

if (index >= _shape._memoryLength || index < 0)
ThrowHelper.ThrowIndexOutOfRangeException();
if (offsetsArray != null)
ArrayPool<nint>.Shared.Return(offsetsArray);
if (lengthsArray != null)
ArrayPool<nint>.Shared.Return(lengthsArray);

return new ReadOnlyTensorSpan<T>(ref Unsafe.Add(ref _reference, index), lengths, _shape.Strides, _shape._memoryLength - index);
return toReturn;
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -690,37 +690,70 @@ public TensorSpan<T> Slice(params scoped ReadOnlySpan<NRange> ranges)
if (ranges.Length != Lengths.Length)
ThrowHelper.ThrowIndexOutOfRangeException();

TensorSpan<T> toReturn;
scoped Span<nint> lengths;
scoped Span<nint> offsets;
nint[]? lengthsArray;
nint[]? offsetsArray;
if (Rank > TensorShape.MaxInlineRank)
{
lengths = stackalloc nint[Rank];
offsets = stackalloc nint[Rank];
lengthsArray = ArrayPool<nint>.Shared.Rent(Rank);
lengths = lengthsArray;
lengths = lengths.Slice(0, Rank);
lengthsArray = null;

offsetsArray = ArrayPool<nint>.Shared.Rent(Rank);
offsets = offsetsArray;
offsets = offsets.Slice(0, Rank);
offsetsArray = null;
michaelgsharp marked this conversation as resolved.
Show resolved Hide resolved
}
else
{
lengths = new nint[Rank];
offsets = new nint[Rank];
lengths = stackalloc nint[Rank];
offsets = stackalloc nint[Rank];

lengthsArray = null;
offsetsArray = null;
}

for (int i = 0; i < ranges.Length; i++)
{
(offsets[i], lengths[i]) = ranges[i].GetOffsetAndLength(Lengths[i]);
}

nint index = 0;
for (int i = 0; i < offsets.Length; i++)
// When we have an empty Tensor and someone wants to slice all of it, we should return an empty Tensor.
if (TensorSpanHelpers.CalculateTotalLength(Lengths) == 0)
{
if (offsets[i] < 0 || offsets[i] >= Lengths[i])
for (int i = 0; i < offsets.Length; i++)
{
if (offsets[i] != 0 || lengths[i] != 0)
ThrowHelper.ThrowIndexOutOfRangeException();
}
toReturn = new TensorSpan<T>(ref _reference, lengths, _shape.Strides, _shape._memoryLength);
}
else
{
nint index = 0;
for (int i = 0; i < offsets.Length; i++)
{
if (offsets[i] < 0 || offsets[i] >= Lengths[i])
ThrowHelper.ThrowIndexOutOfRangeException();

index += Strides[i] * (offsets[i]);
}

if (index >= _shape._memoryLength || index < 0)
ThrowHelper.ThrowIndexOutOfRangeException();

index += Strides[i] * (offsets[i]);
toReturn = new TensorSpan<T>(ref Unsafe.Add(ref _reference, index), lengths, _shape.Strides, _shape._memoryLength - index);
}

if (index >= _shape._memoryLength || index < 0)
ThrowHelper.ThrowIndexOutOfRangeException();
if (offsetsArray != null)
ArrayPool<nint>.Shared.Return(offsetsArray);
if (lengthsArray != null)
ArrayPool<nint>.Shared.Return(lengthsArray);

return new TensorSpan<T>(ref Unsafe.Add(ref _reference, index), lengths, _shape.Strides, _shape._memoryLength - index);
return toReturn;
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -979,6 +979,9 @@ public static void ReadOnlyTensorSpanTryCopyTest()
[Fact]
public static void ReadOnlyTensorSpanSliceTest()
{
// Make sure slicing an empty TensorSpan works
new TensorSpan<double>(Array.Empty<double>()).Slice(new NRange[] { .. });

int[] a = [1, 2, 3, 4, 5, 6, 7, 8, 9];
int[] results = new int[9];
ReadOnlyTensorSpan<int> spanInt = a.AsTensorSpan(3, 3);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1704,6 +1704,9 @@ public static void TensorSpanTryCopyTest()
[Fact]
public static void TensorSpanSliceTest()
{
// Make sure slicing an empty TensorSpan works
new TensorSpan<double>(Array.Empty<double>()).Slice(new NRange[] { .. });
michaelgsharp marked this conversation as resolved.
Show resolved Hide resolved

int[] a = [1, 2, 3, 4, 5, 6, 7, 8, 9];
int[] results = new int[9];
TensorSpan<int> spanInt = a.AsTensorSpan(3, 3);
Expand Down
Loading