ページ 11

memcpy関数について

Posted: 2011年7月15日(金) 00:03
by jay
C言語標準ライブラリ関数であるmemcpy関数について素朴(?)な疑問が生じたのですが

例えばint型の配列 iData[20] という配列にあるデータを
別のint型配列 idata[20] にコピーさせる必要があるとします。

その場合、memcpy関数をつかって

コード:

memcpy(idata, iData, sizeof(int) * 20);
とした時と
for文を使って

コード:

for(int i = 0; i < 20; i++)
    idata[i] = iData[i];
としたとき
結果は同じになると思われますが、どちらの処理が早いのでしょうか?
また、深い理由がなければこっちを使った方がいい。 といったモノはあるのでしょうか?
やはり知らない人がいるかもしれないmemcpy関数よりは、for文を使った方がいいのでしょうか?

Re: memcpy関数について

Posted: 2011年7月15日(金) 00:37
by box
実験してみました。
私のところでは、for文の方が2~3倍ほど速かったです。
他の方のところではどうなるかはわかりません。

コード:

#include <stdio.h>
#include <string.h>
#include <sys/time.h>

#define N (10000000)

int main(void)
{
    static int iData[N], idata[N];
    int i;
    struct timeval ts, te;

    gettimeofday(&ts, NULL);
    memcpy(idata, iData, sizeof(int) * N);
    gettimeofday(&te, NULL);
    printf("実行時間:%.6f秒\n",
           te.tv_sec - ts.tv_sec + (te.tv_usec - ts.tv_usec) / 1000000.);

    gettimeofday(&ts, NULL);
    for (i = 0; i < N; i++) {
        idata[i] = iData[i];
    }
    gettimeofday(&te, NULL);
    printf("実行時間:%.6f秒\n",
           te.tv_sec - ts.tv_sec + (te.tv_usec - ts.tv_usec) / 1000000.);
    return 0;
}

Re: memcpy関数について

Posted: 2011年7月15日(金) 01:43
by うしお
基本的にmemcpyは悪しき習慣として有名です。
なぜなら、ちょっとしたミスで簡単にメモリアクセス違反を引き起こしたり、
C++ではpod以外のクラスをmemcpyすると未定義の動作を引き起こす可能性もあります。
計測の結果、仮にその処理系がmemcpyが速くても、
それを理由にmemcpyを使うのはかなり危険な思考だと思います。

C++ではfor分などの他、std::copyを使う方法もあります。可読性も地味にあがるかと思います。

コード:

#include <algorithm>

int main(){
	const int ARRAYSIZE = 1024;

	int source[ARRAYSIZE];
	int destination[ARRAYSIZE];

	for(int i = 0 ; i < ARRAYSIZE ; ++i)
		source[i] = i;

	std::copy(source,source + ARRAYSIZE, destination);
	return 0;
}

Re: memcpy関数について

Posted: 2011年7月15日(金) 03:45
by ISLe
memcpyは内部形式をデータ型に依らず最も高速なバイト数ごとにまとめてコピーします。
Visual C++でint型では不利ですが、short型やchar型ならmemcpyのほうが速くなるはずです。
一般的にintよりサイズが小さいとか構造体にいろんな型が混じっているときはmemcpyが有利です。
#構造体一個コピーするならふつうに代入すればコンパイラが良きに計らってくれます。

ゲームプログラムでは、バイナリデータをバッファにコピーするときとか可変長のデータを扱うときに使いますね。

Re: memcpy関数について

Posted: 2011年7月15日(金) 06:06
by h2so5
実質的に違いはありませんが、
memcpyは構造体のパディング領域もそのままコピーするのに対して、
forを使って代入した場合は有効な領域のみコピーされているようです。

ですから結果が全く同じというわけではありません。

http://ideone.com/2pVql

[訂正]
ideone上の出力が"memcmp"になってますが"memcpy" の間違いです..

Re: memcpy関数について

Posted: 2011年7月15日(金) 17:45
by ISLe

コード:

#include <stdio.h>
#include <string.h>

typedef struct {
	int x;
	short y;
} foo;

