特集

Linuxで動く.NET環境「Mono 1.0」の実力(後編)

株式会社ピーデー
川俣 晶
2004/10/20
Page1 Page2 Page3 Page4

環境間の相違と対策

 異なる環境間では、100%の完ぺきな互換性を望むことは、労力に比して得るものが少ないことはすでに述べた。では、具体的に互換性のない事例を1つ見てみよう。併せて、それに対する対策をどのように取ることができるかも見てみよう。

 ここでは、Linux上のMonoとWindows上の.NET Frameworkで、ファイル名の表記の相違を例に取って見てみる。

 サンプル・プログラムとして、Directory.GetFilesメソッドでファイルのフルパス名を取得し、Path.GetFileNameメソッドとPath.GetDirectoryNameメソッドによってファイル名部分とディレクトリ部分に分解し、さらにPath.Combineメソッドでそれらを連結する作業を行うものを作成した。以下のようなソース・コードである。

using System;
using System.IO;

class PathSampleMain
{
  [STAThread]
  static void Main(string[] args)
  {
    // カレント・ディレクトリにある.exeファイルを取得
    string [] fullpaths = Directory.GetFiles(Directory.GetCurrentDirectory(),"*.exe");
    if( fullpaths.Length == 0 )
    {
      Console.WriteLine("file not found");
      return;
    }

    Console.WriteLine( "Base Path={0}", fullpaths[0] );

    // ファイル名部分を表示
    string fileName = Path.GetFileName(fullpaths[0]);
    Console.WriteLine( "File Name={0}", fileName );

    // ディレクトリ名部分を表示
    string directoryName = Path.GetDirectoryName(fullpaths[0]);
    Console.WriteLine( "Directory Name={0}", directoryName );

    // ディレクトリ名部分をファイル名部分を連結して表示
    string combinedPath = Path.Combine(directoryName,fileName);
    Console.WriteLine( "Combined Path={0}", combinedPath );
  }
}
ディレクトリとファイルを扱うC#のサンプル・プログラム

 このプログラムを、Windows上の.NET Frameworkで実行した結果は以下のとおりである。

D:\work>PathSample001
Base Path=D:\work\PathSample001.exe
File Name=PathSample001.exe
Directory Name=D:\work
Combined Path=D:\work\PathSample001.exe

 これを見ると、パス名の先頭にドライブ名を示すアルファベット「D:」が付加されていたり、区切りの文字が円記号(\。本来はバックスラッシュ)であることが分かる。

 同じプログラムを、Linux上のMonoで実行した結果は以下のとおりである。

[autumn@luna autumn]$ mono PathSample001.exe
Base Path=/home/autumn/PathSample001.exe
File Name=PathSample001.exe
Directory Name=/home/autumn
Combined Path=/home/autumn/PathSample001.exe

 これを見ると、先頭にドライブ名はなく、区切りの文字がスラッシュ記号(/)であることが分かる。

 これらの結果はそれぞれの環境において正しいわけだが、同じメソッドを使ってファイル名を取得しても、取得されるパス名はOS固有であるため、互換性がない。例えば、フルパス名からファイル名を取り出すために、最後に出現する円記号を検索するようなメソッドを作成すると、それはLinux上のMonoでは正常に動作しないことになる。

 このような非互換性については、非互換のままでよいとする考え方と、できるだけ互換を取るように努力するという考え方がある。Monoは、非互換のままでよいとする考え方を採っていることになる。これについて、それぞれの考え方の長所・短所とMonoとの関連について以下のコラムを用意した。

