Quantcast
Channel: C#タグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 9312

Spanのつかいみち

$
0
0

これは、 C# Advent Calendar 2019の 10 日目の記事です(遅刻すみません!)。
前の記事は、 @Xelticaさんの C# 用ゲームエンジンを自作した話です。

.NET Core 2.1 で使えるようになってしばらくたった Span<T>ですが、まだ使えてないよ〜って C#er のみなさんもまだまだいらっしゃると思うので、ぼくが書いたコードを晒していきます。
もっと速くなるよ、とかあれば教えてください!

文字列系

ここでのコツは、 Span<char>#ToString()をうまく使うことと、 stackalloc でスタック領域を活用することです。
ヒープアロケーションを極力避けることで、高速に動作させます。

byte[] -> 16 進数 への変換

byte 配列を 16 進数の文字列に変換する拡張メソッドです。

/// <summary>/// 16 進数の文字列に変換します/// </summary>publicstaticstringToHexString(thisbyte[]source,boolupperCase=false){Span<char>buffer=stackallocchar[source.Length*2];source.WriteAsHexChars(buffer,out_,upperCase);returnbuffer.ToString();}/// <summary>/// バイト列を 16 進数の列として書き込みます/// </summary>publicstaticvoidWriteAsHexChars(thisReadOnlySpan<byte>bytes,Span<char>dest,outintcharsWritten,boolupperCase=false){charsWritten=0;foreach(bytebinbytes){b.WriteAsHexChars(dest.Slice(charsWritten),outvarcount,upperCase);charsWritten+=count;}}/// <summary>/// バイトを 16 進数として書き込みます/// </summary>[MethodImpl(MethodImplOptions.AggressiveInlining)]publicstaticvoidWriteAsHexChars(thisbyteb,Span<char>dest,outintcharsWritten,boolupperCase=false){ReadOnlySpan<char>source=upperCase?s_byteToHexUpper[b]:s_byteToHexLower[b];source.CopyTo(dest);charsWritten=source.Length;// 2 固定のはず}// byte -> char[] に変換するための 配列privatestaticreadonlychar[][]s_byteToHexLower=Enumerable.Range(0,256).Select(x=>((byte)x).ToString("x2").ToCharArray()).ToArray();privatestaticreadonlychar[][]s_byteToHexUpper=Enumerable.Range(0,256).Select(x=>((byte)x).ToString("X2").ToCharArray()).ToArray();

snake_case -> PascalCase への変換

スネークケースからパスカルケースへの変換をします。これは他の変換 (例: Pascal -> snake) にも応用できます。

publicstaticstringSnakeCaseToPascalCase(thisstringsnake){ReadOnlySpan<char>snakeSpan=snake;Span<char>buffer=stackallocchar[snakeSpan.Length];intbufferPos=0;booltoUpper=true;for(vari=0;i<snake.Length;i++){vartarget=snakeSpan[i];if(target=='_'){toUpper=true;}else{buffer[bufferPos++]=toUpper?char.ToUpper(target):target;toUpper=false;}}returnbuffer.Slice(0,bufferPos).ToString();}

MD5 ハッシュの計算

MD5 ハッシュの計算にも Span が活用できます。
MD5CryptoServiceProvider はすこしラップしてあげると使いやすくなりますが、ここでも Span を活用します。
Span のコツは、できるだけ最後まで Span で扱うこと (= スタックを最大限に活用)、だと思ってます。
さっきの byte[] -> 16 進への変換も利用して、16 進数の書き込みまで Span だけでやってます。

