本サイトからスピンオフした姉妹サイトです。前編に続き、次期C#のパフォーマンス向上について解説。7以降での採用が予定もしくは検討されているパフォーマンス向上関連の新機能の内容を具体的に見ていこう。前回は、C#のパフォーマンス向上について、その背景と概要について説明した。現在検討されている機能の一例として以下のようなものがある。7で実装されそうである。7よりもう少し先の話になる。値型を利用することでヒープ確保を避けられる一方で、値型には、変数への代入やメソッドの戻り値に返す際にコピーが発生するという問題がある。based)な配列を考えてみよう。これまでのC#では、リスト2に示すようなコードになるだろう。getの戻り値(コピー品)は書き換えられない。↓こう書かないとダメ。6.0までの非0ベース配列の実装例Point構造体については前回のリスト1を参照。コード中のコメントにも書いたが、この実装方法では、Point構造体全体のコピーが発生する。分かりやすくするため図で表すと、図4のようなメモリの使い方になっている。見ての通り、書き換えたいのはX(int型なので4byte)だけなのに、読み書きの両方でPoint型全体(12byte)のコピーが発生する。このようなコピーのコストは、値型の参照渡しを使えば避けることができる。現状のC#では、値型の参照渡しを使えるのはメソッドの引数だけである。これは、メソッドの引数であれば参照先が有効であることを保証しやすく、実装が簡単だからである。コンパイラーがもう少し頑張れば、安全性を損なわずに(=無効な場所を参照しないことを保証しつつ)値型の参照渡しができる箇所がいくつかある。それが、メソッドやプロパティなどの戻り値とローカル変数である。ちなみに、.NETの仮想マシンレベルでは、最初から戻り値や引数での参照の仕組みを持っている。実は、配列など一部の組み込み型では最初から参照戻り値が使われている。このため、リスト3のようなコードが書ける。先ほどの自作のインデクサーと違って、配列はこの書き方ができる。参照を認めていなかったのはあくまでC#のレベルでの制限である。C#に対してパフォーマンスを向上させる要求が高まったのと、コンパイラーをC#で作り直したことで安全性を保証するためのフロー解析がしやすくなったため、制限が緩和されることになった。7ではリスト4のように書き直すことができる。参照戻り値を使った非0ベース配列の実装例BasedArray型の変更点はインデクサーの実装だけである。Baseindex])にrefキーワードを付けると、参照戻り値が使える。この場合、メモリの使い方は図5のようになり、無駄なコピーが削減できる。特定の関数の中からしか使わないような関数を作りたくなることがあるだろう。それを実現するため、関数内に別の関数を書きたくなることがある。これまでのC#でも、匿名関数(=ラムダ式や匿名メソッド式)を使えば、関数内で関数のようなものを書けた。以下のような制限がかかっていた。7ではローカル関数という構文を導入する。リスト5に示すような書き方で、「関数内関数」が書ける。匿名関数では面倒だった再帰やイテレーターも書けるという利点の他に、パフォーマンス的にも有利な点がある。ローカル関数であっても、デリゲートへの代入が発生する場合には(※メソッドの引数に渡したり、戻り値として返したりする場合を含む)、匿名関数と同じコードが結果として生成されるが、直接呼び出す場合には、少しパフォーマンスがいいコードが生成されるのだ。デリゲートを介さないこと自体、パフォーマンス上、有利である。デリゲート型の変数への代入は無駄なヒープ確保を発生させる。ローカル関数は直接呼び出すことで、この無駄なヒープ確保を避けられる。この場合、ローカル変数をキャプチャする場合のコードも、表1に示すように、匿名関数の場合と少し異なっている。匿名関数ではコンパイラーによってクラスが生成されて、もともとローカル変数だったものはそのフィールドとして扱われる。ここで、クラスのインスタンスが作られることでヒープが確保される。ローカル関数の場合は、コンパイラーは構造体を作って、ローカル関数に対して参照引数として値を渡す。することでヒープ確保を避けている。構造体にしても、デリゲートへの代入の時点でボックス化が発生して、ヒープ確保が必要になる。現状のC#では、非同期メソッドの戻り値はTaskクラス(System.Threading.Tasks名前空間)に限られている。5.0のプロトタイプ段階では任意の型を戻り値に使える仕組みも検討されていたが、オーバーロード解決やジェネリック型引数の推論との相性が悪く、最終的にはTaskクラスに限ることにしたという背景がある。Taskがクラスであることで、無駄なヒープ確保が発生している。リスト6の例を見てほしい。確率で同期・非同期が切り替わる処理の例2分の1の確率で遅延を挟んでから値を返すメソッドである。遅延が挟まらなかった場合は同期的に処理が完了する。この場合、完了済みのTaskクラスのインスタンスが作られることになる。このとき、最初から完了済みなのにTaskクラスのインスタンスを作るのは無駄なヒープ確保となる。リスト7のような、完了済みならただの値を、非同期処理が必要ならTaskクラスを作るような構造体があれば、無駄なヒープ確保を避けられる。構造体版のTask実際、.NET標準ライブラリにこのようなValueTask構造体が追加される予定である(ソースコードリポジトリ上は実装済み)。C#の側としても、非同期メソッドの戻り値として任意の型を返せるようにする機能の追加を検討中である。現状では、ValueTask構造体を活用しようとすると非同期メソッドが使えず、リスト8に示すような、古き良き書き方が必要になる。ValueTaskを非同期処理の戻り値に使う例これは、現状のC#の非同期メソッドがTaskクラス以外の戻り値を認めていないためだ。これに対して、任意の型を非同期メソッドの戻り値に使える構文が検討中である。この構文が入れば、リスト9のような書き方ができる。Taskクラス以外の型を非同期メソッドの戻り値にする構文(検討中)ちなみに、現状のC#がTaskクラス以外の戻り値を認めていないのは、ラムダ式の戻り値の型推論や、オーバーロード解決が難しくなるからである。現在、この問題について、GitHub上でディスカッションが行われている。スライス大き目のデータ列の中から、その一部分だけを抜き出して使いたいことがある。Webでのデータのやりとりを考えてみよう。図6に示すように、ヘッダー情報やさまざまなデータが1つの大きなバイナリ列にシリアライズされて送受信される。Web上での送受信データの例そして、こういう大きなバイナリデータの中から、使いたい部分だけを参照して使いたい場面がある。この要件に対して、現状のC#だと、使いたい部分を別の配列にコピーして使うことになる場面が多く、無駄なヒープ確保が発生していた。これまでも一応、配列に対してはArraySegment構造体(System名前空間)があった。以下のような要件に対応できておらず、現状のArraySegment構造体は使いづらくなっている。配列、string、ネイティブコードで確保したメモリ領域を統一的に扱いたいが、ArraySegment構造体は配列しか扱えないそこで、これらの要件を満たしたSpan構造体(System.Slices名前空間)という新しい型の追加が検討されている(現在は試験的な実装がある状態)。この型と関連して、C#にも配列や文字列からSpan構造体を作る専用構文の追加が検討されている。APIを作る際に、意外と大きなネックとなるのが文字列のエンコードである。C#コンパイラーのコンパイルのパフォーマンスでも、C#ソースコードのエンコードが問題となる。.NETのstring型は、内部データ的にはUTF-16になっている。1990年代に、2byteの固定長で全ての文字を表せるという幻想の下にUnicodeの仕様策定が進んでいた時代の名残だ。一方で、現在、文字列エンコード形式としてデファクトスタンダードになっているのはUTF-8である。レガシー資産が絡む場合を除けば、UTF-8以外のエンコード形式を選ぶことはほぼないだろう。結果として、C#でWebページ・APIを作る際にも、C#コンパイラーがソースコードを読み込む際にも、UTF-8とUTF-16との変換処理が発生することになる。変換が挟まることでCPUもヒープ領域も無駄に浪費するため、これは好ましい状態ではない。そこで、Utf8String(System.Text.Utf8名前空間)という、UTF-8形式を直接扱うためのクラスが標準ライブラリに追加されそうである(試験的実装あり)。UTF-8にエンコードされたバイト列から直接文字列を読み書きするクラスである。C#の構文レベルでは、UTF-8文字列リテラルが検討されている。C#ソースコード中に書かれた文字列リテラルは、現状では有無を言わさずstring型(=UTF-16)になってしまい、リテラルからUtf8Stringオブジェクトを作りたい場合、文字エンコードの変換が必要になってしまう。そこで、直接Uft8Stringクラスのインスタンスを作るような構文が必要である。まとめC#は生産性や安全性に重きを置いているが、パフォーマンスへの配慮もそれなりに考えた言語である。パフォーマンス向上への余地は残されているし、近年、より良いパフォーマンスを求められる状況がC#にも増えてきた。7以降ではパフォーマンス向上もテーマの1つとなっている。今回、そのC#のパフォーマンス向上に関する新機能をいくつか紹介してきた。使える場面はそれほど多くなく、使いこなすのも難しいかもしれない。Coreなどのフレームワークのパフォーマンスが向上することで、多くの開発者に間接的な利益が与えられるだろう。紹介した機能は総じて、メモリ管理の効率化に関するものである。値型の活用など、不要なヒープ確保を避けることで、GC負担が減り、また、参照局所性が高まり、パフォーマンス向上が期待できる。1.0がプレビュー版だった頃からC#によるプログラミング入門を公開していて、C#とともに今年で15年になる。最近の自己紹介は「C#でググれ」。※以下では、本稿の前後を合わせて5回分(第5回~第9回)のみ表示しています。INDEX]を参照してください。パターンマッチングの内部的な仕組みC#の次バージョンに追加される「パターンマッチング」の挙動(パターンマッチングがどう展開されるのかや、その実現方法)について説明する。機能や構文ばかりが注目されるが、プログラミング言語ではパフォーマンスも重要だ。パフォーマンス向上に対する要求が高まってきた背景と、向上のための改善方法を説明。7以降で追加が検討されている新機能にも言及する。前編に続き、次期C#のパフォーマンス向上について解説。7以降での採用が予定もしくは検討されているパフォーマンス向上関連の新機能の内容を具体的に見ていこう。7にはどんな新機能が含まれるのかが見えてきた。7はかなり速いペースでのリリースとなる。その背景にはどんな事情があるのだろうか。Task-likeC#の進化の中でも「非同期メソッド」はコーディング方法を大きく変えるほど革新的だったが、そこにはまだ課題もある。7~将来のC#で、非同期処理はどう進化するのか、前後編で見ていこう。機械学習エンジニアを目指す人~初中級者のための技術情報サイトです。「機械学習&AI」「クラウド&ビッグデータ」「IoTデバイス」のネット記事情報を共有中。開発者のためのSlackチーム、作りました:「機械学習&AI」「クラウド&ビッグデータ」「IoTデバイス」のネット記事情報を共有中。2020最適なビューへの切り替えを推奨します:スマートフォン用表示に。