Hello Dark-Side C# (Part. 1)

50 %
50 %
Information about Hello Dark-Side C# (Part. 1)

Published on December 6, 2016

Author: YutoTakei

Source: slideshare.net

1. Hello Dark-Side C# (Part. 1) Yuto Takei Software Engineer bitFlyer Inc.

2. 免責 このトークは、情報提供のみを目的として行われており、正確性・最新性についての保 障は一切ありません。内容は、会社の見解ではありません。この情報を元にして生じた 不利益について、当社およびスピーカは一切の責任を負いません。 bitFlyer 上での取引についての詳細は当社カスタマ サポートへお問い合わせください。

3. 自己紹介 Yuto Takei Software Engineer わたしの人生設計 ¼ : C# と心を通わせる ¾ : 美味しいモノを食べる 最近、宅建うかりました

4. 文字列について 本日のお題は… サンプル コードは https://gist.github.com/yutopio/697ac1f75b66fca2b16ceedb9b0c1dd5

5. 文字列のインスタンス C# の文字列は不変です §8.2.1 Predefined types The predefined reference types are object and string. The type object is the ultimate base type of all other types. The type string is used to represent Unicode string values. Values of type string are immutable. [訳] 神は object と string という参照型を作りたもうた。 object は万物の根源である。 string は Unicode 文字列をあらわす。何人も string の値を 変えることはできない。 – ECMA-334 C# Language Specification, p. 17. “

6. Insert とか Remove とかあるじゃん … ご存知のとおり、新しい文字列になって返ってきます string String.Insert(int, string) “Returns a new string in which a specified string is inserted at a specified index position in this instance.” – https://msdn.microsoft.com/library/system.string.insert.aspx string String.Remove(int, int) “Returns a new string in which a specified number of characters in the current instance beginning at a specified position have been deleted.” – https://msdn.microsoft.com/library/system.string.remove.aspx

7. Insert とか Remove とかあるじゃん String result = FastAllocateString(newLength); unsafe { fixed (char* src = &m_firstChar) { fixed (char* dst = &result.m_firstChar) { wstrcpy(dst, src, startIndex); wstrcpy(dst + startIndex, src + startIndex + count, newLength - startIndex); } } } return result; – Reference Source for .NET 4.6 – ndpclrsrcbclsystemstring.cs (Line. 2873 – 2885) ← 新しいインスタンスを作ってます

8. 中身を変えたければ、どうするの? System.Text.StringBuilder を使いましょう StringBuilder StringBuilder.Insert(int, string) “Inserts a string into this instance at the specified character position.” – https://msdn.microsoft.com/library/system.text.stringbuilder.insert.aspx StringBuilder StringBuilder.Remove(int, int) “Removes the specified range of characters from this instance.” – https://msdn.microsoft.com/library/system.text.stringbuilder.remove.aspx