/// <summary>/// MD5 ヘルパ/// </summary>publicstaticclassMd5HashHelper{// md5 インスタンスキャッシュ// このインスタンスはメソッド呼び出しによって内部状態が遷移するため// かならず内部状態の初期化を行うこと。// 呼び出し後に自動的に内部状態が初期化されるメソッドは ComputeHash, TryComputeHash のみです。[ThreadStatic]privatestaticMD5CryptoServiceProvidert_md5=null;/// <summary>/// MD5 ハッシュのバイト長/// </summary>publicconstintHashBytesLength=16;/// <summary>/// MD5 ハッシュの十六進表記の文字数/// </summary>publicconstintHashHexStringLength=32;/// <summary>/// キャッシュを使用するか/// </summary>/// <remarks>/// true の場合、 MD5CryptoServiceProvider のインスタンスをスレッドごとにキャッシュして使用します。/// false の場合は毎回新しい MD5CryptoServiceProvider のインスタンスを使用します。/// </remarks>publicstaticboolUseCache{get;set;}=true;/// <summary>/// 指定したバイト配列のハッシュ値を計算します。/// </summary>publicstaticbyte[]ComputeHash(byte[]buffer){returnComputeHash(buffer,0,buffer.Length);}/// <summary>/// 指定したバイト配列の指定した領域のハッシュ値を計算します。/// </summary>publicstaticbyte[]ComputeHash(byte[]buffer,intoffset,intcount){varmd5=RentUnsafe();varresult=md5.ComputeHash(buffer,offset,count);ReturnUnsafe(md5);returnresult;}/// <summary>/// 指定した Stream オブジェクトのハッシュ値を計算します/// </summary>publicstaticbyte[]ComputeHash(StreaminputStream){varmd5=RentUnsafe();varresult=md5.ComputeHash(inputStream);ReturnUnsafe(md5);returnresult;}/// <summary>/// 入力バイト列のハッシュ値を計算し、出力バイト列にコピーします。/// </summary>publicstaticboolTryComputeHash(ReadOnlySpan<byte>source,Span<byte>destination,outintbytesWritten){varmd5=RentUnsafe();varresult=md5.TryComputeHash(source,destination,outbytesWritten);ReturnUnsafe(md5);returnresult;}/// <summary>/// 入力バイト列のハッシュ値を計算し、16 進数表記で出力文字列にコピーします/// </summary>publicstaticboolTryComputeHash(ReadOnlySpan<byte>source,Span<char>destination,outintcharsWritten,boolupperCase=false){Span<byte>buffer=stackallocbyte[16];if(TryComputeHash(source,buffer,outvarbytesWritten)){((ReadOnlySpan<byte>)buffer).WriteAsHexChars(destination,outcharsWritten,upperCase);returntrue;}else{charsWritten=0;returnfalse;}}/// <summary>/// MD5 インスタンスを借用します。/// </summary>privatestaticMD5CryptoServiceProviderRentUnsafe(){if(!UseCache||t_md5==null){returnnewMD5CryptoServiceProvider();}else{varmd5=t_md5;t_md5=null;returnmd5;}}/// <summary>/// MD5 インスタンスをキャッシュに返却します。/// ※返却前に内部状態が汚染されたインスタンスを返却しないこと。/// </summary>privatestaticvoidReturnUnsafe(MD5CryptoServiceProvidermd5){if(UseCache&&t_md5==null&&md5!=null){t_md5=md5;}}}

Utf8Json のカスタムフォーマッタ

JSON のフォーマッタを書くときも Span を活用できます。

Enum を CamelCase にするフォーマッタ

PascalCase な enum メンバを CamelCase でシリアライズしたいことがあって書いてみました。

publicclassCamelCaseEnumFormatter<T>:IJsonFormatter<T>whereT:struct{publicvoidSerialize(refJsonWriterwriter,Tvalue,IJsonFormatterResolverformatterResolver){varstr=value.ToString();Span<char>buffer=stackallocchar[str.Length];str.AsSpan().CopyTo(buffer);buffer[0]=char.ToLower(buffer[0]);writer.WriteString(buffer.ToString());}publicTDeserialize(refJsonReaderreader,IJsonFormatterResolverformatterResolver){varstr=reader.ReadString();returnEnum.Parse<T>(str,true);}}

おまけ: StringBuilder

これは半分遊びですが、スタック上で複雑な文字列を構築していきたいときのための、StringBuilder の Span 実装です。

publicrefstructSpanStringBuilder{privateSpan<char>_buffer;privateint_index;publicSpanStringBuilder(Span<char>buffer){_buffer=buffer;_index=0;}publicvoidWrite(ReadOnlySpan<char>str){str.CopyTo(_buffer.Slice(_index));_index+=str.Length;}publicvoidWriteLine(ReadOnlySpan<char>str){Write(str);Write(Environment.NewLine);}publicvoidWrite(charc){_buffer[_index++]=c;}publicvoidWrite(intvalue){if(!value.TryFormat(_buffer.Slice(_index),outvarcharsWritten)){thrownewOutOfMemoryException("buffer のサイズが足りません");}_index+=charsWritten;}publicSpan<char>AsSpan()=>_buffer.Slice(0,_index);publicoverridestringToString()=>AsSpan().ToString();}

Viewing all articles
Browse latest Browse all 9312

Latest Images

Trending Articles