++C++; // 未確認飛行 C ブログ

http://ufcpp.net/

C# 7に向けて(2): 性能と信頼性

with 2 comments

C# 7の提案のうちいくつかを見た瞬間、思ったのは「C++ 11みたい」でした。

まあ、背景には、年々C#の適用範囲が広がっていること、そして、オープンソース化に伴って今後はさらに広がるであろうことがあります。

ゲーム開発の標準言語と言えばC#

その広がった先の1つはゲーム開発でしょう。「ゲーム開発の標準言語と言えばC#」なんていう煽り(わりかしほんとに「煽り」ではあると思う。「話題にならないよりは炎上する方がマシ」くらいの覚悟の上の)もあります。まあ、割かし昔から、ゲーム開発者にもC#好きな人はいて、ゲーム会社の社内ツールなんかはC#で書かれることが結構あったみたいです。しかし、「ゲーム自体を」となると、これは割かし最近の話。「XNAがあったし(震え声)」と小声で主張もしたいところですが、実際のところ、割かし皮肉なことに、「C#でゲーム開発」が注目を浴びるようになったのはUnityのおかげで、iOS/Android向けのゲーム開発ができるようになったからでしょう。今では、Mono for Unreal EngineとかParadoxとかいうものもあります。

実際のところ「C#でゲーム開発」がどうなのかというと、「ジャンルを選べば割と十分」と言ったところ。そこそこよい性能を、かなりいい開発効率で実現できると思います。

とはいえ、「そこそこよい」ではダメなジャンルももちろんあります。アクション性の高いゲームだと結構きついんじゃないかと思います。C#単体でももっと性能を求めることや、C++などの性能的に実績の高い言語との相互運用をさらに深めていくなど、改善の余地はまだまだあります。

性能を求めて

ゲーム以外でも、性能を求めたい場面は多々あるはずです。マイクロソフト自身のプロジェクトで言っても、WPFやRoslynはかなりシビアな性能を求められたはずです。そんな中で「C#でもこう書ければいいのに」「C++ならこう書けるのに」といった類の要望がいろいろ出てきたのでしょう。もちろん、C#の持ち味である安全性や生産性の高さを崩すわけにもいきませんが、その前提下においても、できそうなことはあります。

ということで、以下のような提案が出ています。

なんかC++っぽい。

Lambda Capture Lists

前回、readonlyローカル変数のところでちょっと話題にも出しましたが、C#のラムダ式で、ラムダ式の外の変数をキャプチャした場合、参照渡しになって(内部的にはクラスとそのフィールド生成とかいう結構大ごとなコード生成が起きます)、いろんなところから値が書き替えられる可能性があります(書き換えを防ぐという観点でいうと、前回のreadonlyローカル変数も有効な解決策ですが)。

この挙動がほしいことは確かにあります。便利です。ですが、「規定動作がこれでいいか」とか、「挙動を変えれなくていいか」とか考えると、結構嫌な感じ。

C++の場合、C++ 11でラムダ式を導入したわけですが、その文法では、ラムダ式の外の変数をどうキャプチャするかを指定できます。これにならって、C#のラムダ式でも、参照渡しなのか、値渡しなのか、指定できるようにしたいというのが議題。

「どう書くか」は悩ましいですが… C++のラムダ式の書き方はちょっと煩雑すぎて。

Ref Returns and Locals

現状のC#では、「参照渡し」ができるのは ref 引数と out 引数だけです。

しかし、.NETランタイムのレベルでは、もっといろいろな場面で参照を使えます。ローカル変数を参照変数にすることもできますし、戻り値に参照を返すこともできます。これをC#でも認めれば、性能を改善できる場面がありますという議題。

まあ、悩ましいのは、「参照型の参照渡し」とかが、なじみのない人にとってなかなかにわかりにくい概念なところですかねぇ。同じ問題は抱えます。

Slicing

配列の一部分だけを参照したいことは結構あります。string.Substring とか、LINQで .Skip(start).Take(length) とか書く方法もありますが、配列のコピーが発生するので、性能的にはいまいちです。.NETランタイムや、C#の言語レベルでのサポートがほしいという議題。

今のところ、Slice<T>型と、array[left : right]的な文法が提案されています。例えば、クイックソートなんかを書くなら以下のような感じ。

Fixed-size buffers enhancements

今、固定長バッファーは unsafe 下でだけ認められているけども、safe な文脈でもやりようあるし、それで性能改善とか、unsafe を必要としないネイティブ相互運用ができるんじゃないかという議題。

今出ている提案では、「Ref Returnsが入れば、それを使って実現できるし実装コスト低いんじゃないか」というようなノリ。

move

「ある変数やフィールドから、別の変数に所有権を移す」というような処理があります。別の変数に代入して中身を引き渡した後、自分自身はnullを代入して所有を放棄するというようなもの。これを、「move」(要は、所有権の移動)と言って、新しい構文として導入できないかという議題。

要はこれも、C++ 11のmove。「コピー コンストラクターの呼び出しが不要になる」という目に見えたメリットがあるC++と違って、C#に導入する利点は少し弱いんですが…

とはいえこれは、所有者が単一であることを保証するのに便利です。同時に複数の変数から同じ値を参照する必要がないことは結構あるんですが、この場合、リソース管理がかなり簡単になります(C++で言うところの、unique_ptr 的なアプローチ)。具体的には、次節のDestructible Typesとの組み合わせが便利ということになります。

Destructible Types

C#は、デストラクターという名前の構文はあるもの、その実態は「ファイナライザー」で、要するに、「ガベージ コレクションの回収対象になった時に呼ばれる」ものです。が、ファイナライザーは性能への影響もでかいし、呼ばれるタイミングが非決定的なのがたびたび問題になります。結局、IDisposableを実装して、自前でDisposeを呼ぶタイミングを管理することになって、かなり煩雑な処理が必要です。

そこで、C++的なマナーのデストラクター、要するに、変数がスコープを抜けた時に呼ばれる、決定論的な破棄処理を掛けたいという議題。

destructive 修飾をクラスや構造体につけることで、「~型名」をファイナライザー的動作ではなく、C++のデストラクター的動作にするという提案が出ています。

このやり方だと、shared_ptr などを使わない場合の C++ と同じく、値の所有権を正しく管理する必要があります。所有している変数やフィールドが単一でないと、破棄のタイミングが確定しません(複数のうちの1つがスコープを抜けた時に破棄されてしまうとなると、残りは破棄済みの不正な値を参照することになる)。そこで、所有権を移さないなら readonly ref、移したいなら前節の move などが必要になります。

まとめ

「C++だわ」という感想はありますが。

まあ、C# 7を待つまでもなく、.NET Nativeや、SIMD 演算対応など、これまでC#や.NETが苦手としていた性能面へのアプローチは始まっています。C#の用途が広がってきている今、C++ 11 (14, 17)に習うべきところは多々あるでしょう。

とはいえ、この辺りはおそらく、C# 7への組み込みが最も難航しそうなところな気がします。要は、費用対効果はどうか、pros/consのバランスが合うかという辺りが結構難しそう。

実際、1/28のミーティング議事録の時点ですでに、Safe fixed-size buffersは「優先度最低で」認定を受けていたりします。

広告

Written by ufcpp

2015年2月2日 @ 01:13

カテゴリー: C#

Tagged with

コメント / トラックバック2件

Subscribe to comments with RSS.

  1. […] C# 7に向けて(2): 性能と信頼性 […]

  2. […] C# 7に向けて(2): 性能と信頼性 […]


コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中

%d人のブロガーが「いいね」をつけました。