9. 中身を変えたければ、どうするの? public StringBuilder Remove(int startIndex, int length) { // ... if (length > 0) { StringBuilder chunk; int indexInChunk; Remove(startIndex, length, out chunk, out indexInChunk); } return this; } – Reference Source for .NET 4.6 – ndpclrsrcbclsystemtextstringbuilder.cs (Line. 865 – 893, excerpted) ← インスタンス自身がそのまま返る

10. 中身を変えたければ、どうするの? public override String ToString() { // ... string ret = string.FastAllocateString(Length); StringBuilder chunk = this; unsafe { fixed (char* destinationPtr = ret) ; // copy here... } return ret; } – Reference Source for .NET 4.6 – ndpclrsrcbclsystemtextstringbuilder.cs (Line. 330 – 368, excerpted) ← ToString するたびに ← インスタンスが作られる

11. 文字列の intern とは 同一文字列は、ひとつのインスタンスにまとめられる (メモリ節約が目的) User Strings ----------------------------- 70000001 : (10) L"HelloWorld" ldstr "HelloWorld" /* 70000001 */ 0xDEADBEEF: var hello = "HelloWorld"; – or – string.Intern(otherHello); System.String “HelloWorld”

12. 余談: いつ intern されるか CompilationRelaxtions というコンパイラ制御用の属性があり、 NoStringInterning という属性を指定することができる using System.Runtime.CompilerServices; [assembly: CompilationRelaxations(CompilationRelaxations.NoStringInterning)] … 無視されます (intern しなくていいよ、というだけで、結局される)

13. 余談: いつ intern されるか ところが Ngen.exe にかけると無条件で intern できなくなる > ngen.exe install ConsoleApp1.exe ナンナンダ、この一貫性のなさは!!

14. ところでなぜ String を char* で fixed できるのか public override String ToString() { // ... string ret = string.FastAllocateString(Length); StringBuilder chunk = this; unsafe { fixed (char* destinationPtr = ret) ; // copy here... } return ret; } – Reference Source for .NET 4.6 – ndpclrsrcbclsystemtextstringbuilder.cs (Line. 330 – 368, excerpted)

15. ところでなぜ String を char* で fixed できるのか 仕様です。 §27.6 The fixed statement An expression of type string, provided the type char* is implicitly convertible to the pointer type given in the fixed statement. In this case, the initializer computes the address of the first character in the string, and the entire string is guaranteed to remain at a fixed address for the duration of the fixed statement. [訳] string 型の式は char* 型が与えられたなら fixed によりポインタに暗黙変換できる。 このとき初期化子では文字列の先頭アドレスが計算されて、 fixed スコープ中 では文字列全体が固定アドレスに留まることが保障される。 – ECMA-334 C# Language Specification, p. 437. “

16. さて 前置きはこのくらいにしておいて…

17. 文字列のインスタンス (復習) C# の文字列は不変です §8.2.1 Predefined types The predefined reference types are object and string. The type object is the ultimate base type of all other types. The type string is used to represent Unicode string values. Values of type string are immutable. [訳] 神は object と string という参照型を作りたもうた。 object は万物の根源である。 string は Unicode 文字列をあらわす。何人も string の値を 変えることはできない。 – ECMA-334 C# Language Specification, p. 17. “

18. 文字列のインスタンス C# の文字列が、不変だと思っていたでしょう! … 違います (ガタッ) §27.6 The fixed statement Modifying objects of managed type through fixed pointers can result in undefined behavior. [Note: For example, because strings are immutable, it is the programmer’s responsibility to ensure that the characters referenced by a pointer to a fixed string are not modified. end note] [超意訳] 文字列はホントは不変だけど、ポインタで参照された文字列を変えてもイイよ! 責任取れるならね! (^ρ^) – ECMA-334 C# Language Specification, p. 439. “

19. fixed 文字列は、いじれるんじゃね? そのとおり。

20. 文字列インスタンスの生成は特殊 public override String ToString() { // ... string ret = string.FastAllocateString(Length); StringBuilder chunk = this; unsafe { fixed (char* destinationPtr = ret) ; // copy here... } return ret; } – Reference Source for .NET 4.6 – ndpclrsrcbclsystemtextstringbuilder.cs (Line. 330 – 368, excerpted)

21. 文字列クラスの双対性 (デュアリティ) public sealed class String : IComparable, ICloneable, IConvertible, IEnumerable { [NonSerialized]private int m_stringLength; [NonSerialized]private char m_firstChar; – Reference Source for .NET 4.6 – ndpclrsrcbclsystemstring.cs (Line. 48 – 60, excerpted) 対応付け class StringObject : public Object { DWORD m_StringLength; WCHAR m_Characters[0]; – .NET CoreCLR (https://github.com/dotnet/coreclr) – src/vm/object.h (Line. 1087 – 1099, excerpted)

22. 文字列インスタンスの生成は特殊 LEAF_ENTRY AllocateStringFastMP_InlineGetThread, _TEXT ; We were passed the number of characters in ECX ; we need to load the method table for string from the global mov r9, [g_pStringClass] ; Instead of doing elaborate overflow checks, we just limit the number of elements ; to (LARGE_OBJECT_SIZE - 256)/sizeof(WCHAR) or less. ; This will avoid all overflow problems, as well as making sure ; big string objects are correctly allocated in the big object heap. cmp ecx, (ASM_LARGE_OBJECT_SIZE - 256)/2 jae OversizedString mov edx, [r9 + OFFSET__MethodTable__m_BaseSize] ; Calculate the final size to allocate. ; We need to calculate baseSize + cnt*2, then round that up by adding 7 and anding ~7. lea edx, [edx + ecx*2 + 7] and edx, -8 (… 続く) ← String 型の情報を読み込み ← 文字列長の評価。大きすぎるときは別方法で割り当て ← 型のベース サイズを評価(C++ の WCHAR[0] はこのため) ← 最終的に必要な領域の計算

23. 文字列インスタンスの生成は特殊 PATCHABLE_INLINE_GETTHREAD r11, AllocateStringFastMP_InlineGetThread__PatchTLSOffset mov r10, [r11 + OFFSET__Thread__m_alloc_context__alloc_limit] mov rax, [r11 + OFFSET__Thread__m_alloc_context__alloc_ptr] add rdx, rax cmp rdx, r10 ja AllocFailed mov [r11 + OFFSET__Thread__m_alloc_context__alloc_ptr], rdx mov [rax], r9 mov [rax + OFFSETOF__StringObject__m_StringLength], ecx ifdef _DEBUG call DEBUG_TrialAllocSetAppDomain_NoScratchArea endif ; _DEBUG ret OversizedString: AllocFailed: jmp FramedAllocateString LEAF_END AllocateStringFastMP_InlineGetThread, _TEXTracters[0]; – .NET CoreCLR (https://github.com/dotnet/coreclr) – src/vm/amd64/JitHelpers_InlineGetThread.asm – Line. 159 – 204 ← メモリ割り当てに関する状態の取得 ← 実際の割り当て ← 空き領域の検査 ← Length プロパティの設定 ← 高速割り当てがダメだった場合は ← SlowAllocateString へ ← フォールバック

24. 高速化のための合わせ技 みんなパフォーマンス気になりますよね… http://stackoverflow.com/questions/311165/ how-do-you-convert-byte-array-to-hexadecimal-string-and-vice-versa byte[] から 16 進数表現 string への変換は、 文字列の高速割り当てをして、ポインタで書き込めば、 最速のものの約 1.5 倍まで加速できる!

25. intern した文字列変更したらどうなる? 結論: 壊れます

26. まとめ ● 文字列のインスタンスは基本的に不変 ● 定数は、文字列リテラルのルックアップ テーブルが用いられる ● Ngen しない限り、メモリ節約のため、別で作った文字列も intern できる ● 文字列はデータ構造がとても特殊 ● 新しいインスタンスの生成時にはメモリ高速割り当てされる

27. まとめ (ダーク サイド) ● 仕様書に外れる行為をすれば… 文字列インスタンスは変更できる (ただし自己責任) ● Intern されている変数は、 絶対に変更してはいけない ● 万が一変更すると、定数などが全部壊れるし、 string.Intern が動かなくなる マイクロ最適化楽しい!! ლ(´‫`ڡ‬ლ)

28. あとがき 本日のトークは 2008 年春 (C# 3 の頃) の再放送です。

29. 会場から出た質問まとめ ● StringBuilder は意味ないの? ○ いえいえ、紹介したテクニックは、あくまで固定長文字列でしか使えないかと。 ● intern された文字列のライフサイクルは? ○ AppDomain でなく System domain だから、 仮に変更したら読み込んでいる他の dll の文字列リテラルなどにも影響が出る ○ 文字列定数を lock ステートメントに食わせたらプロセス全体に影響出るよ (@ufcpp さん) ● fixed した文字列 char* pt に対して ((int*)pt)[-1] を 書き換えたら文字列長、いじれるんじゃね? ○ いじれますね。ヤヴァい。悪用禁止。 実行領域ではないので、不正コード埋められはしないはず ...? (要調査)

Add a comment