int main(void)
{
	int i;
	foo a[20], b[20], c[20];

	memset(a, 1, sizeof(foo) * 20);
	memset(b, 2, sizeof(foo) * 20);
	memset(c, 3, sizeof(foo) * 20);

	memcpy(b, a, sizeof(foo) * 20);

	for(i=0; i<20; i++) c[i] = a[i];

	printf("memcpy : %d\n", memcmp(b, a, sizeof(foo) * 20));
	printf("for    : %d\n", memcmp(c, a, sizeof(foo) * 20));

	return 0;
}
上記のコードで試してみました。
Ubuntu11.04のgcc (Ubuntu/Linaro 4.5.2-8ubuntu4) 4.5.2
Cygwinのgcc-4 (GCC) 4.3.4 20090804 (release) 1
VC++2010ExpressのMicrosoft(R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
ではmemcpy,forともに0と表示されました。
#パディングを比較して結果が違うというのがおかしな気がしますけど。

バイナリデータの圧縮伸張や画像・映像・音声処理等ではよく使われるのではないでしょうかね。
特にその手の組み込み分野でC++標準ライブラリのサポートをあてにはできませんし。
低レベル処理に手を出さず、既存ライブラリを使って賢くプログラミングすれば良いという考えにはある意味賛成です。
誰も低レベル処理に手を出さなくなったら困りますけどね。

Re: memcpy関数について

Posted: 2011年7月15日(金) 18:17
by たかぎ
ISLe さんが書きました:memcpy,forともに0と表示されました。
#パディングを比較して結果が違うというのがおかしな気がしますけど。
パディングごとmemsetでゼロクリアしているので、一致するのは当然ですね。

まあ、C言語の話をしているのにstd::copyを持ち出すのは論外としても、memcmpはそれなりに落とし穴もあります。
賢く使うのは賛成ですが、賢く使えるかどうかわからないプログラマは、いっそ手を出さない方が無難なのかもしれません。

Re: memcpy関数について

Posted: 2011年7月15日(金) 18:35
by a5ua
boxさんのコードだと、キャッシュの影響で、後半のほうが早くなる気がします。

私も実験してみたところ、memcpyの方がやや速いという結果になりました。
ideone.comでの結果 - http://ideone.com/yK8do
codepad.orgでの結果 - http://codepad.org/0hpCvEId

コード:

#include <stdio.h>
#include <string.h>
#include <time.h>

#define N 10000000

int main(void)
{	
	// memcpy
	{
		static int src[N], dst[N];
		clock_t start = clock();
		memcpy(dst, src, N * sizeof(int));
		printf("実行時間:%.6f秒\n", (clock() - start) / (double)CLOCKS_PER_SEC);
	}

	// for-loop
	{
		static int src[N], dst[N];
		clock_t start = clock();
		size_t i;
		for (i = 0; i < N; ++i) {
			dst[i] = src[i];
		}
		printf("実行時間:%.6f秒\n", (clock() - start) / (double)CLOCKS_PER_SEC);
	}
	return 0;
}

Re: memcpy関数について

Posted: 2011年7月15日(金) 18:52
by かずま
C では、構造体の代入は、パディングも含めて構造体全体のビットパターンのコピーですから、代入も memcpy も結果は同じでしょう。

C++ で、operator= を定義したクラスをメンバとして含むクラスのインスタンスの代入は、メンバごとの代入になりますから、パディングはコピーされません。

Re: memcpy関数について

Posted: 2011年7月15日(金) 19:02
by ISLe
たかぎ さんが書きました:パディングごとmemsetでゼロクリアしているので、一致するのは当然ですね。
すみません。どこでゼロクリアされているのか教えていただけませんか?
パディングの中身をどうするかは決まってなかったと思うのですが、暗黙的にmemsetでゼロクリアする仕様ってありましたっけ。
たかぎ さんが書きました:まあ、C言語の話をしているのにstd::copyを持ち出すのは論外としても、memcmpはそれなりに落とし穴もあります。
賢く使うのは賛成ですが、賢く使えるかどうかわからないプログラマは、いっそ手を出さない方が無難なのかもしれません。
memcpyを直接使わなくてもmemcpyを使う場合と同等かそれ以上に処理系が最適化してくれることを示したつもりなのですが、それを否定する投稿をされる意図は何ですか?
memcpyを使うことを否定しているようでいてmemcpyを使わせる方向へ誘導しているようですが。
memcpyを使わせたくないのか使わせたいのかどちらなのでしょう。

(追記)
memcpyを賢く使う、なんて文言もどこからでてきたんでしょう。

Re: memcpy関数について

Posted: 2011年7月15日(金) 19:15
by ISLe
いつのまにか構造体やクラスをコピーするのにmemcpyは是か非かという話になってる。

Re: memcpy関数について

Posted: 2011年7月15日(金) 22:06
by たかぎ
すみません。
勘違いやら、悪文やらでご迷惑おかけしました。
ISLe さんが書きました:
たかぎ さんが書きました:パディングごとmemsetでゼロクリアしているので、一致するのは当然ですね。
すみません。どこでゼロクリアされているのか教えていただけませんか?
パディングの中身をどうするかは決まってなかったと思うのですが、暗黙的にmemsetでゼロクリアする仕様ってありましたっけ。
勘違いでした。
1, 2, 3でクリアしていますね?
ISLe さんが書きました:
たかぎ さんが書きました:まあ、C言語の話をしているのにstd::copyを持ち出すのは論外としても、memcmpはそれなりに落とし穴もあります。
賢く使うのは賛成ですが、賢く使えるかどうかわからないプログラマは、いっそ手を出さない方が無難なのかもしれません。
memcpyを直接使わなくてもmemcpyを使う場合と同等かそれ以上に処理系が最適化してくれることを示したつもりなのですが、それを否定する投稿をされる意図は何ですか?
ここは悪文でした。
std::copyとmemcmpを同じ分の中で書いたのがダメでした。
賢く使うかどうかはmemcmpの話です。

memcpyに関しては、それでディープコピーまでやってくれると勘違いするような素人でなければ、使って問題ないと思います。
もちろん、memmoveとの使い分けができることも必須条件です。

Re: memcpy関数について

Posted: 2011年7月16日(土) 01:25
by ISLe
たかぎ さんが書きました:すみません。
勘違いやら、悪文やらでご迷惑おかけしました。
承知致しました。

構造体やクラスをコピーするのにmemcpyは是か非かという話であれば、わたしは非ですね。
絶対に使ってはいけないと思います。

わたしがmemcpyを使うのは、オリジナルから位置も長さも不定のデータをバッファにコピーする場合くらいです。
構造体としてまとめてコピーする場合も少なくないですが、絶対ポインタを含まないデータなのでディープコピーじゃなくて困ることはありませんし。
ゲームプログラムではそういう処理がけっこうな割り合いを占めます。

Re: memcpy関数について

Posted: 2011年7月16日(土) 14:40
by tk-xleader
jay さんが書きました:結果は同じになると思われますが、どちらの処理が早いのでしょうか?
また、深い理由がなければこっちを使った方がいい。 といったモノはあるのでしょうか?
やはり知らない人がいるかもしれないmemcpy関数よりは、for文を使った方がいいのでしょうか?
処理速度でいえば環境によります。最適化によって、memcpy(for文)が高速なコードが生成されることも、はたまた全く同一のコードが生成されることもあります。
深い理由がないのならどっちを使ってもいいと思います。知らない人が読むということを考えるのなら、そこにコメントで「配列のコピーを行う」とでも残しておけばいいのではないかと思います。

Re: memcpy関数について

Posted: 2011年7月16日(土) 18:32
by jay
まずはスレ立てした後で色々と立て込んでしまい、思ったより返信が遅れてしまって申し訳ないです。

そして僕が最初に求めた答えとしては
tkmakwins15 さんが書きました:処理速度でいえば環境によります。最適化によって、memcpy(for文)が高速なコードが生成されることも、はたまた全く同一のコードが生成されることもあります。
が正解ということでよろしいのでしょうか?

ちなみにa5uaさんのコードで試してみたところ、僕の環境ではmemcpyの方がやや早かったです。


使うかどうかは(そんなに多用することはまずないでしょうけど)
たかぎさんがおっしゃる通り
たかぎ さんが書きました:memcpyに関しては、それでディープコピーまでやってくれると勘違いするような素人でなければ、使って問題ないと思います。
もちろん、memmoveとの使い分けができることも必須条件です。
これが正解な気がします(まぁ、ホントの正解なんてないんでしょうけど)
tkmakwins15さんがおっしゃる通りちゃんとコメント書いておけば知らない人が見ても問題はないでしょうしね