2005年09月30日

プログラミング言語の比較(5回目)

★測定方法

以下の環境でそれぞれのプログラムを5回動かし、その平均値を測定結果とします。
また、コピー対象のファイルは、約300MBの大きさの物を使用しています。

【測定環境】
OSWindows2000 SP2
CPUPentium3 860 MHz
Memory384 MB

★実行結果

結果は、以下の通りです。

【300MBファイルコピーの実行結果(秒)】

CC++JAVA(io) JAVA(nio)
1回目38.83641.4739.37632.657
2回目38.61541.7542.93227.65
3回目39.99741.58939.88729.683
4回目38.40541.42938.85629.833
5回目39.41641.6541.07929.763
平均39.053841.577640.42629.9172

この結果は、見る人によって、予想通りであったり、意外な結果であったりすると思います。
但し、1つ注意して頂きたいのは、この結果だけで言語の優劣が決まる訳では無いと言うことです。

例えば、今回の計測対象はファイルのコピー処理その物(execute関数の実行時間)だけですが、実際にプログラムの起動から終了までの時間を計測対象にすれば結果は当然変わってきます。

プログラムについても、使用するバッファサイズを適切に設定したり、C/C++ではOSに依存した機能(標準ライブラリ以外の関数)を利用する事でも、性能向上が見込めます。
(今回は比較対象外ですが、その他にも実行モジュールのサイズ、実行中の使用メモリサイズ等、速度以外のパフォーマンスでも違いがあります。)

さらに興味のある方は、これまで示したプログラムを改良したり、他の言語によるプログラムを作成して比較を行ってみて下さい。

投稿者 Tsuda : 18:38 | コメント (0) | トラックバック

2005年09月27日

プログラミング言語の比較(4回目)

★JAVAで作成2(nioパッケージ利用)

JAVAではバージョン1.4から、NIOパッケージ(New I/O)が加えられました。
以下のプログラムは、その機能を利用したバージョンです。

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;

public class CopyByJava2 {
	public void execute(String src, String dest) throws IOException {
		FileInputStream in = null;
		FileOutputStream out = null;
		try {
			// ファイルのオープン
			in = new FileInputStream(src);
			out = new FileOutputStream(dest);
			
			// コピー準備
			FileChannel cIn = in.getChannel();
			FileChannel cOut = out.getChannel();

			// コピー実行
			cIn.transferTo(0, cIn.size(), cOut);
		}
		finally {
			// ファイルクローズ
			try {
				if(in != null) {
					in.close();
				}
			}
			catch(IOException e) {
			}
			try {
				if(out != null) {
					out.close();
				}
			}
			catch(IOException e) {
			}
		}
	}
	
	public static void main(String[] args) {
		if(args.length < 2) {
			System.out.println("java CopyByJava2 [コピー元ファイルパス] [コピー先ファイルパス]");
			return;
		}
		else if(args[0].equals(args[1])) {
			System.out.println("コピー元とコピー先が同じです。");
			return;
		}
		
		long start = System.currentTimeMillis();
		try {
			new CopyByJava2().execute(args[0], args[1]);
		}
		catch(IOException e) {
			e.printStackTrace();
			return;
		}
		long end = System.currentTimeMillis();
		System.out.println((end - start)/1000.0 + " 秒経過");
	}
}

JAVA(ioパッケージ利用)と殆ど同じですが、FileChannelクラスが登場しています。
このFileChannelクラスはNIOで提供される機能の一つです。そして、NIOは主に(速度的な)パフォーマンスを上げる事を目的に導入されました。
実際にパフォーマンス上の利点があるのでしょうか?その事を調べる目的も込めて比較対象に加えています。

次回は、これまでに挙げた4種類のプログラムを使用して、実際に測定をしてみましょう。

投稿者 Tsuda : 16:18 | コメント (0) | トラックバック

2005年09月26日

プログラミング言語の比較(3回目)

★JAVAで作成1(ioパッケージ利用)

JAVAでは、2通りの方法でファイルコピーを行えます。今回はその内の1つである、ioパッケージ利用編です。
JAVAのバージョンは1.4を使用します。

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class CopyByJava1 {
	public void execute(String src, String dest) throws IOException {
		int buffer_size = 2048;
		byte[] buffer = new byte[buffer_size];
		
		InputStream in = null;
		OutputStream out = null;
		try {
			// ファイルのオープン & ストリームのバッファサイズ指定
			in = new BufferedInputStream(new FileInputStream(src), buffer_size);
			out = new BufferedOutputStream(new FileOutputStream(dest), buffer_size);
			
			// コピー実行
			int readsize;
			while((readsize = in.read(buffer)) != -1) {
				out.write(buffer, 0, readsize);
			}
		}
		finally {
			// ファイルクローズ
			try {
				if(in != null) {
					in.close();
				}
			}
			catch(IOException e) {
			}
			try {
				if(out != null) {
					out.close();
				}
			}
			catch(IOException e) {
			}
		}
	}
	
	public static void main(String[] args) {
		if(args.length < 2) {
			System.out.println("java CopyByJava1 [コピー元ファイルパス] [コピー先ファイルパス]");
			return;
		}
		else if(args[0].equals(args[1])) {
			System.out.println("コピー元とコピー先が同じです。");
			return;
		}
		
		long start = System.currentTimeMillis();
		try {
			new CopyByJava1().execute(args[0], args[1]);
		}
		catch(IOException e) {
			e.printStackTrace();
			return;
		}
		long end = System.currentTimeMillis();
		System.out.println((end - start)/1000.0 + " 秒経過");
	}
}

