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

Hello Dark-Side C# (Part. 1)

Embed Size (px)

Citation preview

Hello Dark-Side C# (Part. 1)Yuto TakeiSoftware EngineerbitFlyer Inc.

免責

このトークは、情報提供のみを目的として行われており、正確性・最新性についての保

障は一切ありません。内容は、会社の見解ではありません。この情報を元にして生じた

不利益について、当社およびスピーカは一切の責任を負いません。

bitFlyer 上での取引についての詳細は当社カスタマ サポートへお問い合わせください。

自己紹介

Yuto TakeiSoftware Engineer

わたしの人生設計

¼ : C# と心を通わせる

¾ : 美味しいモノを食べる

最近、宅建うかりました

文字列について本日のお題は…

サンプル コードは https://gist.github.com/yutopio/697ac1f75b66fca2b16ceedb9b0c1dd5

文字列のインスタンス

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.

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

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– ndp\clr\src\bcl\system\string.cs (Line. 2873 – 2885)

← 新しいインスタンスを作ってます

中身を変えたければ、どうするの?

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

中身を変えたければ、どうするの?

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– ndp\clr\src\bcl\system\text\stringbuilder.cs (Line. 865 – 893, excerpted)

← インスタンス自身がそのまま返る

中身を変えたければ、どうするの?

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– ndp\clr\src\bcl\system\text\stringbuilder.cs (Line. 330 – 368, excerpted)

← ToString するたびに← インスタンスが作られる

文字列の intern とは

同一文字列は、ひとつのインスタンスにまとめられる

(メモリ節約が目的)User Strings-----------------------------70000001 : (10) L"HelloWorld"

ldstr "HelloWorld" /* 70000001 */

0xDEADBEEF:

var hello = "HelloWorld";

– or –

string.Intern(otherHello); System.String “HelloWorld”

余談: いつ intern されるか

CompilationRelaxtions というコンパイラ制御用の属性があり、

NoStringInterning という属性を指定することができる

using System.Runtime.CompilerServices;

[assembly: CompilationRelaxations(CompilationRelaxations.NoStringInterning)]

… 無視されます

(intern しなくていいよ、というだけで、結局される)

余談: いつ intern されるか

ところが Ngen.exe にかけると無条件で intern できなくなる

> ngen.exe install ConsoleApp1.exe

ナンナンダ、この一貫性のなさは!!

ところでなぜ 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– ndp\clr\src\bcl\system\text\stringbuilder.cs (Line. 330 – 368, excerpted)

ところでなぜ 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.

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

文字列のインスタンス (復習)

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.

文字列のインスタンス

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.

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– ndp\clr\src\bcl\system\text\stringbuilder.cs (Line. 330 – 368, excerpted)

文字列クラスの双対性 (デュアリティ)public sealed class String : IComparable, ICloneable, IConvertible, IEnumerable{ [NonSerialized]private int m_stringLength; [NonSerialized]private char m_firstChar;

– Reference Source for .NET 4.6– ndp\clr\src\bcl\system\string.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)

文字列インスタンスの生成は特殊

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] はこのため)

← 最終的に必要な領域の計算

文字列インスタンスの生成は特殊

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_NoScratchAreaendif ; _DEBUG

ret

OversizedString: AllocFailed: jmp FramedAllocateStringLEAF_END AllocateStringFastMP_InlineGetThread, _TEXTracters[0];

– .NET CoreCLR (https://github.com/dotnet/coreclr)– src/vm/amd64/JitHelpers_InlineGetThread.asm– Line. 159 – 204

← メモリ割り当てに関する状態の取得

← 実際の割り当て

← 空き領域の検査

← Length プロパティの設定

← 高速割り当てがダメだった場合は← SlowAllocateString へ← フォールバック

高速化のための合わせ技

みんなパフォーマンス気になりますよね… http://stackoverflow.com/questions/311165/how-do-you-convert-byte-array-to-hexadecimal-string-and-vice-versa

byte[] から 16 進数表現 string への変換は、

文字列の高速割り当てをして、ポインタで書き込めば、

最速のものの約 1.5 倍まで加速できる!

intern した文字列変更したらどうなる?

結論: 壊れます

まとめ

● 文字列のインスタンスは基本的に不変

● 定数は、文字列リテラルのルックアップ テーブルが用いられる

● Ngen しない限り、メモリ節約のため、別で作った文字列も intern できる

● 文字列はデータ構造がとても特殊

● 新しいインスタンスの生成時にはメモリ高速割り当てされる

まとめ (ダーク サイド)

● 仕様書に外れる行為をすれば…

文字列インスタンスは変更できる (ただし自己責任)

● Intern されている変数は、

絶対に変更してはいけない

● 万が一変更すると、定数などが全部壊れるし、

string.Intern が動かなくなる

マイクロ最適化楽しい!! ლ(´ڡ`ლ)

あとがき

本日のトークは 2008 年春 (C# 3 の頃) の再放送です。

会場から出た質問まとめ

● StringBuilder は意味ないの?○ いえいえ、紹介したテクニックは、あくまで固定長文字列でしか使えないかと。

● intern された文字列のライフサイクルは?

○ AppDomain でなく System domain だから、

仮に変更したら読み込んでいる他の dll の文字列リテラルなどにも影響が出る

○ 文字列定数を lock ステートメントに食わせたらプロセス全体に影響出るよ (@ufcpp さん)

● fixed した文字列 char* pt に対して ((int*)pt)[-1] を書き換えたら文字列長、いじれるんじゃね?

○ いじれますね。ヤヴァい。悪用禁止。

実行領域ではないので、不正コード埋められはしないはず ...? (要調査)