Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Generics on Xamarin products

Generics on Xamarin products

2017/6/24 ジェネリクス勉強会

Atsushi Eno

June 24, 2017
Tweet

More Decks by Atsushi Eno

Other Decks in Programming

Transcript

  1. このセッションの目的 - Xamarinをダシにして素材としてreified genericsの世界で起こる問題を紹介する - XamarinはC#/Monoをベースにした製品だけど、 - .NET環境には実装がいろいろあって、プラットフォームによる実装の違いもある - その辺の事情を交通整理する

    - Xamarinにおけるジェネリクスは何が特別なのかを説明する - 主にランタイム - ついでにmaciosとAndroid - XamarinやC#/.NETのジェネリクス関連で今後の面白トピックを紹介する
  2. Terms • .NET: 雑にECMA 334 (C#)とECMA 335 (CLI)に基づく環境を表すことが多い • Xamarin:

    .NETに基づく、モバイルプラットフォーム用の開発ツール • Mono: Xamarinが使っている.NET標準の実装 ◦ Mono Runtime: Monoのランタイム。.NETならCLR、JavaならJVM ◦ Mono SDK: Monのランタイムと標準ライブラリと C#コンパイラのセット ◦ Roslyn: MSのオープンソース版 C#コンパイラ • Xamarin.iOS: Xamarin on iOS (, watchOS, tvOS) ◦ Xamarin.Mac: Xamarin on MacOS ◦ まとめてxamarin-macios • Xamarin.Android: Xamarin on Android
  3. .NET Platforms and Runtimes 今日はXamarin/Monoの実装の詳細の話 Framework Runtime Platform .NET Framework

    (,WinRT,UWP) CLR* Windows .NET Core (Core)CLR Win, Mac, Linux, Tizen Mono Mono(新) Linux, Mac, Win Xamarin Mono(新) iOS*, Android Unity Mono(古→新) almost everywhere
  4. .NET Compilers 今後はRoslynに一本化する方向(ジェネリクスの実装の詳細は気にしなくても良い) XamarinはVSからも使えて、その場合はWindows上のcscが使われる .NET < 4.6 / Visual Studio

    < 2015 ~ C# 5.0 classic csc .NET >= 4.6 / Visual Studio >= 2015 C# 6.0 ~ Roslyn csc .NET Core C# 6.0 ~ Roslyn csc Mono < 5.0 / Xamarin Studio ~ C# 6.0 mcs Mono >= 5.0 / Visual Studio for Mac C# 7.0 ~ Roslyn csc Unity * mcs → Roslyn?
  5. C#/.NET Generics • キーワードはreified generics ◦ 実行時にも実体が存在するジェネリクス ◦ Object(など)への変換が発生しないのでパフォーマンスが良い  e.g.

    ArrayList vs. List<T> ▪ C#ではboxingのコストが高い • Java ◦ 実行コード上にジェネリック型情報は無い ※実行コードとして存在しないだけで、ジェネリック型情報が全部消えているわけではない • C++ templates ◦ 実行時には(今日話題にするような)「型情報」が無い • Swift, Kotlinなど - 識者の解説待ち(!?)
  6. Terms • CLI: .NETのランタイム • CIL(MSIL): CLIのバイトコード ◦ CILメタデータ ◦

    アセンブリ: CILを保持するファイル( DLLまたはEXE) • マネージドコード: CILで書かれたプログラム • アンマネージドコード、ネイティブコード: CPUネイティブ命令に基づくプログラム • 実行エンジン: CILを実行するためのエンジン
  7. Mono Runtime main()が呼び出される ↓ AppDomainを作成しアセンブリをロードし 型情報を解決(vtableなど) ↓ エントリポイントのCLI型を探索し、 メソッドの内容(コード、CIL)を取得 ↓

    CILを{解釈して | ネイティブコードに変換して}実行 monoランタイムのEE • インタープリター • コードジェネレーター (mini) ◦ JIT(ジャストインタイム)コンパイル ◦ AOT(事前)コンパイル
  8. More on Mono EEs • インタープリター (mint) ◦ 実装は仮想命令をシミュレートするだけなので簡単 ◦

    実行速度は(きわめて)悪い ◦ 2002年までの実装で、もう無い • コードジェネレーター (mini) ◦ 対象アーキテクチャごとに実装する必要がある ◦ JITでは変換速度がそれなりに重要 ◦ AOTでは変換処理はみっちり行える。静的なコードは ROMからでも読み出せる ▪ embedded mono - 組み込み環境で有用( RAMが少ない) ▪ mono --aot で変換できる ▪ .NET Coreならcorert
  9. Genrating code for generics... • ジェネリクスがあると生成されるコードがジェネリック型引数で変わってしまう • ただし、値型の型引数についてのみ問題 参照型については、1つのコードをどの型引数についても使いまわせる •

    CLIの型の種類 ◦ 参照型: GCでメモリ管理されるオブジェクトの型 ◦ 値型: GCでメモリ管理されないオブジェクトの型 • 参照型のランタイム上の表現はただのポインタ→生成コードの統一は容易 • 値型は内容による = 可変サイズ→生成コードがどうしても変わる(!)
  10. Generating code for generics... 例: メソッド呼び出しの引数確保部分 class G<T> { void

    M ( int X, T Y, double Z ) {...} } G<int>.M() Offset Size 0 4 3 4 7 8 G<double>.M() Offset Size 0 4 3 8 11 8 G<Uri>.M() Offset Size 0 4 3 4 7 8 G<object>.M() Offset Size 0 4 3 4 7 8 Uri FooBar BF7F5 31D4 A2E9 C6B BF7F5 31D4 A2E9 C6B BF7F5 31D BF7F5 31D
  11. Generic code sharing • 対応策1: ジェネリック インスタンスごとにネイティブコードを生成する → 使われた型引数(の組み合わせ)の数だけメモリ消費が増える  Dictionary<int,bool>,

    Dictionary<int,short>, ... • 対応策2: ジェネリック型引数の部分だけ動的に生成するコードにする → 他の部分は動的に生成 → 実装が複雑になる • monoは200x年代後半に対応策1から対応策2に切り替え 「ジェネリックコード共有」
  12. Generic code sharing • Mono以外ではどうなのか? • erased genericsの世界では問題にならない 例: 全部java.lang.Objectになる ◦

    C#で同じことをやるとboxingによるメモリ確保の嵐になって死ぬ ◦ Varhallaってこの辺どうするんだろう? • clang/llvmはどうなの? → わかりません • 参考資料 ◦ http://www.mono-project.com/docs/advanced/runtime/docs/generic-sharing/ とその先にリンクされている開発者ブログ ◦ mono/mini/mini-generic-sharing.c
  13. AOT + generic sharing A) generic sharing: 共有できない部分は実行時にコード生成している(!) B) iPhone:

    動的コード生成は禁止 A + B = □※$”#%¥!!!!11 →monoランタイムでは「完全AOT」モードを実装 $ mono --aot=full ちなみに、元々はUnityのmonoランタイムで実現していた
  14. Generic sharing for value type • 問題になるコード: generic value sharing

    (gsharedvt) ◦ 事前に使われるとわかっていない値型のジェネリック インスタンス • やっつけ対応策 ◦ ある程度の大きさのスペースを固定サイズで確保しておいて、 その範囲内のサイズに収まるオブジェクトであれば問題ない • 本格的な対応策 ◦ gsharedvt な引数については、実行時ジェネリック情報 rgctx を もとに、実際の値型に必要なメモリスペースを localloc 命令で スタック領域に確保し、その後 CPUアーキテクチャ別のトランポリン を 使って、実際に必要なメモリレイアウトを構築してから呼び出す GM<T>() Offset Size 0 4 3 X 3+X 8
  15. shared generics on NGen Professional .NET 2.0 Generics: "With NGen,

    there can't be anything "lazy" about how generics are instantiated. Instead, NGen must create representations of every generics instance it could encounter. The CLR won't be around to JIT anything. So, after all the discussion of avoiding code bloat and taking advantage of CLR's run-time optimizations, NGen takes all those values and turns them on their collective heads." "For value types, where you can't share code, there's more motivation to avoid precompiling those types that are not needed. To achieve this, NGen will compute the set types that are required for a given assembly (using transitive closure) and precompile just those instances. This will minimize the amount of bloat you'll take on for instances that will never get created at run-time." ※NGenではJITできないから事前に生成するよ
  16. xamarin-macios • Xamarin.iOSとXamarin.Mac - まとめてxamarin-macios • Objective-C APIとの相互運用の世界: ObjCRuntimeをP/Invokeする •

    任意のObjective-Cライブラリのバインディングも作成可能 ◦ Xamarin.iOS.dll - iOS SDK APIのバインディング ◦ 型やメンバーに[Register] 属性を付けるとバインディングになる • Objective-Cにはreified genericsが存在しないので ◦ Objective-Cランタイム側からCLIのジェネリック型をインスタンス化することができない ◦ ジェネリック型をバインディング APIの定義に含めることはできない ◦ ジェネリック型から派生した非ジェネリック型を使うことはできる • 公式サイトのドキュメントが親切で詳しい
  17. xamarin-android • Java APIとの相互運用の世界 - JNIEnvをP/Invokeする • 任意のJava/Androidライブラリのバインディングも作成可能 ◦ Mono.Android.dll

    - Android APIのバインディング ◦ 型やメンバーに[Register] 属性を付けるとバインディングになる(通常は IDEで生成) • ジェネリクスの制約はだいたいiOSと同じ ◦ generic value sharing関連の、AOTを理由とする制約は無い ◦ オーバーライドしたメンバーを JNIで呼び出せるようにJava glueコードを生成していて、 実行時に動的にこれを生成することはできないので、リフレクションの制約は似ている • 公式ドキュメントで言及されている(あまり親切ではない…)
  18. C#: default interface methods • Javaのデフォルトインターフェースメソッドと同等のものがC#にも導入される(予定) • 仕様について詳しくは ◦ インターフェースを「契約」として見たときの問題点

    ― C#への「インターフェースのデフォルト実装」の導入(前編) ◦ デフォルト実装の導入がもたらす影響 ― C#への「インターフェースのデフォルト実装」の導入(中編) ◦ インターフェースを拡張する2つの手段 ― C#への「インターフェースのデフォルト実装」の導入(後編) • 要は ◦ インターフェースに後から破壊的変更を引き起こさずにメンバーを追加できる ◦ ランタイムのサポートが必要( C# 3.5〜C#7.0では不要だった) • 今日は「ここから先」の話だけをします public interface IX { int X (); int Y (); int Z () { return 0; } }
  19. default interface methods in Roslyn/.NET Core • "d15.6" branch (15はVSのバージョン,

    15 = 2017。 .n はサービスリリース) • Roslynの仕様 ◦ https://github.com/dotnet/roslyn/tree/features/DefaultInterfaceImplementation • Roslynの実装 ◦ https://github.com/dotnet/roslyn/compare/dev15.6...features/DefaultInterfaceImplementation • Roslynの残っている課題 ◦ https://github.com/dotnet/roslyn/issues/17952 • CoreCLRの進捗状況 ◦ https://github.com/dotnet/coreclr/issues/10504 • CoreCLRの実装(PR) ◦ https://github.com/dotnet/coreclr/pull/10505 (dev/defaultintf)
  20. default interface methods in Mono • コンパイラはRoslynの実装待ち • mono runtimeには試験的に(?)実装済み

    https://github.com/mono/mono/pull/4625 • まとめ ◦ Roslynの特別なブランチのビルドが必要 ◦ coreclrも特別なブランチのビルドが必要 ◦ monoはmasterで試せそう(ただし試すには特別なブランチの roslynビルドが必要)
  21. How should shared generics process this IL...  ①  ③  ④

     ② は  ②② は  ①③④①③④ IL_0003: constrained. !!T IL_0009: callvirt instance int32 class IX`1<!!U>::M() このメソッドは引数のboxingを起こすかもしれないし、起こさないかもしれない でもそんな呼び出し方はCLRに存在しないので困る(!)
  22. Default interface methods in Xamarin • Xamarin.iOS ◦ Objective-Cのプロトコルは「実装しても良いし、しなくても良い」存在なので ちょうど平仄が合っている?

    ◦ Swiftバインディングツール(SwiftNetifier)でサポートされるか? • Xamarin.Android ◦ Android N API以降のAPIが徐々に利用し始めているので、 そのバインディングに必要(現在はバインドしていない)
  23. Wrap up • reified genericsはたいへん • 値型が特にたいへん • full AOTが特にたいへん

    • そのうち来るデフォルトインターフェースメソッドと値型のコンボもたいへん • というのを他の言語で実装するときに思い出すといいかも?