前回と同じく、以下の項目をちょっとだけ見てみましょう。

▽クラス
クラスが登場する点はC++と同じです。但し、自作しているCopyByJava1クラスがあります。
JAVAは単独でメソッド(C/C++における関数のこと)が存在出来ません。
必ず、クラスのメンバーとしてメソッドが存在します。このルールは mainメソッドにも当てはまる為、最低でも1つのクラスが必要になります。

▽例外処理機構
try-catchは同じです。但し、JAVAではfinallyが存在します。
tryブロックに処理が入った場合、例外の発生有無に関らず、必ずfinallyブロックを通ります。
処理が成功・失敗のいずれであっても、行う処理が有る場合にfinallyを用います。

▽リソースの解放
JAVAではC++と同じくクラスが登場する事は、先ほど説明しました。
では何故、C++で必要の無かったファイルのクローズを行っているのでしょう?
JAVAにはデストラクタが無いのでしょうか?それともFileInputStreamやFileOutputStreamクラスのデストラクタでは自動的にファイルをクローズしてくれないのでしょうか?
答えは否です。デストラクタに相当する物(finalizeメソッド)も有りますし、該当クラスのfinalizeメソッドでファイルのクローズも行われます。
JAVAでは、C/C++に無い機能としてガーベッジコレクションがあります。これにより、動的に確保したメモリ(上に構築されたクラス)の解放はプログラマが意識する必要が無いので、プログラマとしては非常に助かります。
しかし、ファイルディスクプリタやソケットなどの非メモリ・リソースはこの対象外であり、これらは(finalizeメソッドの中で処理すれば)メモリ回収のついでとして解 放されるだけです。
つまり、メモリ・リソースが潤沢に存在しても、非メモリ・リソースの不足が発生する可能性があります。この様な状態を極力起さない為には、プログラマが責任を持っ て非メモリ・リソースの解放を行う事が必要になります。(その為に、先のfinallyを利用してファイルクローズを行っています)

次回は、もう1つのJAVAである、nioパッケージ利用編です。

投稿者 Tsuda : 16:30 | コメント (0) | トラックバック

2005年09月25日

プログラミング言語の比較(2回目)

★C++で作成 今回はC++ですね。
かなり間が空いてしまったので、早速行きましょう。
#include 
#include 
#include 
#include 

using namespace std;

void execute(char* src, char* dest) throw (ios_base::failure) {
	const int buffer_size = 2048;
	char buffer[buffer_size];
	// ファイルオープン
	ifstream in(src, ios_base::in | ios_base::binary);
	if(!in.is_open()) {
		throw ios_base::failure(src);
	}
	ofstream out(dest, ios_base::out |
			ios_base::binary | ios_base::trunc);
	if(!out.is_open()) {
		throw ios_base::failure(dest);
	}
	
	// ストリームのバッファサイズ指定
	in.rdbuf()->pubsetbuf(0, buffer_size);
	out.rdbuf()->pubsetbuf(0, buffer_size);
	
	// コピー実行
	out << in.rdbuf();
}

int main(int argc, char** args) {
	if(argc < 3) {
		cout << args[0] << " [コピー元ファイルパス] [コピー先ファイルパス]" << endl;
		return 0;
	}
	else if(!strcmp(args[1], args[2])) {
		cout << "コピー元とコピー先が同じです。" << endl;
		return 1;
	}
	
	int start = clock();
	try {
		execute(args[1], args[2]);
	}
	catch(const ios_base::failure& e) {
		cout << e.what() << " ファイルが見つかりません。" << endl;
		return 1;
	}
	int end = clock();
	cout << (end - start)/1000.0 << " 秒経過" << endl;
	
	return 0;
}
関数の構成・処理順序は、他の言語で作成した物と比べ易いように極力似せて作っています。
但し、そうは言っても違う所は当然あるので、以下の項目をちょっとだけ見てみましょう。(CとC++の違いを全て説明すると趣旨が曖昧になるので、C++に関して詳しく知りたければ、こちらへ) ▽例外処理機構
main関数から execute関数を呼び出す前後にtry-catch文、execute関数ではthrow文があります。これが例外処理機構です。
例外処理機構では、エラーが発生した場合、エラー発生箇所からエラー処理部分まで一気に処理を移す事が可能です。
(これにより、処理成否を判定するif文のネストや、エラー処理部分まで処理を飛ばす為のgoto文を削減する事が出来ます)

