| if(Avx2.IsSupported) |
| { |
| if(offset<(nuint)(uint)length) |
| { |
| if((((nuint)(uint)Unsafe.AsPointer(refsearchSpace)+offset)&(nuint)(Vector256<byte>.Count-1))!=0) |
| { |
| // Not currently aligned to Vector256 (is aligned to Vector128); this can cause a problem for searches |
| // with no upper bound e.g. String.strlen. |
| // Start with a check on Vector128 to align to Vector256, before moving to processing Vector256. |
| // This ensures we do not fault across memory pages while searching for an end of string. |
| Vector128<byte>values=Vector128.Create(value); |
| Vector128<byte>search=LoadVector128(refsearchSpace,offset); |
| |
| // Same method as below |
| intmatches=Sse2.MoveMask(Sse2.CompareEqual(values,search)); |
| if(matches==0) |
| { |
| // Zero flags set so no matches |
| offset+=(nuint)Vector128<byte>.Count; |
| } |
| else |
| { |
| // Find bitflag offset of first match and add to current offset |
| return(int)(offset+(uint)BitOperations.TrailingZeroCount(matches)); |
| } |
| } |
| |
| lengthToExamine=GetByteVector256SpanLength(offset,length); |
| if(lengthToExamine>offset) |
| { |
| Vector256<byte>values=Vector256.Create(value); |
| do |
| { |
| Vector256<byte>search=LoadVector256(refsearchSpace,offset); |
| intmatches=Avx2.MoveMask(Avx2.CompareEqual(values,search)); |
| // Note that MoveMask has converted the equal vector elements into a set of bit flags, |
| // So the bit position in 'matches' corresponds to the element offset. |
| if(matches==0) |
| { |
| // Zero flags set so no matches |
| offset+=(nuint)Vector256<byte>.Count; |
| continue; |
| } |
| |
| // Find bitflag offset of first match and add to current offset |
| return(int)(offset+(uint)BitOperations.TrailingZeroCount(matches)); |
| }while(lengthToExamine>offset); |
| } |
| |
| lengthToExamine=GetByteVector128SpanLength(offset,length); |
| if(lengthToExamine>offset) |
| { |
| Vector128<byte>values=Vector128.Create(value); |
| Vector128<byte>search=LoadVector128(refsearchSpace,offset); |
| |
| // Same method as above |
| intmatches=Sse2.MoveMask(Sse2.CompareEqual(values,search)); |
| if(matches==0) |
| { |
| // Zero flags set so no matches |
| offset+=(nuint)Vector128<byte>.Count; |
| } |
| else |
| { |
| // Find bitflag offset of first match and add to current offset |
| return(int)(offset+(uint)BitOperations.TrailingZeroCount(matches)); |
| } |
| } |
| |
| if(offset<(nuint)(uint)length) |
| { |
| lengthToExamine=((nuint)(uint)length-offset); |
| gotoSequentialScan; |
| } |
| } |
| } |
| elseif(Sse2.IsSupported) |
| { |
| if(offset<(nuint)(uint)length) |
| { |
| lengthToExamine=GetByteVector128SpanLength(offset,length); |
| |
| Vector128<byte>values=Vector128.Create(value); |
| while(lengthToExamine>offset) |
| { |
| Vector128<byte>search=LoadVector128(refsearchSpace,offset); |
| |
| // Same method as above |
| intmatches=Sse2.MoveMask(Sse2.CompareEqual(values,search)); |
| if(matches==0) |
| { |
| // Zero flags set so no matches |
| offset+=(nuint)Vector128<byte>.Count; |
| continue; |
| } |
| |
| // Find bitflag offset of first match and add to current offset |
| return(int)(offset+(uint)BitOperations.TrailingZeroCount(matches)); |
| } |
| |
| if(offset<(nuint)(uint)length) |
| { |
| lengthToExamine=((nuint)(uint)length-offset); |
| gotoSequentialScan; |
| } |
| } |
| } |
| elseif(AdvSimd.Arm64.IsSupported) |
| { |
| if(offset<(nuint)(uint)length) |
| { |
| lengthToExamine=GetByteVector128SpanLength(offset,length); |
| |
| // Mask to help find the first lane in compareResult that is set. |
| // MSB 0x10 corresponds to 1st lane, 0x01 corresponds to 0th lane and so forth. |
| Vector128<byte>mask=Vector128.Create((ushort)0x1001).AsByte(); |
| intmatchedLane=0; |
| |
| Vector128<byte>values=Vector128.Create(value); |
| while(lengthToExamine>offset) |
| { |
| Vector128<byte>search=LoadVector128(refsearchSpace,offset); |
| Vector128<byte>compareResult=AdvSimd.CompareEqual(values,search); |
| |
| if(!TryFindFirstMatchedLane(mask,compareResult,refmatchedLane)) |
| { |
| // Zero flags set so no matches |
| offset+=(nuint)Vector128<byte>.Count; |
| continue; |
| } |
| |
| return(int)(offset+(uint)matchedLane); |
| } |
| |
| if(offset<(nuint)(uint)length) |
| { |
| lengthToExamine=((nuint)(uint)length-offset); |
| gotoSequentialScan; |
| } |
| } |
| } |
| elseif(Vector.IsHardwareAccelerated) |
| { |
| if(offset<(nuint)(uint)length) |
| { |
| lengthToExamine=GetByteVectorSpanLength(offset,length); |
| |
| Vector<byte>values=newVector<byte>(value); |
| |
| while(lengthToExamine>offset) |
| { |
| varmatches=Vector.Equals(values,LoadVector(refsearchSpace,offset)); |
| if(Vector<byte>.Zero.Equals(matches)) |
| { |
| offset+=(nuint)Vector<byte>.Count; |
| continue; |
| } |
| |
| // Find offset of first match and add to current offset |
| return(int)offset+LocateFirstFoundByte(matches); |
| } |
| |
| if(offset<(nuint)(uint)length) |
| { |
| lengthToExamine=((nuint)(uint)length-offset); |
| gotoSequentialScan; |
| } |
| } |
| } |
We have a non-trivial number of implementations that duplicate loops across multiple hardware instruction sets plus a generalized Vector path, e.g.
runtime/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.Byte.cs
Lines 266 to 434 in 9f93bcb
has a structure that's approximately:
We have others like:
runtime/src/libraries/System.Collections/src/System/Collections/BitArray.cs
Lines 152 to 224 in 9f93bcb
that similarly have paths for Avx2, Sse2, and AdvSimd but without a Vector path.
This is a lot of complicated code being duplicated, and it should no longer be necessary. In most cases, the path using
Vector<T>should produce code identical to what we'd otherwise write manually using hardware intrinsics, automatically picking 32-bytes or 16-bytes based on what the hardware supports. So, for any of these cases that do Avx2/Sse2/AdvSimd/Vector, we should theoretically be able to simply delete the Avx2/Sse2/AdvSimd code paths, leaving only the Vector code path, because if the Avx2 path would be supported, then Vector would similarly use 32-byte widths, and if Avx2 wouldn't be supported but Sse2/AdvSimd would, Vector would similarly use 16-byte widths. And for the cases that today use Avx2/Sse2/AdvSimd without a Vector path, we should be able to add a path for Vector and then delete the others, for the same reason.For paths that don't have an Avx2 option, and instead just have paths for Sse2/AdvSimd/Vector, it's less clear whether we could delete the Sse2/AdvSimd implementations, as it's possible there could be performance differences (for better or worse) if the Vector path ended up using 32-byte widths.
cc: @tannergooding, @benaadams