[コラム] ファイル名の互換性を維持する方法と、OSごとに合わせる方法の比較

 複数のOSにわたって共通の仕様を持つソフトウェアを使う際、ファイル名の表記を統一するか、それとも個々のOSごとの表記を使うか、その相違についての1つの事例を見てみよう。Monoのファイル名の取り扱いについて考える1つの参考にしていただきたい。

 UNIX上のコマンドライン・ツール群はWindowsやその祖先となるMS-DOSと比較してはるかに強力で使いやすかった。そのため、UNIX風のツールをWindowsやMS-DOSにも持ち込んで使おうという動きが現れた。この場合問題になるのが、ファイル名の表記をどうするかである。もともとUNIX由来のツールなのだから、互換性という点ではスラッシュで区切るUNIXの流儀を使うのが自然に思える。もし、UNIXの流儀を通せば、それらのツールを活用するスクリプト類も変更することなくWindowsで使用できることになる。

 しかし、Windowsのコマンドライン・ツールの補強のために使おうと思うなら、両者のツールを混用することになるため、ツールによってファイル名の表記を変えるのは不便すぎる。例えば、UNIX風のfindコマンド(ファイル検索ツール)で発見したファイルのパス名を、Windowsのコマンドライン・ツールにそのまま渡せないのは不便極まりない。

 歴史的に見ると、Windows(MS-DOS)上でUNIX風ツールを使う動きの発端となったと思われるアスキーの「MS-DOS Tools」では、ファイル・パスの表記としてMS-DOSのものを使用するようになっていた。MS-DOSの貧弱なコマンドライン・ツールの環境をUNIX風ツールで補強するために使われていたことから考えれば、それは当然の仕様といえる。

 しかし、最近のWindows上のUNIX風ツールについては、UNIXのパス名を使用するものが主流である。例えば、Cygwinや、Windows Services for UNIXがそれに当たる。例えば、Windows XPにインストールされたWindows Services for UNIXのうえでシェル、カレント・ディレクトリを出力するpwdコマンドを打ち込むと、以下のように表示される。

% pwd
/dev/fs/D/work

 これは、Windowsでは「D:\work」と表記されるディレクトリに相当する。見て分かるとおり、明らかにWindowsの補強のために使うには使いにくい表記である(それ故に筆者は、CygwinやWindows Services for UNIXがあるにもかかわらず、Windowsの補強という点に優れるNT版UNIX-like toolsというようなものを移植したりしている)。

 このような、使いにくい表記を使う流儀が主流となったのは、これらが既存のUNIX上のソフトウェア資産をWindows上で使うことを目的としているためではないかと思う。そのような観点からいえば、ファイル・パスの表記をできるだけUNIXに近づけることには意味がある。

 同じような意図で、MonoがWindows風のファイル・パス表記を採用することもできただろう。それはさほど難しい話ではない。例えば、Linuxのファイル・システムのツリーは、すべてCドライブ上にあると仮定して、区切り文字を円記号からバックスラッシュに入れ替えるだけでよいのである。しかし、Monoはそれを採用しなかった。それは、なぜだろうか。

 これは筆者の推測にすぎないが、その答えは、Monoの存在意義が.NET Framework上のソフトウェア資産の流用などという、志の低い安易なものではないからだと思う。あくまでMonoは、それに携わる者たち自らが開発するプログラムを実行するために生み出されたのであって、異環境の他人のソフトウェア資産をそのまま使おうなどとは思っていないのだろう。そのように考えれば、ファイル・パスの表記を、自分が使ってもいない異種OSの流儀に合わせる必要などまったくないことになる。その点で、Mono Projectは志が高いという印象を持った。安易に既存のソフトウェア資産に頼ろうとしない志の高さは、現在では貴重な価値を持つと思う。

 これは「使えるものは使う」というポリシーとは矛盾しない。自ら新しいプログラムを生み出していこうと努力する場合には、「使えるものは使う」というリアリズムが要求されるのである。

 さて、最後にファイル・パス表記の問題に限っては容易に乗り越えられることを付記しておく。上記のサンプル・プログラムをもう一度よく見ていただきたい。これはファイルのフルパス名を、ディレクトリ部とファイル名部に分解したうえで、再度連結して1つのフルパス名に戻している。この処理の過程で、ドライブ名の有無や、パスの区切り文字の相違は一切出てきていない。標準クラス・ライブラリを活用してパス名を操作する限り、たいていの操作は可能である。ここで見たように、ディレクトリ部の取り出し、ファイル名部の取り出し、パスの連結などができるほか、拡張子抜きのファイル名の取り出しや、拡張子の置き換えなどのメソッドもある。これらを誠実に活用していれば、パス名を操作する大抵のプログラムは作成できるので、パス名表記の違いを、ソース・コードを記述する際に意識する必要はない。

 なお、上記の実行例はどちらもVisual Studio .NET 2003でビルドした実行ファイルによって行われたことを付記しておく。Monoによる実行例でも正しくLinuxのパス名の分解と連結が行われているが、これもVisual Studio .NET 2003でビルドした実行ファイルの実行結果である。