▽演算子のオーバーロード
ファイルのコピーを以下の1行で行っています。
  out << in.rdbuf();
CやJAVAしか知らない状態で見ると、シフト演算を行っているかのように見えますが、C++では演算子のオーバーロードが可能な為、これでコピーが行われます。
実際には以下の記述と同義です。
  out.operator<<(in.rdbuf());
オーバーロードは、直感的な判り易さを与えます。この例では、「inの内容をoutに入れる(コピーする)」と言った所でしょうか。

▽リソースの解放
さて、Cのプログラムと見比べると、ファイルをクローズする処理がありません。一体、どうなっているのでしょうか?
C++ではクラスと呼ばれるデータ構造が登場します。ファイル操作をしている、ifstreamやofstreamもクラスです。
さらに、クラスにはデストラクタと呼ばれる、メモリ上に構築されたクラスが破棄される直前に実行される(メンバ)関数があります。
ifstreamやofstreamクラスのデストラクタでは、開いているファイルをクローズする処理が行われるので、このクラスを使用した場合、プログラマによる明示的なクロー ズは不要です。(auto変数でない場合、クラス自体の明示的な削除が必要になるのですが、それに付いての説明は割愛します)

次回は、JAVAです。

投稿者 Tsuda : 23:09 | コメント (0) | トラックバック

2005年08月24日

プログラミング言語の比較(1回目)

プログラミング言語と一口で言っても、
C/C++/JAVA/Basic/Fortran/Pascal・・・等々、
とても此処には挙げ切れないほどの種類があります。
アプリケーションを作成する際に、どのプログラミング言語を選択するかは、「アプリケーションの目的を達成できるか?」、「使用する言語に精通したプログラマを揃えられるか?」、「開発に必要なコストは?」、「実行速度は?」等の条件を総合的に考慮して行われます。
(実際には、お客様の指定、既存アプリケーション拡張等の理由で、決定される事も多々あります)

今回は、複数の言語で同じ処理を行うプログラムを作成し、言語毎の比較をしてみたいと思います。
但し、何を持って比較するかと言うのは考え出すと難しいので、定量的に計測できる「実行速度」に焦点を当ててみましょう。(また、それぞれの言語の文法・設計思想の違いもプログラムを見比べる事によって実感出来ると思います)

まず必要なのは、比較対象の言語ですが、この記事を書いている人間が使用できる言語などたがが知れている為、無条件に以下で決定です。


  1. C
  2. C++
  3. JAVA

(注:1つだけルールを設けます。プログラム中で使用するライブラリは、言語の標準ライブラリのみとします。)

後は、何をするプログラム?ですが、複雑な事を3つの言語で書くなんて事をすると、廃人になりかねないので、単純にファイルコピーを行うプログラムにします。


★Cで作成

早速、C言語でファイルコピーのプログラムを作成していきたい所ですが、その前に、C/C++の開発で使用するコンパイラーを示します。
Borland C++ Compiler 5.5は、Windows環境で利用できるフリーのコンパイラです。(これから紹介するプログラムは、GCCやVC++でも問題無くコンパイル出来ると思いますが、試していないので不具合が有る場合は、ご了承下さい。)

さて、実際に作成したプログラムがこれです。(料理番組みたい・・・)

--- CopyByC.c


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#define BUFFER_SIZE 2048
int execute(char* src, char* dest) {
const int buffer_size = BUFFER_SIZE;
char buffer[BUFFER_SIZE];
FILE *in, *out;
int readsize;
/* ファイルオープン */
in = fopen(src, "rb");
if(in == NULL) {
printf("%s ファイルが見つかりません。\n", src);
return 1;
}
out = fopen(dest, "wb");
if(out == NULL) {
printf("%s ファイルが見つかりません。\n", dest);
fclose(in);
return 1;
}
/* ストリームのバッファサイズ指定 */
setvbuf(in, NULL, _IOFBF, buffer_size);
setvbuf(out, NULL, _IOFBF, buffer_size);
/* コピー実行 */
while((readsize = fread(buffer, 1, buffer_size, in)) != 0) {
fwrite(buffer, 1, readsize, out);
}
/* ファイルクローズ */
fclose(in);
fclose(out);
return 0;
}

int main(int argc, char** args) {
int start, end;
if(argc < 3) {
printf("%s [コピー元ファイルパス] [コピー先ファイルパス]\n", args[0]);
return 0;
}
else if(!strcmp(args[1], args[2])) {
printf("コピー元とコピー先が同じです。\n");
return 1;
}
start = clock();
if(execute(args[1], args[2])) {
return 1;
}
end = clock();
printf("%f 秒経過\n", (end - start)/1000.0);
return 0;
}

簡単にプログラムの解説ですが、execute関数では、コピー元ファイルとコピー先ファイルをオープンし、コピー&クローズの処理を行っています。
また、main関数では、execute関数の実行に掛かる時間を計測し出力します。(この時間をコピーに要した時間とします)

取り合えず、今回はここまでです。

投稿者 Tsuda : 19:03 | コメント (0) | トラックバック