C/C++ 実行速度

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
神崎

C/C++ 実行速度

#1

投稿記事 by 神崎 » 4年前

私は少々Javaの方で開発をしてきたのですが、今後のためにC/C++を基礎から振り返ろうと思い学習中の身です。
部分的にはJavaの方が高速に処理できる物もある程度で、漠然とC/C++の方が高速であると思っていました。
ですので、単純な処理を比較しながら学習をはじめました。ポインタの使い方など指摘がありましたらお願いします。
ご教授願いたい点は、さらに高速化できる場合どうしたらよいのかと、微差でも3Dであったり物理演算などの複雑な処理になると、圧倒的な差がでるものなのか。また、最適化を行わないで、java以上の速度を望めるのかです。

実行環境 os : win7 : cpu: i7-4770k : memo:16GB
java :Eclipse Java EE IDE for Web Developers:Version: Mars Release (4.5.0)
C++   : Visual Studio Express 2013 for Desktop


【c++】

コード:

clock_t start, end;
	int dd[100];
	start = clock();[
	for (int n = 0; n < 100000000; n++){
		for (int i = 0; i < 100; i++){
			dd[i] = i + n;
		}
	}
	end = clock();
	printf("100億回繰り返す時間: %f秒\n", (double)(end - start) / CLOCKS_PER_SEC);
【実行結果】
21.46秒(default)
2.60秒(実行速度の最大化(/O2)
【Java】

コード:

int[] i = new int[100];
		long start = System.currentTimeMillis();
		for (int n = 0; n < 100000000; n++) {
			for (int t = 0; t < 100; t++) {
				i[t] = t + n;
			}
		}
		long end = System.currentTimeMillis();
		System.out.println((end - start) + "ms");
【実行結果】
2943ms

外側for文のint nの参照が遅いっぽいので改良
[c++]

コード:

clock_t start, end;
	int dd[100];
        int* po;
	start = clock();[
	for (int n = 0; n < 100000000; n++){
        po = &n;
		for (int i = 0; i < 100; i++){
			dd[i] = i + *po;
		}
	}
	end = clock();
	printf("100億回繰り返す時間: %f秒\n", (double)(end - start) / CLOCKS_PER_SEC);
【実行結果】
17.61秒(default)
1.77秒(実行速度の最大化(/O2)

黒崎圭一
記事: 3
登録日時: 4年前

Re: C/C++ 実行速度

#2

投稿記事 by 黒崎圭一 » 4年前

こういうのはどうでしょう。

コード:

	int* dd = new int[100];
	memset(dd, 0, sizeof(int)*100);

	int* pArray = NULL;

	start = clock();

	for (int n = 0; n < 100000000; ++n)
	{
		pArray = &dd[0] - 1;

		int i = 0;

		do
			*(++pArray) = i + n;
		while((++i)!=100);
	}

	end = clock();
自分のパソコンでは、載せていただいたコードで28秒、
上記のコードで19秒くらいかかりました。(CPUが旧世代のi7-2600kでして…)
因みにjavaと同じようにするためnewキーワードを使って動的で宣言しています。

まず、速度を上げるためのプログラミング方法について
個人的に分かっている範囲で宜しければご説明いたします。
(C++並びにVS2010基準です。間違ってるところありましたらご指摘ください)


一、変数を宣言する時は、常に初期化を行ってください。
  デバッグモードでは変数と宣言と同時にメモリーを確保しますが、
  リリースモードでは変数に値を保存する時に初めてメモリーを確保したりします。

  配列を宣言するだけで初期化しまいまま使われると、
  ループの最初の速度とその次からの速度が明らかに違います。

  動的割り当てではないとしたら、以下のように初期化できます。

コード:

int dd[100] = {0,};

二、繰り返し文を使う時のインデックス変数は繰り返し文の始めに宣言した方が、ほんーの少し早いです。

  これはアクセスするメモリーアドレスの位置関係に原因があるのではないかと。
  例えば、int a[3]という変数があったとするとき、a[0], a[2], a[1] 順よりは a[0], a[1], a[2] 順が早くアクセスできます。

  同じく、コード作成方法にもよりますが、
  上記のコードでも *(++pArray) = i + n; と *(++pArray) = n + i; の速度は全然違う場合がありますね。


三、++記号を使う時にはなるべくprefix形式で使うことを心がけてください。

  繰り返し文ではこのように二つの方法で作成することができます。

コード:

for(int i=0; i<100; i++);
for(int i=0; i<100; ++i);
  が、これにはほぼ違いがありません。
  正確には二つ目の++iのように作成した方がほんの少しだけ早いとされていますが、
  コンパイラーによります。近年の大抵のC++コンパイラーでは
  二つのコード間に差はありません。

  しかし、下記の二つのコードは明らかな速度差があります。

コード:

*(pArray++) = i + n;
*(++pArray) = i + n;
  これは明らかに二つ目のコードが早いです。段違いと言っていいでしょう。
  例えば一つ目のコードのpArray++という文はパソコン内部で以下のように動作するとかしないとか。

コード:

int* temp = pArray + 1;
pArray = temp;

四、ポインターを通じてアクセスすると、アクセス数は増えるのでその分だけ速度が落ちます。

  お載せになったコードではpoという変数を利用してnにアクセスしていますが、
  これはむしろ速度低下の原因になります。

  nに直接アクセスできるものを、po, nの順に迂回してアクセスしなければならないせいです。
  それでも私がpArrayというポインター変数を用いたのは、ddが配列であるためです。

  例えば、dd[50]にアクセスするとき、パソコン内では、
  まずdd[0]にアクセスして、そこから50の分だけアドレス数を増やしてdd[50]を探し当てます。
  これはdd[0] + 50と同じようなもので、
  もし事前にint* pArray = &dd[50]; という宣言をしているとなると
  pArrayを通じて一回だけ迂回してからアクセスした方が断然早いものです。


五、最適化の仕上げに、コードのサイクル数を確認してみましょう。

  私のコードでdo/while文を使った理由は、単に演算回数の軽減のためです。
  例えば以下のようなコードだとすると、

コード:

for(int i=0; i<100; ++i)
  *(++pArray) = i + n;
  演算回数は以下のようになります。(もちろんコンパイラーによって違いますが)
   ① int i =0
   ② i<100 確認
   ③ ++i の後 i<100確認して、falseならbreak

  ここで、問題なのは②です。
  do/while文で書き換えるとこの一動作がなくなるため、
  合計一億回の演算がなくなりますね。だからほんの少し実行速度が速くなります。

  同じ理由で、nのfor文はdo/whileに書き換えても速いなりません。
  一回の演算など、時間測定すらできない程の差なのですから。

  まあ、このような最適化方法は、普通コードを作成する途中では難しいですね。
  必要な時だけ後作業として行うのがよいでしょう。



それじゃ、お役に立てましたら幸いです。

黒崎圭一
記事: 3
登録日時: 4年前

Re: C/C++ 実行速度

#3

投稿記事 by 黒崎圭一 » 4年前

すみません。二番の説明が不足していたと思いますので追記します。

つまり、以下のようなコードより、

コード:

int n =0;
for(n = 0; n<100; ++n);
こうして作成した方がいいということです。

コード:

for(int n=0; n<100; ++n);
これは、事前に宣言した変数のメモリーのアドレスまで行くより
新しく宣言した変数を利用した方が少しだけ早くアクセスできるためだそうです。

ではでは…

たいちう
記事: 418
登録日時: 9年前

Re: C/C++ 実行速度

#4

投稿記事 by たいちう » 4年前

> また、最適化を行わないで、java以上の速度を望めるのかです。

ここで言う最適化は、何を指しているのでしょうか?
プログラマが色々工夫した結果でしょうか?
コンパイラが最適化した結果でしょうか?

例として挙げているコードでは、演算した結果を使っていません。
最近のコンパイラが本気で最適化したら、forループを実行しないのではないですか?
実行結果が20秒もかかっていることから、デバッグモードでビルド・実行したのではないかと思いますが、
コンパイルオプションなど、ビルドの設定を教えてください。

速さを求めるならば、コンパイラに最適化させないで他の言語と比較しても意味がないと思います。
意味があるのなら、説明をお願いします。


黒崎さんが書いているような最適化の定石についてですが、
コンパイラを作っているプログラマも、そのような定石の大半を知っていると思います。
当然取り入れて最適化するコンパイラを作っているでしょうから、
まずは自然で合理的なプログラムを作ることが、
一般のプログラマの行える最適な最適化ではないでしょうか。
下手をしたら、時代遅れの最適化テクニックのせいで、
コンパイラが最適化した結果が悪化する恐れもあります。

とっち
記事: 56
登録日時: 7年前
住所: 岡山

Re: C/C++ 実行速度

#5

投稿記事 by とっち » 4年前

私も気になったので少し実験してみました。
神崎さんが最初に示しているc++の二つのコードを以下の環境で動作させてみました。
※なお、提示されてるコードではコンパイラの最適化によって0[sec]でしたので、以下のコードを付け足してます

コード:

printf("%d\n",dd[0]);
環境
OS: win7, CPU: Core i7 M620 2.67GHz
コンパイラ: gcc 4.7.2

結果:
最初のほう: 6.88[sec]
改良後: 6.86[sec]

結果から誤差レベルしか違わないことが分かります。
そこで今度は-Sオプションでアセンブラを出力し、それをdiffコマンドで比較してみました。
その結果完全に一致していました。

gccでは神崎さんの最適化は意味のないものとなってしまったようです。
一般的に手動で最適化はするべきでありません。
今回のように無意味なものになってしまうばかりでなく、コードの意味が分かりにくくなってしまうというデメリットがあります。
最適化はプログラムが完成した後、どうしてもという時に限り最終手段として利用すればいいかと思います。
※私も別の分野で最適化に携わったことありますが、最大で理論ベースの最適化で5倍程度、プログラムの最適化で2倍程度しか改善できませんでした。

黒崎圭一
記事: 3
登録日時: 4年前

Re: C/C++ 実行速度

#6

投稿記事 by 黒崎圭一 » 4年前

たいちう さんが書きました: 黒崎さんが書いているような最適化の定石についてですが、
コンパイラを作っているプログラマも、そのような定石の大半を知っていると思います。
おっしゃる通りですね。

私から述べた最適化テクニックは基本的なことであって、
普通はVisualStudioでサポートされてる最適化オプションを使ったら何の意味も持ちません。

重要なのはプログラムの作成スタイルというより
メモリーへのアクセスを如何に効率的に行うかです。

ゼロからコードで作られたメモリースペースなら何の違いも見出せませんが、
特定のデータ(画像や動画、バイナリデータなど)を扱う時はその限りではありません。

扱うデータがどういう仕組みになっているか分からない以上
データへのアクセスをコンパイラー任せっきりには出来ないものですから。

閉鎖

“C言語何でも質問掲示板” へ戻る