You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The ToSnakeCase method in ABP can be optimized using Span for better performance. In dotnet8, a ToSnakeCase method is also provided. I conducted a performance test using Benchmark and compared the three methods. The results show that my algorithm has the best performance, followed by Microsoft's dotnet8. However, when it comes to memory usage, dotnet8 is the best.
Benchmark Result
Method
Mean
Error
StdDev
Rank
Gen0
Allocated
ToSnakeCaseBySpan
52.34 ns
0.899 ns
0.797 ns
1
0.0803
168 B
ToSnakeCaseDotnet8
75.30 ns
0.472 ns
0.442 ns
2
0.0381
80 B
ToSnakeCaseByAbp
111.98 ns
1.160 ns
1.085 ns
3
0.1529
320 B
Benchmark Code
publicstaticstringname="BBCToSnakeCaseBySpanTest2";[Benchmark]publicstringToSnakeCaseBySpan(){intbufferSize=name.Length;ReadOnlySpan<char>nameBuffer=name.AsSpan();Span<char>buffer=newchar[bufferSize+bufferSize/2];intbufferPosition=0;intnamePosition=0;while(bufferPosition<bufferSize){if(char.IsUpper(nameBuffer[namePosition])){if(namePosition==0||namePosition==nameBuffer.Length-1||(char.IsUpper(nameBuffer[namePosition+1])&&char.IsUpper(nameBuffer[namePosition-1]))){buffer[bufferPosition]=char.ToLowerInvariant(nameBuffer[namePosition]);bufferPosition++;}else{buffer[bufferPosition]='_';buffer[bufferPosition+1]=char.ToLowerInvariant(nameBuffer[namePosition]);bufferPosition+=2;bufferSize++;}namePosition++;continue;}buffer[bufferPosition]=nameBuffer[namePosition];bufferPosition++;namePosition++;}returnbuffer.Slice(0,bufferSize).ToString();}[Benchmark]publicstringToSnakeCaseDotnet8(){if(string.IsNullOrEmpty(name)){returnname;}charseparator='_';boollowercase=true;varchars=name.AsSpan();char[]?rentedBuffer=null;// While we can't predict the expansion factor of the resultant string,// start with a buffer that is at least 20% larger than the input.intinitialBufferLength=(int)(1.2*chars.Length);Span<char>destination=initialBufferLength<=128?stackallocchar[128]:(rentedBuffer=ArrayPool<char>.Shared.Rent(initialBufferLength));SeparatorStatestate=SeparatorState.NotStarted;intcharsWritten=0;for(inti=0;i<chars.Length;i++){// NB this implementation does not handle surrogate pair letters// cf. https://github.com/dotnet/runtime/issues/90352charcurrent=chars[i];UnicodeCategorycategory=char.GetUnicodeCategory(current);switch(category){caseUnicodeCategory.UppercaseLetter:switch(state){caseSeparatorState.NotStarted:break;caseSeparatorState.LowercaseLetterOrDigit:caseSeparatorState.SpaceSeparator:// An uppercase letter following a sequence of lowercase letters or spaces// denotes the start of a new grouping: emit a separator character.WriteChar(separator,refdestination);break;caseSeparatorState.UppercaseLetter:// We are reading through a sequence of two or more uppercase letters.// Uppercase letters are grouped together with the exception of the// final letter, assuming it is followed by lowercase letters.// For example, the value 'XMLReader' should render as 'xml_reader',// however 'SHA512Hash' should render as 'sha512-hash'.if(i+1<chars.Length&&char.IsLower(chars[i+1])){WriteChar(separator,refdestination);}break;default:Debug.Fail($"Unexpected state {state}");break;}if(lowercase){current=char.ToLowerInvariant(current);}WriteChar(current,refdestination);state=SeparatorState.UppercaseLetter;break;caseUnicodeCategory.LowercaseLetter:caseUnicodeCategory.DecimalDigitNumber:if(stateisSeparatorState.SpaceSeparator){// Normalize preceding spaces to one separator.WriteChar(separator,refdestination);}if(!lowercase&&categoryisUnicodeCategory.LowercaseLetter){current=char.ToUpperInvariant(current);}WriteChar(current,refdestination);state=SeparatorState.LowercaseLetterOrDigit;break;caseUnicodeCategory.SpaceSeparator:// Space characters are trimmed from the start and end of the input string// but are normalized to separator characters if between letters.if(state!=SeparatorState.NotStarted){state=SeparatorState.SpaceSeparator;}break;default:// Non-alphanumeric characters (including the separator character and surrogates)// are written as-is to the output and reset the separator state.// E.g. 'ABC???def' maps to 'abc???def' in snake_case.WriteChar(current,refdestination);state=SeparatorState.NotStarted;break;}}stringresult=destination.Slice(0,charsWritten).ToString();if(rentedBufferis not null){destination.Slice(0,charsWritten).Clear();ArrayPool<char>.Shared.Return(rentedBuffer);}returnresult;[MethodImpl(MethodImplOptions.AggressiveInlining)]voidWriteChar(charvalue,refSpan<char>destination){if(charsWritten==destination.Length){ExpandBuffer(refdestination);}destination[charsWritten++]=value;}voidExpandBuffer(refSpan<char>destination){intnewSize=checked(destination.Length*2);char[]newBuffer=ArrayPool<char>.Shared.Rent(newSize);destination.CopyTo(newBuffer);if(rentedBuffer!=null){destination.Slice(0,charsWritten).Clear();ArrayPool<char>.Shared.Return(rentedBuffer);}rentedBuffer=newBuffer;destination=rentedBuffer;}}[Benchmark]publicstringToSnakeCaseByAbp(){if(string.IsNullOrWhiteSpace(name)){returnname;}varbuilder=newStringBuilder(name.Length+Math.Min(2,name.Length/5));varpreviousCategory=default(UnicodeCategory?);for(varcurrentIndex=0;currentIndex<name.Length;currentIndex++){varcurrentChar=name[currentIndex];if(currentChar=='_'){builder.Append('_');previousCategory=null;continue;}varcurrentCategory=char.GetUnicodeCategory(currentChar);switch(currentCategory){caseUnicodeCategory.UppercaseLetter:caseUnicodeCategory.TitlecaseLetter:if(previousCategory==UnicodeCategory.SpaceSeparator||previousCategory==UnicodeCategory.LowercaseLetter||previousCategory!=UnicodeCategory.DecimalDigitNumber&&previousCategory!=null&¤tIndex>0&¤tIndex+1<name.Length&&char.IsLower(name[currentIndex+1])){builder.Append('_');}currentChar=char.ToLower(currentChar);break;caseUnicodeCategory.LowercaseLetter:caseUnicodeCategory.DecimalDigitNumber:if(previousCategory==UnicodeCategory.SpaceSeparator){builder.Append('_');}break;default:if(previousCategory!=null){previousCategory=UnicodeCategory.SpaceSeparator;}continue;}builder.Append(currentChar);previousCategory=currentCategory;}returnbuilder.ToString();}internalenumSnakeCaseState{Start,Lower,Upper,NewWord}internalenumSeparatorState{NotStarted,UppercaseLetter,LowercaseLetterOrDigit,SpaceSeparator,}
The text was updated successfully, but these errors were encountered:
Description
The ToSnakeCase method in ABP can be optimized using Span for better performance. In dotnet8, a ToSnakeCase method is also provided. I conducted a performance test using Benchmark and compared the three methods. The results show that my algorithm has the best performance, followed by Microsoft's dotnet8. However, when it comes to memory usage, dotnet8 is the best.
Benchmark Result
Benchmark Code
The text was updated successfully, but these errors were encountered: