public static unsafe void CalculateDiagonalSection_Avx2 <T>(void *refDiag1Ptr, void *refDiag2Ptr, char *sourcePtr, char *targetPtr, ref int rowIndex, int columnIndex) where T : struct { if (typeof(T) == typeof(int)) { var diag1Ptr = (int *)refDiag1Ptr; var diag2Ptr = (int *)refDiag2Ptr; var sourceVector = Avx2.ConvertToVector256Int32((ushort *)sourcePtr + rowIndex - Vector256 <T> .Count); var targetVector = Avx2.ConvertToVector256Int32((ushort *)targetPtr + columnIndex - 1); targetVector = Avx2.Shuffle(targetVector, 0x1b); targetVector = Avx2.Permute2x128(targetVector, targetVector, 1); var substitutionCostAdjustment = Avx2.CompareEqual(sourceVector, targetVector); var substitutionCost = Avx2.Add( Avx.LoadDquVector256(diag1Ptr + rowIndex - Vector256 <T> .Count), substitutionCostAdjustment ); var deleteCost = Avx.LoadDquVector256(diag2Ptr + rowIndex - (Vector256 <T> .Count - 1)); var insertCost = Avx.LoadDquVector256(diag2Ptr + rowIndex - Vector256 <T> .Count); var localCost = Avx2.Min(Avx2.Min(insertCost, deleteCost), substitutionCost); localCost = Avx2.Add(localCost, Vector256.Create(1)); Avx.Store(diag1Ptr + rowIndex - (Vector256 <T> .Count - 1), localCost); } else if (typeof(T) == typeof(ushort)) { var diag1Ptr = (ushort *)refDiag1Ptr; var diag2Ptr = (ushort *)refDiag2Ptr; var sourceVector = Avx.LoadDquVector256((ushort *)sourcePtr + rowIndex - Vector256 <T> .Count); var targetVector = Avx.LoadDquVector256((ushort *)targetPtr + columnIndex - 1); targetVector = Avx2.Shuffle(targetVector.AsByte(), REVERSE_USHORT_AS_BYTE_256).AsUInt16(); targetVector = Avx2.Permute2x128(targetVector, targetVector, 1); var substitutionCostAdjustment = Avx2.CompareEqual(sourceVector, targetVector); var substitutionCost = Avx2.Add( Avx.LoadDquVector256(diag1Ptr + rowIndex - Vector256 <T> .Count), substitutionCostAdjustment ); var deleteCost = Avx.LoadDquVector256(diag2Ptr + rowIndex - (Vector256 <T> .Count - 1)); var insertCost = Avx.LoadDquVector256(diag2Ptr + rowIndex - Vector256 <T> .Count); var localCost = Avx2.Min(Avx2.Min(insertCost, deleteCost), substitutionCost); localCost = Avx2.Add(localCost, Vector256.Create((ushort)1)); Avx.Store(diag1Ptr + rowIndex - (Vector256 <T> .Count - 1), localCost); } }
static unsafe void Main(string[] args) { var bytes = stackalloc sbyte[] { 0x48, 0x64, 0x6A, 0x69, 0x6B, 0x1B, 0x4D, 0x5C, 0x59, 0x63, 0x57, 0x67, 0x14, 0x4A, 0x61, 0x63, 0x5C, 0x53, 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, }; var scalarDiff = stackalloc sbyte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, }; var avx2Diff = stackalloc sbyte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x09, 0x09, 0x0A, 0x11, 0x18, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, }; if (Avx2.IsSupported) { var data = Avx.LoadDquVector256(bytes); var diff = Avx.LoadDquVector256(avx2Diff); Avx.Store(bytes, Avx2.Add(data, diff)); } else { for (var i = 0; i < 32; i++) { bytes[i] += scalarDiff[i]; } } var s = new string(bytes, 0, 32, Encoding.UTF8); Console.WriteLine(s); }
internal static unsafe Vector256 <T> LoadDquVector256(T *address) { if (typeof(T) == typeof(sbyte)) { return(Avx.LoadDquVector256((sbyte *)address).As <sbyte, T>()); } if (typeof(T) == typeof(byte)) { return(Avx.LoadDquVector256((byte *)address).As <byte, T>()); } if (typeof(T) == typeof(short)) { return(Avx.LoadDquVector256((short *)address).As <short, T>()); } if (typeof(T) == typeof(ushort)) { return(Avx.LoadDquVector256((ushort *)address).As <ushort, T>()); } if (typeof(T) == typeof(int)) { return(Avx.LoadDquVector256((int *)address).As <int, T>()); } if (typeof(T) == typeof(uint)) { return(Avx.LoadDquVector256((uint *)address).As <uint, T>()); } if (typeof(T) == typeof(long)) { return(Avx.LoadDquVector256((long *)address).As <long, T>()); } if (typeof(T) == typeof(ulong)) { return(Avx.LoadDquVector256((ulong *)address).As <ulong, T>()); } throw new NotSupportedException(); }
static unsafe int Main(string[] args) { int testResult = Pass; if (Avx.IsSupported) { using (TestTable <int> intTable = new TestTable <int>(new int[8] { 1, -5, 100, 0, 1, 2, 3, 4 }, new int[8])) { var vf = Avx.LoadDquVector256((int *)(intTable.inArrayPtr)); Unsafe.Write(intTable.outArrayPtr, vf); if (!intTable.CheckResult((x, y) => x == y)) { Console.WriteLine("AVX LoadDquVector256 failed on int:"); foreach (var item in intTable.outArray) { Console.Write(item + ", "); } Console.WriteLine(); testResult = Fail; } } using (TestTable <uint> intTable = new TestTable <uint>(new uint[8] { 1, 5, 100, 0, 1, 2, 3, 4 }, new uint[8])) { var vf = Avx.LoadDquVector256((uint *)(intTable.inArrayPtr)); Unsafe.Write(intTable.outArrayPtr, vf); if (!intTable.CheckResult((x, y) => x == y)) { Console.WriteLine("AVX LoadDquVector256 failed on uint:"); foreach (var item in intTable.outArray) { Console.Write(item + ", "); } Console.WriteLine(); testResult = Fail; } } using (TestTable <long> intTable = new TestTable <long>(new long[4] { 1, -5, 100, 0 }, new long[4])) { var vf = Avx.LoadDquVector256((long *)(intTable.inArrayPtr)); Unsafe.Write(intTable.outArrayPtr, vf); if (!intTable.CheckResult((x, y) => x == y)) { Console.WriteLine("AVX LoadDquVector256 failed on long:"); foreach (var item in intTable.outArray) { Console.Write(item + ", "); } Console.WriteLine(); testResult = Fail; } } using (TestTable <ulong> intTable = new TestTable <ulong>(new ulong[4] { 1, 5, 100, 0 }, new ulong[4])) { var vf = Avx.LoadDquVector256((ulong *)(intTable.inArrayPtr)); Unsafe.Write(intTable.outArrayPtr, vf); if (!intTable.CheckResult((x, y) => x == y)) { Console.WriteLine("AVX LoadDquVector256 failed on ulong:"); foreach (var item in intTable.outArray) { Console.Write(item + ", "); } Console.WriteLine(); testResult = Fail; } } using (TestTable <short> intTable = new TestTable <short>(new short[16] { 1, -5, 100, 0, 1, 2, 3, 4, 1, -5, 100, 0, 1, 2, 3, 4 }, new short[16])) { var vf = Avx.LoadDquVector256((short *)(intTable.inArrayPtr)); Unsafe.Write(intTable.outArrayPtr, vf); if (!intTable.CheckResult((x, y) => x == y)) { Console.WriteLine("AVX LoadDquVector256 failed on short:"); foreach (var item in intTable.outArray) { Console.Write(item + ", "); } Console.WriteLine(); testResult = Fail; } } using (TestTable <ushort> intTable = new TestTable <ushort>(new ushort[16] { 1, 5, 100, 0, 1, 2, 3, 4, 1, 5, 100, 0, 1, 2, 3, 4 }, new ushort[16])) { var vf = Avx.LoadDquVector256((ushort *)(intTable.inArrayPtr)); Unsafe.Write(intTable.outArrayPtr, vf); if (!intTable.CheckResult((x, y) => x == y)) { Console.WriteLine("AVX LoadDquVector256 failed on ushort:"); foreach (var item in intTable.outArray) { Console.Write(item + ", "); } Console.WriteLine(); testResult = Fail; } } using (TestTable <byte> intTable = new TestTable <byte>(new byte[32] { 1, 5, 100, 0, 1, 2, 3, 4, 1, 5, 100, 0, 1, 2, 3, 4, 1, 5, 100, 0, 1, 2, 3, 4, 1, 5, 100, 0, 1, 2, 3, 4 }, new byte[32])) { var vf = Avx.LoadDquVector256((byte *)(intTable.inArrayPtr)); Unsafe.Write(intTable.outArrayPtr, vf); if (!intTable.CheckResult((x, y) => x == y)) { Console.WriteLine("AVX LoadDquVector256 failed on byte:"); foreach (var item in intTable.outArray) { Console.Write(item + ", "); } Console.WriteLine(); testResult = Fail; } } using (TestTable <sbyte> intTable = new TestTable <sbyte>(new sbyte[32] { 1, -5, 100, 0, 1, 2, 3, 4, 1, -5, 100, 0, 1, 2, 3, 4, 1, -5, 100, 0, 1, 2, 3, 4, 1, -5, 100, 0, 1, 2, 3, 4 }, new sbyte[32])) { var vf = Avx.LoadDquVector256((sbyte *)(intTable.inArrayPtr)); Unsafe.Write(intTable.outArrayPtr, vf); if (!intTable.CheckResult((x, y) => x == y)) { Console.WriteLine("AVX LoadDquVector256 failed on sbyte:"); foreach (var item in intTable.outArray) { Console.Write(item + ", "); } Console.WriteLine(); testResult = Fail; } } } return(testResult); }
public static unsafe void TrimLengthOfMatchingCharacters(char *sourcePtr, char *targetPtr, ref int sourceLength, ref int targetLength) { var searchLength = Math.Min(sourceLength, targetLength); #if NETCOREAPP var sourceUShortPtr = (ushort *)sourcePtr; var targetUShortPtr = (ushort *)targetPtr; if (Sse2.IsSupported && searchLength >= Vector128 <ushort> .Count * 2) { if (Avx2.IsSupported) { while (searchLength >= Vector256 <ushort> .Count) { var sourceVector = Avx.LoadDquVector256(sourceUShortPtr + sourceLength - Vector256 <ushort> .Count); var targetVector = Avx.LoadDquVector256(targetUShortPtr + targetLength - Vector256 <ushort> .Count); var match = (uint)Avx2.MoveMask( Avx2.CompareEqual( sourceVector, targetVector ).AsByte() ); if (match == uint.MaxValue) { sourceLength -= Vector256 <ushort> .Count; targetLength -= Vector256 <ushort> .Count; searchLength -= Vector256 <ushort> .Count; continue; } var lastMatch = BitOperations.LeadingZeroCount(match ^ uint.MaxValue) / sizeof(ushort); sourceLength -= lastMatch; targetLength -= lastMatch; return; } } while (searchLength >= Vector128 <ushort> .Count) { var sourceVector = Sse2.LoadVector128(sourceUShortPtr + sourceLength - Vector128 <ushort> .Count); var targetVector = Sse2.LoadVector128(targetUShortPtr + targetLength - Vector128 <ushort> .Count); var match = (uint)Sse2.MoveMask( Sse2.CompareEqual( sourceVector, targetVector ).AsByte() ); if (match == ushort.MaxValue) { sourceLength -= Vector128 <ushort> .Count; targetLength -= Vector128 <ushort> .Count; searchLength -= Vector128 <ushort> .Count; continue; } var lastMatch = BitOperations.LeadingZeroCount(match ^ ushort.MaxValue) / sizeof(ushort) - Vector128 <ushort> .Count; sourceLength -= lastMatch; targetLength -= lastMatch; return; } } #endif sourcePtr += sourceLength - 1; targetPtr += targetLength - 1; while (searchLength > 0 && sourcePtr[0] == targetPtr[0]) { sourcePtr--; targetPtr--; sourceLength--; targetLength--; searchLength--; } }
public static unsafe int GetIndexOfFirstNonMatchingCharacter(char *sourcePtr, char *targetPtr, int sourceLength, int targetLength) { var searchLength = Math.Min(sourceLength, targetLength); var index = 0; #if NETCOREAPP var sourceUShortPtr = (ushort *)sourcePtr; var targetUShortPtr = (ushort *)targetPtr; if (Sse2.IsSupported && searchLength >= Vector128 <ushort> .Count * 2) { if (Avx2.IsSupported) { while (searchLength >= Vector256 <ushort> .Count) { var sourceVector = Avx.LoadDquVector256(sourceUShortPtr + index); var targetVector = Avx.LoadDquVector256(targetUShortPtr + index); var match = (uint)Avx2.MoveMask( Avx2.CompareEqual( sourceVector, targetVector ).AsByte() ); if (match == uint.MaxValue) { index += Vector256 <ushort> .Count; searchLength -= Vector256 <ushort> .Count; continue; } index += BitOperations.TrailingZeroCount(match ^ uint.MaxValue) / sizeof(ushort); return(index); } } while (searchLength >= Vector128 <ushort> .Count) { var sourceVector = Sse2.LoadVector128(sourceUShortPtr + index); var targetVector = Sse2.LoadVector128(targetUShortPtr + index); var match = (uint)Sse2.MoveMask( Sse2.CompareEqual( sourceVector, targetVector ).AsByte() ); if (match == ushort.MaxValue) { index += Vector128 <ushort> .Count; searchLength -= Vector128 <ushort> .Count; continue; } index += BitOperations.TrailingZeroCount(match ^ ushort.MaxValue) / sizeof(ushort); return(index); } } #endif while (searchLength > 0 && sourcePtr[index] == targetPtr[index]) { searchLength--; index++; } return(index); }
public unsafe static int GetDistance(string source, string target) { var startIndex = 0; var sourceEnd = source.Length; var targetEnd = target.Length; fixed(char *sourcePtr = source) fixed(char *targetPtr = target) { var charactersAvailableToTrim = Math.Min(targetEnd, sourceEnd); if (Avx2.IsSupported) { var sourceUShortPtr = (ushort *)sourcePtr; var targetUShortPtr = (ushort *)targetPtr; while (charactersAvailableToTrim >= Vector256 <ushort> .Count) { var match = (uint)Avx2.MoveMask( Avx2.CompareEqual( Avx.LoadDquVector256(sourceUShortPtr + startIndex), Avx.LoadDquVector256(targetUShortPtr + startIndex) ).AsByte() ); if (match != uint.MaxValue) { var remaining = BitOperations.TrailingZeroCount(match ^ uint.MaxValue) / sizeof(ushort); startIndex += remaining; charactersAvailableToTrim -= remaining; break; } startIndex += Vector256 <ushort> .Count; charactersAvailableToTrim -= Vector256 <ushort> .Count; } while (charactersAvailableToTrim >= Vector256 <ushort> .Count) { var match = (uint)Avx2.MoveMask( Avx2.CompareEqual( Avx.LoadDquVector256(sourceUShortPtr + sourceEnd - Vector256 <ushort> .Count), Avx.LoadDquVector256(targetUShortPtr + targetEnd - Vector256 <ushort> .Count) ).AsByte() ); if (match != uint.MaxValue) { var lastMatch = BitOperations.LeadingZeroCount(match ^ uint.MaxValue) / sizeof(ushort); sourceEnd -= lastMatch; targetEnd -= lastMatch; break; } sourceEnd -= Vector256 <ushort> .Count; targetEnd -= Vector256 <ushort> .Count; charactersAvailableToTrim -= Vector256 <ushort> .Count; } } while (charactersAvailableToTrim > 0 && source[startIndex] == target[startIndex]) { charactersAvailableToTrim--; startIndex++; } while (charactersAvailableToTrim > 0 && source[sourceEnd - 1] == target[targetEnd - 1]) { charactersAvailableToTrim--; sourceEnd--; targetEnd--; } } var sourceLength = sourceEnd - startIndex; var targetLength = targetEnd - startIndex; if (sourceLength == 0) { return(targetLength); } if (targetLength == 0) { return(sourceLength); } var sourceSpan = source.AsSpan().Slice(startIndex, sourceLength); var targetSpan = target.AsSpan().Slice(startIndex, targetLength); var previousRow = ArrayPool <int> .Shared.Rent(targetSpan.Length); var allOnesVector = Vector128.Create(1); fixed(int *previousRowPtr = previousRow) fixed(char *sourcePtr = sourceSpan) fixed(char *targetPtr = target) { for (var columnIndex = 0; columnIndex < targetLength; columnIndex++) { previousRowPtr[columnIndex] = columnIndex; } for (var rowIndex = 0; rowIndex < sourceLength; rowIndex++) { var lastSubstitutionCost = rowIndex; var lastInsertionCost = rowIndex + 1; var sourceChar = sourcePtr[rowIndex]; if (Sse41.IsSupported) { var lastSubstitutionCostVector = Vector128.Create(lastSubstitutionCost); var lastInsertionCostVector = Vector128.Create(lastInsertionCost); for (var columnIndex = 0; columnIndex < targetLength; columnIndex++) { var localCostVector = lastSubstitutionCostVector; var lastDeletionCostVector = Vector128.Create(previousRowPtr[columnIndex]); if (sourceChar != targetPtr[columnIndex]) { localCostVector = Sse2.Add( Sse41.Min( Sse41.Min( lastInsertionCostVector, localCostVector ), lastDeletionCostVector ), allOnesVector ); } lastInsertionCostVector = localCostVector; previousRowPtr[columnIndex] = localCostVector.GetElement(0); lastSubstitutionCostVector = lastDeletionCostVector; } } else { for (var columnIndex = 0; columnIndex < targetLength; columnIndex++) { var localCost = lastSubstitutionCost; var deletionCost = previousRowPtr[columnIndex]; if (sourceChar != targetPtr[columnIndex]) { localCost = Math.Min(lastInsertionCost, localCost); localCost = Math.Min(deletionCost, localCost); localCost++; } lastInsertionCost = localCost; previousRowPtr[columnIndex] = localCost; lastSubstitutionCost = deletionCost; } } } } var result = previousRow[targetSpan.Length - 1]; ArrayPool <int> .Shared.Return(previousRow); return(result); }
public unsafe static int GetDistance(string source, string target) { var startIndex = 0; var sourceEnd = source.Length; var targetEnd = target.Length; fixed(char *sourcePtr = source) fixed(char *targetPtr = target) { var charactersAvailableToTrim = Math.Min(targetEnd, sourceEnd); if (Avx2.IsSupported) { var sourceUShortPtr = (ushort *)sourcePtr; var targetUShortPtr = (ushort *)targetPtr; while (charactersAvailableToTrim >= Vector256 <ushort> .Count) { var match = (uint)Avx2.MoveMask( Avx2.CompareEqual( Avx.LoadDquVector256(sourceUShortPtr + startIndex), Avx.LoadDquVector256(targetUShortPtr + startIndex) ).AsByte() ); if (match != uint.MaxValue) { var remaining = BitOperations.TrailingZeroCount(match ^ uint.MaxValue) / sizeof(ushort); startIndex += remaining; charactersAvailableToTrim -= remaining; break; } startIndex += Vector256 <ushort> .Count; charactersAvailableToTrim -= Vector256 <ushort> .Count; } while (charactersAvailableToTrim >= Vector256 <ushort> .Count) { var match = (uint)Avx2.MoveMask( Avx2.CompareEqual( Avx.LoadDquVector256(sourceUShortPtr + sourceEnd - Vector256 <ushort> .Count), Avx.LoadDquVector256(targetUShortPtr + targetEnd - Vector256 <ushort> .Count) ).AsByte() ); if (match != uint.MaxValue) { var lastMatch = BitOperations.LeadingZeroCount(match ^ uint.MaxValue) / sizeof(ushort); sourceEnd -= lastMatch; targetEnd -= lastMatch; break; } sourceEnd -= Vector256 <ushort> .Count; targetEnd -= Vector256 <ushort> .Count; charactersAvailableToTrim -= Vector256 <ushort> .Count; } } while (charactersAvailableToTrim > 0 && source[startIndex] == target[startIndex]) { charactersAvailableToTrim--; startIndex++; } while (charactersAvailableToTrim > 0 && source[sourceEnd - 1] == target[targetEnd - 1]) { charactersAvailableToTrim--; sourceEnd--; targetEnd--; } } var sourceLength = sourceEnd - startIndex; var targetLength = targetEnd - startIndex; if (sourceLength == 0) { return(targetLength); } if (targetLength == 0) { return(sourceLength); } var sourceSpan = source.AsSpan().Slice(startIndex, sourceLength); var targetSpan = target.AsSpan().Slice(startIndex, targetLength); var previousRow = ArrayPool <int> .Shared.Rent(targetSpan.Length); var allOnesVector = Vector128.Create(1); fixed(int *previousRowPtr = previousRow) fixed(char *sourcePtr = sourceSpan) fixed(char *targetPtr = target) { var maximumNumberOfWorkers = Environment.ProcessorCount; var numberOfWorkers = targetLength / MINIMUM_CHARACTERS_PER_THREAD; if (numberOfWorkers == 0) { numberOfWorkers = 1; } else if (numberOfWorkers > maximumNumberOfWorkers) { numberOfWorkers = maximumNumberOfWorkers; } var numberOfColumnsPerWorker = targetLength / numberOfWorkers; var remainderColumns = targetLength % numberOfWorkers; var rowCountPtr = stackalloc int[Environment.ProcessorCount]; var columnBoundariesPool = ArrayPool <int[]> .Shared.Rent(numberOfWorkers + 1); //Initialise shared task boundaries for (var i = 0; i < numberOfWorkers + 1; i++) { columnBoundariesPool[i] = ArrayPool <int> .Shared.Rent(sourceLength + 1); columnBoundariesPool[i][0] = i * numberOfColumnsPerWorker; } columnBoundariesPool[numberOfWorkers][0] += remainderColumns; //Fill first column boundary (ColumnIndex = 0) with incrementing numbers fixed(int *startBoundaryPtr = columnBoundariesPool[0]) { for (var rowIndex = 0; rowIndex <= sourceLength; rowIndex++) { startBoundaryPtr[rowIndex] = rowIndex; } } for (var workerIndex = 0; workerIndex < numberOfWorkers - 1; workerIndex++) { var columnIndex = workerIndex * numberOfColumnsPerWorker; ThreadPool.QueueUserWorkItem(WorkerTask, new WorkerState { RowCountPtr = rowCountPtr, WorkerIndex = workerIndex, ColumnIndex = columnIndex, SourcePtr = sourcePtr, SourceLength = sourceLength, TargetRegionPtr = targetPtr + columnIndex, TargetRegionLength = numberOfColumnsPerWorker, BackColumnBoundary = columnBoundariesPool[workerIndex], ForwardColumnBoundary = columnBoundariesPool[workerIndex + 1] }); } //Run last segment synchronously (ie. in the current thread) var lastWorkerIndex = numberOfWorkers - 1; var lastWorkerColumnIndex = lastWorkerIndex * numberOfColumnsPerWorker; WorkerTask_CalculateRegion(new WorkerState { RowCountPtr = rowCountPtr, WorkerIndex = numberOfWorkers - 1, ColumnIndex = (numberOfWorkers - 1) * numberOfColumnsPerWorker, SourcePtr = sourcePtr, SourceLength = sourceLength, TargetRegionPtr = targetPtr + lastWorkerColumnIndex, TargetRegionLength = numberOfColumnsPerWorker + remainderColumns, BackColumnBoundary = columnBoundariesPool[lastWorkerIndex], ForwardColumnBoundary = columnBoundariesPool[lastWorkerIndex + 1] }); //Extract last value in forward column boundary of last task (the actual distance) var result = columnBoundariesPool[numberOfWorkers][sourceLength]; //Cleanup //Return all column boundaries then the container of boundaries for (var i = 0; i < numberOfWorkers + 1; i++) { ArrayPool <int> .Shared.Return(columnBoundariesPool[i]); } ArrayPool <int[]> .Shared.Return(columnBoundariesPool); return(result); } }