Monoと.NET Frameworkとの速度比較

 さて、2種類のC#コンパイラ、2種類の実行環境のどの組み合わせでも実行ファイルが確かに実行できることは確認できた。そうすると次の興味は、どの組み合わせが最も速いかである。そこで、同じprime.exeを用いて、100000までの素数を20万回計算するのにかかった時間を調べてみた。これは、1台のPCにWindows 2000 Professional+.NET Frameworkと、Fedora Core2+Monoの双方をインストールして、その上で実行させている。単位は「分:秒」である。

  Mono上で実行 .NET Framework上で実行
Mono C#コンパイラ
18:04
11:53
マイクロソフト製C#コンパイラ
20:13
11:53
Monoと.NET Frameworkによる実行処理速度の比較
各環境でコンパイルしたprime.exeを用いて、100000までの素数を20万回計算するのにかかった時間を比較している。

 この結果を見る前に確認しておくことが1つある。それは、これらのプログラムは実行前にJITによってネイティブ・コードに変換されているということである。実行速度にはC#コンパイラだけでなくJITの能力も影響する。つまり、実行速度の優劣がC#コンパイラの優劣だけで決定するわけではない、ということである。

 さて、まず実行環境の相違であるが、表を見ると.NET Frameworkで実行する方が高速であったことが読み取れる。もちろん、この結果によって、すべてのプログラムの実行で.NET Frameworkが高速だと保証するわけではない。

 Monoで実行した場合は、コンパイラの相違によって実行結果の時間にも相違が生じていることが読み取れる。Monoで実行する場合は、マイクロソフト製C#コンパイラを用いるよりも、同じプロジェクトで開発しているC#コンパイラを使用する方が、相性が良いようである。

 また、.NET Frameworkで実行した場合は、コンパイラの相違による結果はほとんど変わらなかった。恐らく、JITがネイティブ・コードを生成する際に行われる最適化を通じて、コンパイラ間の生成コードの差異が(処理時間的な意味で)消失してしまったのだろう。

 さて、この速度差をどう見るだろうか。JITはチューン次第で速くなる技術である。生まれたばかりのMonoに、チューンを積み重ねてきたほかのJITと同等であることを望むのは少々酷かもしれない。とはいえ、限界まで性能を求めなければ、十分に実用になる速度は出ていると思う。まだスタート・ラインに立ったばかりで、これから改良が見込めることも含めて考えれば、利用を検討する価値がある速度ではないかと思う。


 INDEX
  [特集]Linuxで動く.NET環境「Mono 1.0」の実力(前編)
     1.WindowsとLinuxとを結ぶ「Mono」
     2.Monoが生まれた背景
     3.Monoの機能とインストール
     4.C#コンパイラとVB.NETコンパイラ
  [特集]Linuxで動く.NET環境「Mono 1.0」の実力(後編)
     1.バイナリの互換性を検証する
   2.環境間の相違と対策/Monoと.NET Frameworkとの速度比較
     3.XSPを用いたASP.NET
     4.Apacheを用いたASP.NET
 


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間