Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

fputc と fwrite の速度

1,807 views
Skip to first unread message

KIMURA Shigenobu

unread,
Dec 3, 2000, 3:00:00 AM12/3/00
to
> ある長いバイト列があるときに、それをファイルに書き込むとします。
> この時に、for内でfputcを使って1バイトずつ書いていくのと、
> fwriteを使ってまとまったバイト数まとめて書いていくのと
> どちらのほうが処理速度が早くなるでしょうか。
> ぱっと見た目にはfwriteのほうがループの回数が少ないので
> 早く済みそうですが、fwriteが内部でfputcを呼び出している
> だけだとすると結局遅くなってしまいます。

やってみれば?

% cat fputs.c
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

main(int c, char *v[])
{
int i, s, N;
FILE *p;
char *buf;
clock_t t;

N = 100;
if (c > 1) N = atoi(v[1]);
p = fopen("hoge.tmp","w");
buf = malloc(sizeof(char)*N);
t = clock();
for (i=0; i < N; i++) fputc((int)buf++,p);
s = clock() - t;
printf("%d %d %f\n", N, s, (double)s/CLOCKS_PER_SEC);
return 0;
}
% cat fwrite.c
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

main(int c, char *v[])
{
int i, s, func, N;
FILE *p;
char *buf;
clock_t t;

N = 100;
func = 0;
if (c > 1) N = atoi(v[1]);
p = fopen("hoge.tmp","w");
buf = malloc(sizeof(char)*N);
t = clock();
fwrite(buf, N, sizeof(char), p);
s = clock() - t;
printf("%d %d %f\n", N, s, (double)s/CLOCKS_PER_SEC);
return 0;
}
% cc -O -o fputs fputs.c
% cc -O -o fwrite fwrite.c
% for i in 1 2 3 4 5 6; do ./fputs `echo 10 ^ $i | bc`; done
10 0 0.000000
100 0 0.000000
1000 0 0.000000
10000 1 0.010000
100000 5 0.050000
1000000 45 0.450000
% for i in 1 2 3 4 5 6; do ./fwrite `echo 10 ^ $i | bc`; done
10 0 0.000000
100 0 0.000000
1000 0 0.000000
10000 0 0.000000
100000 1 0.010000
1000000 15 0.150000
%

時刻の精度がもっと欲しければ、
頭出しして、何回か繰り返したり、実時間も
欲しいときは、getrusage(2) や gettimeofday(2) を
使ったりすればいいかもしれません。

木村 栄伸


KIMURA Shigenobu

unread,
Dec 3, 2000, 3:00:00 AM12/3/00
to
ばぐ、

KIMURA Shigenobu <sk...@mac.com> writes:
> for (i=0; i < N; i++) fputc((int)buf++,p);
% (echo 'g/(int)buf/s//(int)*buf/'; echo 'wq') | ed fputs.c
してください。


Arita

unread,
Dec 3, 2000, 9:19:23 PM12/3/00
to
有田と申します。

ある長いバイト列があるときに、それをファイルに書き込むとします。
この時に、for内でfputcを使って1バイトずつ書いていくのと、
fwriteを使ってまとまったバイト数まとめて書いていくのと
どちらのほうが処理速度が早くなるでしょうか。
ぱっと見た目にはfwriteのほうがループの回数が少ないので
早く済みそうですが、fwriteが内部でfputcを呼び出している
だけだとすると結局遅くなってしまいます。

とりあえず処理系はFreeBSDですがWin32の場合でもかまわないです。

ご存知の方が折られましたら教えてください。

有田

Junn Ohta

unread,
Dec 3, 2000, 10:28:43 PM12/3/00
to
fj.comp.lang.c,fj.comp.lang.c++の記事<3A2AFF2B...@geocities.co.jp>で
bar...@geocities.co.jpさんは書きました。

> ある長いバイト列があるときに、それをファイルに書き込むとします。
> この時に、for内でfputcを使って1バイトずつ書いていくのと、
> fwriteを使ってまとまったバイト数まとめて書いていくのと
> どちらのほうが処理速度が早くなるでしょうか。

どちらも渡されたデータを内部バッファーにためて、バ
ッファーがいっぱいになったらwrite()で書き出してい
るだけですから、ファイル入出力のほうがメモリー操作
より格段に遅いことを考えると、本質的には同じです。
--
太田純(Junn Ohta) (株)リコー/新横浜事業所
oh...@sdg.mdd.ricoh.co.jp

KATO,Kenji

unread,
Dec 3, 2000, 10:24:23 PM12/3/00
to
"Arita" <bar...@geocities.co.jp> wrote
in message news:3A2AFF2B...@geocities.co.jp...

> ある長いバイト列があるときに、それをファイルに書き込むとします。
> この時に、for内でfputcを使って1バイトずつ書いていくのと、
> fwriteを使ってまとまったバイト数まとめて書いていくのと
> どちらのほうが処理速度が早くなるでしょうか。
> ぱっと見た目にはfwriteのほうがループの回数が少ないので
> 早く済みそうですが、fwriteが内部でfputcを呼び出している
> だけだとすると結局遅くなってしまいます。
>
> とりあえず処理系はFreeBSDですがWin32の場合でもかまわないです。

処理系というよりは、コンパイラ依存でしょうね。
一般的には、frwite のほうがライブラリ内部で
最適化されている可能性が高いと思いますが。

--
========================================
KATO Kenji (ken...@cij.co.jp)
========================================


MIYASAKA Masaru

unread,
Dec 3, 2000, 11:39:39 PM12/3/00
to
"Arita" <bar...@geocities.co.jp> wrote in message
news:3A2AFF2B...@geocities.co.jp...
> この時に、for内でfputcを使って1バイトずつ書いていくのと、
> fwriteを使ってまとまったバイト数まとめて書いていくのと
> どちらのほうが処理速度が早くなるでしょうか。
> ぱっと見た目にはfwriteのほうがループの回数が少ないので
> 早く済みそうですが、fwriteが内部でfputcを呼び出している
> だけだとすると結局遅くなってしまいます。

ループの中で、1バイトごとに関数呼び出しが起こることを考えると、
for + fputc() よりも fwrite の方が若干速いと思います。

fwrite() では、memcpy() で書き出しバッファに対象のデータをコピーし、
バッファが満杯になったら write() で書き出す、という実装になっている
ものが多いです。中には、対象のデータのサイズがバッファサイズよりも
大きいときはバッファをバイパスして直接 write() するというものもあります。

もちろん fwrite() の実装法はコンパイラ(に附属のライブラリ)によって
違いますから、詳しいことはそのライブラリのソースコードを見るのが良いと
思います。


---------------------------------
宮坂 賢 (Miyasaka, Masaru)
Asahikawa-City, Hokkaido, Japan
e-mail : alk...@coral.ocn.ne.jp

Junn Ohta

unread,
Dec 4, 2000, 12:21:52 AM12/4/00
to
fj.comp.lang.c,fj.comp.lang.c++の記事<90f77v$3i2$1...@nn-tk106.ocn.ad.jp>で
alk...@coral.ocn.ne.jpさんは書きました。

> ループの中で、1バイトごとに関数呼び出しが起こることを考えると、
> for + fputc() よりも fwrite の方が若干速いと思います。

元記事ではfputc()との比較になっていますが、べつに
fputc()である必要はなくて、putc()だってよいわけで
す。だとすると関数呼び出しのオーバーヘッドは本質的
ではないでしょう。

Shiroh Sado

unread,
Dec 4, 2000, 3:00:00 AM12/4/00
to

佐渡です。

スレッド「Re: fputc と fwrite の速度」での
oh...@src.ricoh.co.jp氏の発言 <90f9lg$nb0$1...@ns.src.ricoh.co.jp> より引用


>> fj.comp.lang.c,fj.comp.lang.c++の記事<90f77v$3i2$1...@nn-tk106.ocn.ad.jp>で
>> alk...@coral.ocn.ne.jpさんは書きました。
>> > ループの中で、1バイトごとに関数呼び出しが起こることを考えると、
>> > for + fputc() よりも fwrite の方が若干速いと思います。
>>
>> 元記事ではfputc()との比較になっていますが、べつに
>> fputc()である必要はなくて、putc()だってよいわけで
>> す。だとすると関数呼び出しのオーバーヘッドは本質的
>> ではないでしょう。

alk...@coral.ocn.ne.jpさんが書いているのは、「関数呼び
出しのオーバーヘッド」ではなくて、関数内部での処理の所
要時間のことではないでしょうか。

ところで、自分も、fputc/putcの方がfwriteより遅いような
気がしていたので、次の方法で比較をしてみました。

・2**20個の要素を持つchar型の配列の内容をファイルまたは
/dev/nullに書き出す処理を、
・fputc, putc, fwriteのそれぞれの関数を用いて行い、これ
にかかった時間をclock(3)関数で計測して、
・それを10セット繰り返して各々の合計を取る

これをするプログラムを本記事末尾につけます。
(すみません、Linuxのclock(3)ってPOSIX準拠のtimes(2)の
返り値とほぼ同じみたいです。そっちにした方が良かった
かも知れません)。

それで、vine linux 2.1パッケージのgcc(egcs-2.91.66)を
用いてコンパイルして、PentiumII/300MHz のPC (HDDのスペ
ックは知りません、すみません) で計測した結果を次の表に
つけます。

──────────────────────────
出力先 最適化 fputc putc fwrite
──────────────────────────
ファイル なし 22.40 21.35 1.530
ファイル -O3 20.31 20.66 1.500
/dev/null なし 20.75 19.71 0.000e+00(*)
/dev/null -O3 18.65 19.00 0.000e+00(*)
──────────────────────────

数字の単位は秒です。
( (*)のところは、数字が小さいためうまく計測できていない )

一応4桁表示してますが、数回実行するとばらつきがあって、
誤差は100~200msぐらいだと思います。

fputcとputcの比較と言う点ではあんまり面白くないですが
(なぜか、最適化オプション付きでコンパイルした場合はfputcの
方がちょっとだけ速そうに見えるのですが、これは、たまたま
誤差がfputcに有利なように表れただけだと思います)、この
場合fwriteと比較すると10倍ぐらい遅くなってしまっていますし、
メモリからHDDにたかだか10MB吐き出す(正確には1MB吐き出すを
10回)だけで20秒以上所要しているので、既に存在する大きな文
字列をファイルに書き出す場合は、fputc/putcより、fwriteの方
が良いのではないでしょうか。

# /dev/nullを対象にした場合の、fwriteは所要時間が計れてい
# ません。計るのを繰り返すより、繰り返してから計った方が良
# かったのかも知れません。

---
佐渡詩郎 (さど しろう) / e-mail : sa...@smlab.tutkie.tut.ac.jp

/*
fputfwrite.c

fputc(), putc(), fwrite() の処理時間の比較
*/

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

#define USE_CLOCK

/*
#define OUTPUT "/dev/null"
*/
#define OUTPUT "/lang/data/sado/remove.me"
#define N (10*1024*1024)
#define M (10)

char buf[N];

/* 出力用ファイルを開く */
FILE *fo() {
FILE *fp;

if ( ! ( fp = fopen( OUTPUT, "wb" ) ) ) {
fprintf( stderr, "can't open %s for write\n", OUTPUT );
}
return fp;
}

/*
書き出し

flag==0 ... fputcを使用
flag==1 ... putcを使用
flag==2 ... fwriteを使用
*/
double test_output( int flag ) {
clock_t t0, t1;
FILE *fp;
int i;
struct tms timebuf; /* 値は使用しない */

t0 = clock();
fp = fo();
switch( flag ) {
case 0:
for( i=0 ; i < N ; i++ ) {
fputc( buf[i], fp );
}
break;
case 1:
for( i=0 ; i < N ; i++ ) {
putc( buf[i], fp );
}
break;
case 2:
fwrite( buf, 1, N, fp );
break;
}
fclose(fp);
t1 = clock();

return (double) (t1-t0) / CLOCKS_PER_SEC;
}

int main() {
int i;
double time_fputc=0, time_putc=0, time_fwrite=0;

test_output(1);
for( i=0 ; i<M ; i++ ) {
time_fputc += test_output( 0 );
time_putc += test_output( 1 );
time_fwrite += test_output( 2 );
}
printf( "output : %s\n", OUTPUT );
printf( "size : %d (%d times)\n", N, M );
printf( "fputc ... %5.3e, putc ... %5.3e, fwrite ... %5.3e\n",
time_fputc, time_putc, time_fwrite );

return 0;
}

zea

unread,
Dec 4, 2000, 3:00:00 AM12/4/00
to
ZEAです

本題からちょっとそれますが
最近のPCは早いんで、あまり差がないと思いますが
fwriteのパラメータ2と3は入れ替えて指定しても
動作的には同じなんですが、速度は違っていたと思います。
どっちがどっちか分かりませんがFDDあたりに書き出してみ
れば、わかるかなぁ?

# MS-DOS時代の記憶ですけど...

by ZEA (z...@wolf.email.ne.jp)

Masamichi Takatsu

unread,
Dec 4, 2000, 3:00:00 AM12/4/00
to
高津@ドーガです。

記事 <90fr4p$flr$1...@news.tut.ac.jp> で
Shiroh Sadoさんは書きました

> ・2**20個の要素を持つchar型の配列の内容をファイルまたは
> /dev/nullに書き出す処理を、
> ・fputc, putc, fwriteのそれぞれの関数を用いて行い、これ
> にかかった時間をclock(3)関数で計測して、
> ・それを10セット繰り返して各々の合計を取る

 このベンチマークでは、出力すべきデータ列がすでにchar型の配列に格納さ
れているのが前提になっていますが、データ列が逐次算出されるような処理で
putc の替わりに fwrite で出力することを考えた場合、

・バッファにデータを格納する
・もしバッファが一杯になっていたらfwriteする

というよけいな(putcと同じような)処理が必要になり、結果として fputc との
差は縮まると思います。

以下、8M個のデータ(内容はi++&255)を
・計算のみ
・putc
・fputc
・fwrite バッファサイズ 256Bytes
・fwrite バッファサイズ 1KBytes
・fwrite バッファサイズ 32KBytes
・fwrite バッファサイズ 8MBytes(バッファチェック無し)
で10回出力した時の時間を計測した物です。ソースは末尾に。

Windows FreeBSD
計算のみ 0.75s 2.52s
putc 7.02s 16.57s
fputc 22.72s 36.06s
fwrite 256 6.46s 8.90s
fwrite 1K 4.67s 8.75s
fwrite 32K 3.80s 9.03s
fwrite 8M 5.95s 10.05s

となりました。
Windows: Windows 98SE、K6-III 450MHz、Borland C++ 5.5、-O
FreeBSD: FreeBSD 4.0、Pentium 133MHz、gcc 2.95.2、-O

putc が健闘しています。私はもっと(fwriteより数倍くらいは)遅いと思って
いたのでちょっと意外でした。

メモリを全量取るよりもマメにfwriteした方が速いのはデータキャッシュの影響
でしょうか。

ソースコードの可読性の点から考えると、putc で出力、というのも検討の余地
はまだ残ってそうな結果だと思います。


なお、計測に使用したソースは以下の通りです。

---ここから
#include <stdio.h>
#include <stdlib.h>
#ifdef __WIN32__
#include <windows.h>
#else
#include <time.h>
#endif

#define OUTPUT "test.dat"
#define SIZE (8*1024*1024)
#define COUNT 10
#define BUFFER1 (256)
#define BUFFER2 (1024)
#define BUFFER3 (32*1024)

int output_none(FILE *fp)
{
int i, c;
int temp;
(void)fp;
for (i = 0; i < SIZE; i++) {
c = i & 255;
temp += c;
}
return temp;
}


void output_putc(FILE *fp)
{
int i, c;
for (i = 0; i < SIZE; i++) {
c = i & 255;
putc(c, fp);
}
}


void output_fputc(FILE *fp)
{
int i, c;
for (i = 0; i < SIZE; i++) {
c = i & 255;
fputc(c, fp);
}
}

void output_fwrite(FILE *fp, char *buffer, int bufsize)
{
int i, c;
char *bufend = buffer + bufsize;
char *ptr = buffer;
for (i = 0; i < SIZE; i++) {
c = i & 255;
*ptr++ = c;
if (ptr == bufend) {
fwrite(buffer, 1, bufsize, fp);
ptr = buffer;
}
}
if (ptr != buffer) {
fwrite(buffer, 1, ptr - buffer, fp);
}
}

void output_fwrite_nocheck(FILE *fp, char *buffer)
{
int i, c;
char *ptr = buffer;
for (i = 0; i < SIZE; i++) {
c = i & 255;
*ptr++ = c;
}
fwrite(buffer, 1, ptr - buffer, fp);
}

double output(int type)
{
FILE *fp;
#ifdef __WIN32__
DWORD time1, time2;
#else
clock_t time1, time2;
#endif
char *buffer;
fp = fopen(OUTPUT, "wb");
if (fp == NULL) {printf("cannot open %s\n", OUTPUT); exit(1);}
buffer = malloc(SIZE);
#ifdef __WIN32__
time1 = GetTickCount();
#else
time1 = clock();
#endif
switch (type) {
case 0: output_none(fp); break;
case 1: output_putc(fp); break;
case 2: output_fputc(fp); break;
case 3: output_fwrite(fp, buffer, BUFFER1); break;
case 4: output_fwrite(fp, buffer, BUFFER2); break;
case 5: output_fwrite(fp, buffer, BUFFER3); break;
case 6: output_fwrite_nocheck(fp, buffer); break;
}
#ifdef __WIN32__
time2 = GetTickCount();
#else
time2 = clock();
#endif
fclose(fp);
unlink(OUTPUT);
free(buffer);
#ifdef __WIN32__
return (time2-time1)*(1.0/1000.0);
#else
return (time2-time1)*(1.0/CLOCKS_PER_SEC);
#endif
}

int main()
{
int i, j;
double total[7];
double lap;
for (j = 0; j < sizeof(total)/sizeof(total[0]); j++) total[j] = 0;

for (i = 0; i < COUNT+1; i++) {
for (j = 0; j < sizeof(total)/sizeof(total[0]); j++) {
lap = output(j);
printf("%d: %5.3fs, ", j, lap);
if (i != 0) total[j] += lap;/* 一回目の結果は捨てる */
}
fputc('\n', stdout);
}
for (j = 0; j < sizeof(total)/sizeof(total[0]); j++) {
printf("%d: %5.3fs\n", j, total[j]);
}
return 0;
}
---ここまで


PROJECT TEAM DoGA 高津正道 ta...@doga.co.jp
TBD0...@nifty.ne.jp
PROJECT TEAM DoGAのホームページ → http://www.doga.co.jp/ptdoga/
12月4日(月) 今日のマーフィーの法則 [企業における企画の法則]
変更可能なものは、変更する時間がなくなるまで、変更される。

Junn Ohta

unread,
Dec 4, 2000, 3:00:00 AM12/4/00
to
fj.comp.lang.c,fj.comp.lang.c++の記事<90fr4p$flr$1...@news.tut.ac.jp>で
sa...@smlab.tutkie.tut.ac.jpさんは書きました。

> ──────────────────────────
> 出力先 最適化 fputc putc fwrite
> ──────────────────────────
> ファイル なし 22.40 21.35 1.530
> ファイル -O3 20.31 20.66 1.500
> /dev/null なし 20.75 19.71 0.000e+00(*)
> /dev/null -O3 18.65 19.00 0.000e+00(*)
> ──────────────────────────

うーん、ずいぶん違うものですね...。

ひょっとすると最近のfwrite()は大きなデータが渡され
たときはBUFSIZより格段に大きなバッファーをいちどに
write()しているのでしょうか? 4.3BSDやSVR4のころの
fwrite()は関数fputc()やマクロputc()と同じ下請け関
数を使っていたので、これほど大きな差が出ることはな
かったはずです。

Shiroh Sado

unread,
Dec 4, 2000, 3:00:00 AM12/4/00
to

佐渡です。

スレッド「Re: fputc と fwrite の速度」での

ta...@doga.co.jp氏の発言 <90g65j$m19$1...@maha2.doga.higashiyodogawa.osaka.jp> より引用
>>  このベンチマークでは、出力すべきデータ列がすでにchar型の配列に格納さ
>> れているのが前提になっていますが、データ列が逐次算出されるような処理で

ええと、

「ある長いバイト列があるときに、それをファイルに書き込むとします。」
<3A2AFF2B...@geocities.co.jp>

とあったため、そのような状況(のみ)を想定していたのです。
説明が舌足らずで申し訳ありません。

>> putc が健闘しています。私はもっと(fwriteより数倍くらいは)遅いと思って
>> いたのでちょっと意外でした。

>> メモリを全量取るよりもマメにfwriteした方が速いのはデータキャッシュの影響
>> でしょうか。

ところで、自分のところの環境だと、fputc/putcの間に差があまり
見られず、fwriteが速いということになっています。

出力先をファイルにした場合

最適化なし -O -O3
計算のみ 2.400s 1.120s 0.570s
putc 17.090s 16.660s 16.300s
fputc 17.080s 16.170s 16.010s
fwrite 256 5.480s 3.060s 3.050s
fwrite 1K 5.340s 2.790s 2.770s
fwrite 32K 5.000s 2.720s 2.700s
fwrite 8M 5.290s 2.720s 2.710s

出力先を/dev/nullにした場合

最適化なし -O -O3
計算のみ 2.390s 1.120s 0.590s
putc 15.840s 15.490s 15.220s
fputc 15.860s 14.920s 14.960s
fwrite 256 4.300s 1.880s 1.870s
fwrite 1K 4.140s 1.630s 1.600s
fwrite 32K 3.800s 1.500s 1.540s
fwrite 8M 3.720s 1.120s 1.130s

fwriteとの速度の差(何倍という数値)は、CPUのメモリに
対する処理の速度とHDDの性能の比に依存する部分が大き
そうです。

>> ソースコードの可読性の点から考えると、putc で出力、というのも検討の余地
>> はまだ残ってそうな結果だと思います。

「もともとバッファがある場合は、どちらで」という話題に沿っ
ていたので、高津さんの言われたような状況でputcでの使用とい
うのは、考えていませんでした。

ただ、高津さんのFreeBSDの環境(Pentium133MHz)や自分のところ
の環境(PentiumII300MHz)で、(なぜかどちらも同じぐらいなので
すが)putcが一秒間に500万回ぐらい実行できているわけですから、
可読性の上でputcを使った方が自然な場面では、putcを使う方が
良いだろうと自分も思っております。
# 理屈の上では、このような環境の場合、putcを1個「節約」した
# として、よくても1個あたり500万分の1秒。

ところで、
・数バイトまとめて書き出す場合は、どちらが良いのか
・第2引数を増やすより第3引数を増やした方が速度が有利と言う
のは本当か?
とで調べてみたのですが次の表です。

2**20個の要素を持つcharの配列について10回繰り返しで、数字は
秒が単位、括弧内は第3引数のみを変化させた場合のものです。

プログラムは記事の末尾につけます。
(前回の記事につけたものとほとんど同じです)

最適化 -O3 で/dev/nullに出力の場合
────────────────────
fputc ... 18.65
putc ... 19.02
fwrite ... 0.00
fwrite (@ 1byte) ... 37.26 ( 37.62 )
fwrite (@ 2byte) ... 19.44 ( 19.20 )
fwrite (@ 3byte) ... 13.76 ( 13.85 )
fwrite (@ 4byte) ... 10.90 ( 10.87 )
fwrite (@ 5byte) ... 10.06 ( 9.34 )
fwrite (@ 6byte) ... 8.70 ( 8.72 )
fwrite (@ 8byte) ... 7.15 ( 7.09 )
fwrite (@ 16byte) ... 4.74 ( 4.70 )
fwrite (@ 100byte) ... 0.96 ( 1.00 )
fwrite (@ 128byte) ... 0.80 ( 0.80 )
fwrite (@1000byte) ... 0.25 ( 0.25 )
fwrite (@1024byte) ... 0.25 ( 0.25 )
fwrite (@1100byte) ... 0.28 ( 0.28 )
fwrite (@8000byte) ... 0.12 ( 0.12 )
fwrite (@8192byte) ... 0.07 ( 0.09 )
fwrite (@8300byte) ... 0.14 ( 0.10 )
────────────────────

最適化 -O3 でファイルに出力の場合
────────────────────
fputc ... 20.24
putc ... 20.55
fwrite ... 1.53
fwrite (@ 1byte) ... 38.74 ( 39.10 )
fwrite (@ 2byte) ... 21.03 ( 20.80 )
fwrite (@ 3byte) ... 15.37 ( 15.42 )
fwrite (@ 4byte) ... 12.52 ( 12.46 )
fwrite (@ 5byte) ... 11.61 ( 10.93 )
fwrite (@ 6byte) ... 10.30 ( 10.34 )
fwrite (@ 8byte) ... 8.74 ( 8.68 )
fwrite (@ 16byte) ... 6.35 ( 6.32 )
fwrite (@ 100byte) ... 2.62 ( 2.69 )
fwrite (@ 128byte) ... 2.42 ( 2.49 )
fwrite (@1000byte) ... 1.91 ( 1.94 )
fwrite (@1024byte) ... 1.94 ( 1.91 )
fwrite (@1100byte) ... 1.97 ( 1.94 )
fwrite (@8000byte) ... 1.80 ( 1.79 )
fwrite (@8192byte) ... 1.79 ( 1.79 )
fwrite (@8300byte) ... 1.80 ( 1.80 )
────────────────────

libc6の実装では、1byte毎にfwriteするのはputcに比べて明らかに
遅い、おそらく意味のないコードになってしまうようなのですが、
2byte毎、3byte毎にならそんなに変ではないように見えます。

第2引数、第3引数という事に関しては、確認できませんでした。

---
佐渡詩郎 (さど しろう) / e-mail : sa...@smlab.tutkie.tut.ac.jp

/*
fputfwrite2.c

数バイト単位でfwrite()を使用した場合の効率を相互に比較
*/

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

#define USE_CLOCK

/*
#define OUTPUT "/dev/null"
*/

#define OUTPUT "remove.me"


#define N (10*1024*1024)
#define M (10)

char buf[N];

/* 出力用ファイルを開く */
FILE *fo() {
FILE *fp;

if ( ! ( fp = fopen( OUTPUT, "wb" ) ) ) {
fprintf( stderr, "can't open %s for write\n", OUTPUT );
}
return fp;
}

/*
書き出し

flag==0 ... fputcを使用
flag==1 ... putcを使用
flag==2 ... fwriteを使用

flag==3 ... fwriteを使用(バイト単位を指定)
*/
double test_output( int flag, int fwrite_bytes ) {


clock_t t0, t1;
FILE *fp;
int i;
struct tms timebuf; /* 値は使用しない */

#ifndef USE_CLOCK
t0 = times(&timebuf);
#else
t0 = clock();
#endif


fp = fo();
switch( flag ) {
case 0:
for( i=0 ; i < N ; i++ ) {
fputc( buf[i], fp );
}
break;
case 1:
for( i=0 ; i < N ; i++ ) {
putc( buf[i], fp );
}
break;
case 2:
fwrite( buf, 1, N, fp );
break;

case 3:
for( i=0 ; i+fwrite_bytes <= N ; i+=fwrite_bytes ) {
fwrite( buf+i, 1, fwrite_bytes, fp );
}
fwrite( buf+i, 1, N-i, fp );
break;
case 4:
for( i=0 ; i+fwrite_bytes <= N ; i+=fwrite_bytes ) {
fwrite( buf+i, fwrite_bytes, 1, fp );
}
fwrite( buf+i, N-i, 1, fp );
break;
}
fclose(fp);
#ifndef USE_CLOCK
t1 = times(&timebuf);

return (double) (t1-t0);
#else
t1 = clock();

return (double) (t1-t0) / CLOCKS_PER_SEC;

#endif
}

int main() {
int i, j;

double time_fputc=0, time_putc=0, time_fwrite=0;
double time_fwrite_bytes_a[20]={0};
double time_fwrite_bytes_b[20]={0};
int fwrite_bytes[]={1,2,3,4,5,6,8,16, 100, 128, 1000, 1024, 1100,
8000, BUFSIZ, 8300, 0};

test_output(2, 0);


for( i=0 ; i<M ; i++ ) {

time_fputc += test_output( 0, 0 );
time_putc += test_output( 1, 0 );
time_fwrite += test_output( 2, 0 );


}
printf( "output : %s\n", OUTPUT );
printf( "size : %d (%d times)\n", N, M );

printf( "fputc ... %.2f, putc ... %.2f, fwrite ... %.2f\n",
time_fputc, time_putc, time_fwrite );

for( i=0 ; i<M ; i++ ) {
for( j=0 ; fwrite_bytes[j]!=0 ; j++ ) {
time_fwrite_bytes_a[j] += test_output( 3, fwrite_bytes[j] );
time_fwrite_bytes_b[j] += test_output( 4, fwrite_bytes[j] );
}
}

for( j=0 ; fwrite_bytes[j]!=0 ; j++ ) {
printf( "fwrite (@%4dbyte) ... %.2f ( %.2f )\n",
fwrite_bytes[j],
time_fwrite_bytes_a[j],
time_fwrite_bytes_b[j] );
}

return 0;
}

Shiroh Sado

unread,
Dec 4, 2000, 3:00:00 AM12/4/00
to

佐渡です。

スレッド「Re: fputc と fwrite の速度」での

oh...@src.ricoh.co.jp氏の発言 <90g755$2b9$1...@ns.src.ricoh.co.jp> より引用


>> ひょっとすると最近のfwrite()は大きなデータが渡され
>> たときはBUFSIZより格段に大きなバッファーをいちどに
>> write()しているのでしょうか? 4.3BSDやSVR4のころの
>> fwrite()は関数fputc()やマクロputc()と同じ下請け関
>> 数を使っていたので、これほど大きな差が出ることはな
>> かったはずです。

こちらの方ですが、BUFSIZ以上の出力を行う場合fwriteがほぼ
writeに丸投げをしているのではないかと、素人考えで想像して
いるのですが、手元に gnu libc がバイナリしかなかったため、
確認できておりません。
# どなたか記事を投げてくれないか期待しているのですが……。

また、はずしているかもしれませんが、差の方は、近年のHDDの
性能向上も理由として考えられないでしょうか。
(性能向上が比較的頭打ちになっているメモリーと比べて)

Masao Seki

unread,
Dec 4, 2000, 10:42:16 AM12/4/00
to
関@神奈川と申します。

zea wrote: in <3a2b8cc8$0$23170$44c9...@news2.asahi-net.or.jp>
  ・
> 本題からちょっとそれますが

さらに、それます。
半分、茶々です。


> 最近のPCは早いんで、あまり差がないと思いますが
> fwriteのパラメータ2と3は入れ替えて指定しても
> 動作的には同じなんですが、速度は違っていたと思います。

返り値は、パラメータ2を単位とした、成功した書込み数だった
と思いますので、動作が同じとは言えないのでは?

# 良く見たら、「動作的には」と云う表現になっていますね。
# この辺を意識されての表現だったでしょうか。


> どっちがどっちか分かりませんがFDDあたりに書き出してみ
> れば、わかるかなぁ?

上記仕様が正しいとしての憶測ですが、パラメータ2を大きく
した方が、速い方に一票。

# 確認する手間をさぼっておいて、賭けの話題にして誤魔化す
# んじゃないって?
# えらい、すんまへん。

VAX/VMS4.2のCでは、差が無かったような記憶が、かすかに有り
ます。
SunOSの1.Xや2.Xでの評価記憶なしです。基本がおろそかに
なっている証拠ですね。

--
関@神奈川
Masao Seki <ma-...@gb3.so-net.ne.jp>

Masamichi Takatsu

unread,
Dec 4, 2000, 9:22:43 PM12/4/00
to
高津@ドーガです。

記事 <90gu13$mii$1...@news.tut.ac.jp> で
Shiroh Sadoさんは書きました

> 「ある長いバイト列があるときに、それをファイルに書き込むとします。」
> <3A2AFF2B...@geocities.co.jp>
> とあったため、そのような状況(のみ)を想定していたのです。
> 説明が舌足らずで申し訳ありません。

 すみません、私は上記前振りの「バイト列」を読んで、「出力すべきバイト
列があるからといって、それがchar配列に格納されているとは限らない」と
考えていました。char配列に入っているなら、fwrite一発で終わる話であり、
fputcとどちらにするか悩むことは無いと思いましたもので…

 たしかに、元記事を読み直してみると、バイト列はchar配列に格納されて
いると取ってもよさそうな気もしてきました。


> ところで、自分のところの環境だと、fputc/putcの間に差があまり
> 見られず、fwriteが速いということになっています。

 佐渡さんのOS環境が書かれていないので推測しますが Linux でしょうか?
手元に Linux 環境が無いので、知り合いのマシン(LASER5 Linux 6.0、libcの
バージョンは不明)をちょっと覗いて見たのですが、putc はマクロではなく
関数呼び出しになっていました。(cc -O -S で確認)

 とすると、putc の遅さは納得できます。このあたりはライブラリの実装
による個体差ということで、個別に判断するしか無いでしょう。

 ついでに、FreeBSD 4.0 で fwrite の実装を追ってみました。

fwrite -> __sfvwrite -> fflush -> __sflush -> __swrite -> _write
-> __swrite -> _write

といった流れになっており、__sfvwrite で、サイズが十分に大きい場合
バッファリングせずに出力するようになっていました。

fputc の方は、

fputc -> putcマクロ -> __swbuf -> fflush -> __sflush -> __swrite -> write

という流れで、最終的な出力は fflush に任せており、fwrite には迂回路が
あるのを抜きにすれば、fwrite とfputc は同じ下請けを使っている、と言え
そうです


PROJECT TEAM DoGA 高津正道 ta...@doga.co.jp
TBD0...@nifty.ne.jp
PROJECT TEAM DoGAのホームページ → http://www.doga.co.jp/ptdoga/

12月5日(火) 今日のマーフィーの法則 [パンツウソの法則]
今日、10ドル95セントで購入した本は、明日、ペーパーバックで発売される。

hana...@abyss.rim.or.jp

unread,
Dec 5, 2000, 12:59:34 AM12/5/00
to
花高です。

sa...@smlab.tutkie.tut.ac.jp (Shiroh Sado) writes:
> oh...@src.ricoh.co.jp氏の発言 <90g755$2b9$1...@ns.src.ricoh.co.jp> より引用
> >> ひょっとすると最近のfwrite()は大きなデータが渡され
> >> たときはBUFSIZより格段に大きなバッファーをいちどに
> >> write()しているのでしょうか? 4.3BSDやSVR4のころの
> >> fwrite()は関数fputc()やマクロputc()と同じ下請け関
> >> 数を使っていたので、これほど大きな差が出ることはな
> >> かったはずです。
>
> こちらの方ですが、BUFSIZ以上の出力を行う場合fwriteがほぼ
> writeに丸投げをしているのではないかと、素人考えで想像して
> いるのですが、手元に gnu libc がバイナリしかなかったため、
> 確認できておりません。

そうですね。 GNU libc version 2 では
buffered な stream に buffer size 以上のデータを fwrite した
場合や line buffered な stream に複数行を fwrite した場合は、
(現在の buffer を flush した後、)可能ならば write 一回で全て
を出力しようとします。
--
HANATAKA, Shinya

MIYASAKA Masaru

unread,
Dec 5, 2000, 2:02:00 AM12/5/00
to
"Junn Ohta" <oh...@src.ricoh.co.jp> wrote in message
news:90f9lg$nb0$1...@ns.src.ricoh.co.jp...

> fj.comp.lang.c,fj.comp.lang.c++の記事<90f77v$3i2$1...@nn-tk106.ocn.ad.jp>で
> alk...@coral.ocn.ne.jpさんは書きました。
> > ループの中で、1バイトごとに関数呼び出しが起こることを考えると、
> > for + fputc() よりも fwrite の方が若干速いと思います。
>
> 元記事ではfputc()との比較になっていますが、べつに
> fputc()である必要はなくて、putc()だってよいわけで
> す。だとすると関数呼び出しのオーバーヘッドは本質的
> ではないでしょう。

質問では具体的な関数名が出されていたので、それをそのまま実装した場合を
想定して答えただけです。

でも、その putc() の方も、VC++ とかのマルチスレッド対応版Cライブラリの
場合は fputc() と同じく関数になっていて、さらにスレッドセーフにするための
ストリームのロック/アンロックなども行われるため、呼び出しのオーバーヘッドが
ないとは言いきれないと思います。

Junn Ohta

unread,
Dec 5, 2000, 2:17:03 AM12/5/00
to
fj.comp.lang.c,fj.comp.lang.c++の記事<90i3uk$gp4$1...@nn-tk106.ocn.ad.jp>で
alk...@coral.ocn.ne.jpさんは書きました。

> でも、その putc() の方も、VC++ とかのマルチスレッド対応版Cライブラリの
> 場合は fputc() と同じく関数になっていて、さらにスレッドセーフにするための
> ストリームのロック/アンロックなども行われるため、呼び出しのオーバーヘッドが
> ないとは言いきれないと思います。

なるほど、そうですね。

# つうことはputchar()も関数!?

結局、オリジナルの実装では[f]putc()とfwrite()の内
部で行われていることはほとんど同じだったけれど、最
近はもっと入出力システムコールの回数を減らす工夫が
なされていて処理コストはそれなりに違う、マルチスレ
ッド対応のためにputc()も関数になっていて、その部分
でもオーバーヘッドが増える可能性がある、ということ
ですね。

Shiroh Sado

unread,
Dec 5, 2000, 2:50:11 AM12/5/00
to

佐渡です。

スレッド「Re: fputc と fwrite の速度」での

ta...@doga.co.jp氏の発言 <90hjhj$aq8$1...@maha2.doga.higashiyodogawa.osaka.jp> より引用
>>  佐渡さんのOS環境が書かれていないので推測しますが Linux でしょうか?

あ、はい。OSのほうもLinuxです。

環境はvine Linux 2.1パッケージそのままと考えて下さって
結構だと思います。

>> 手元に Linux 環境が無いので、知り合いのマシン(LASER5 Linux 6.0、libcの
>> バージョンは不明)をちょっと覗いて見たのですが、putc はマクロではなく
>> 関数呼び出しになっていました。(cc -O -S で確認)
>>  とすると、putc の遅さは納得できます。このあたりはライブラリの実装
>> による個体差ということで、個別に判断するしか無いでしょう。

あ、なるほど。
御指摘ありがとうございます。

また、実装の話の方もありがとうございました。

MIYASAKA Masaru

unread,
Dec 6, 2000, 3:00:00 AM12/6/00
to
"Junn Ohta" <oh...@src.ricoh.co.jp> wrote in message
news:90i4pf$k8n$1...@ns.src.ricoh.co.jp...
> # つうことはputchar()も関数!?

これもいろいろのようです。VC++ の場合、シングルスレッドの場合はマクロ、
マルチスレッドの場合は関数になってます。規格では putchar(c) は
putc((c),stdout) と同等、ということしか書かれていないようですし、
putc() の方も「fputc() と同じだけれど、もしマクロとして実装されている
場合は(いわゆる)マクロの"副作用"が問題になるような実装になっている
可能性がある」というぐらいしか無いようですね。

#このへんは、規格に詳しい方に任せます(^^;

> 結局、オリジナルの実装では[f]putc()とfwrite()の内
> 部で行われていることはほとんど同じだったけれど、最
> 近はもっと入出力システムコールの回数を減らす工夫が
> なされていて処理コストはそれなりに違う、マルチスレ
> ッド対応のためにputc()も関数になっていて、その部分
> でもオーバーヘッドが増える可能性がある、ということ
> ですね。

私もそう思います。

---------------------------

以下、私が経験したことを書きます。

JPEG ファイルのエンコード/デコードを行うソフトで、Independent JPEG Group
の cjpeg/djpeg というソフトがありますが、この djpeg の中の bmp ファイルを
出力する部分は、以下のようになっています。

/* -- wrbmp.c -- */
/* Write the file body from our virtual array */
for (row = cinfo->output_height; row > 0; row--) {
/*(中略)*/
image_ptr = (*cinfo->mem->access_virt_sarray)
((j_common_ptr) cinfo, dest->whole_image, row-1, (JDIMENSION) 1,
FALSE);
data_ptr = image_ptr[0];
for (col = dest->row_width; col > 0; col--) {
putc(GETJSAMPLE(*data_ptr), outfile);
data_ptr++;
}
}
/* ------------- */

要するに、メモリ中のピクセルデータをすべて putc で出力しています。
data_ptr は unsigned char * 型で、GETJSAMPLE() は int 型にキャスト
しているだけです。

この最後の所を、以下のように改変してみます。

/* --fputc 版--- */
for (col = dest->row_width; col > 0; col--) {
fputc(GETJSAMPLE(*data_ptr), outfile);
data_ptr++;
}
/* --fwrite 版-- */
fwrite( data_ptr, sizeof(char), dest->row_width, outfile );
/* ------------- */

そして、オリジナルの putc 版、改変版の fputc 版 / fwrite 版 をそれぞれ
使って djpeg をビルドし、適当に選んだ 49 枚の JPEG ファイルを
BMP ファイルに変換させて、その経過時間を計ってみたのが以下の表です。
VC++ 6.0 pro 使用, Pentium-133, 32MB RAM という環境です。

シングルスレッド マルチスレッド
ライブラリ使用 ライブラリ使用
putc 版 1:16.37 2:32.08
fputc 版 1:21.69 2:27.80
fwrite 版 1:08.61 1:07.95

この時間は JPEG のデコードを行っている時間も含んでいます。違いは歴然
ですね(^^;。最初、私はこの djpeg を DLL 版マルチスレッドライブラリ
(MSVCRT.DLL) とリンクして使っていたのですが、16bit MS-DOS 版の djpeg と
比べてもあまりにも遅かったので、原因を調べてみたのです。それで、
こういう事実がわかりました。

---------------------------

元記事の質問についてですけど、普通は、バイトの配列を出力するときは
fwrite にしておく方が無難だと思います。それで、ある特定の環境で
速さを最適化したい場合は、いろいろ実験してみると良いと思います。

Arita

unread,
Dec 7, 2000, 3:00:00 AM12/7/00
to
皆様 いろいろありがとうございました

まとめると、以前は

Junn Ohta wrote:
> どちらも渡されたデータを内部バッファーにためて、バ
> ッファーがいっぱいになったらwrite()で書き出してい
> るだけですから、ファイル入出力のほうがメモリー操作
> より格段に遅いことを考えると、本質的には同じです。

であったが、最近では

hana...@abyss.rim.or.jp wrote:
> GNU libc version 2 では
> buffered な stream に buffer size 以上のデータを fwrite した
> 場合や line buffered な stream に複数行を fwrite した場合は、
> (現在の buffer を flush した後、)可能ならば write 一回で全て
> を出力しようとします。

なので、顕著にfwriteが速いということですね。

ありがとうございました。

Arita

Shiroh Sado

unread,
Dec 7, 2000, 10:31:33 AM12/7/00
to
佐渡です。

スレッド「Re: fputc と fwrite の速度」での

oh...@src.ricoh.co.jp氏の発言 <90i4pf$k8n$1...@ns.src.ricoh.co.jp> より引用
>> # つうことはputchar()も関数!?

vine Linux2.1パッケージのgcc(egcs-2.91.66)で試してみました。

-------------------------------------------
#include <stdio.h>

int main () {
fputc( '\n', stdout );
putc( '\n', stdout );
putchar('\n');

return 0;
}
-------------------------------------------
というプログラムを gcc -E してみたところ、
-------------------------------------------
(略)
int main () {
fputc( '\n', stdout );
_IO_putc ( '\n' , stdout ) ;
putchar('\n');

return 0;
}
-------------------------------------------
という結果になりました。

また、<stdio.h>をincludeせず、
-------------------------------------------
int main () {
putchar('\n');

return 0;
}
-------------------------------------------
とだけ書いてコンパイルしても、a.outが生成されました。

TANAKA Satoshi

unread,
Dec 7, 2000, 11:36:43 AM12/7/00
to
sa...@smlab.tutkie.tut.ac.jp (Shiroh Sado) writes:

> vine Linux2.1パッケージのgcc(egcs-2.91.66)で試してみました。
>
> -------------------------------------------
> #include <stdio.h>
>
> int main () {
> fputc( '\n', stdout );
> putc( '\n', stdout );
> putchar('\n');
>
> return 0;
> }
> -------------------------------------------
> というプログラムを gcc -E してみたところ、
> -------------------------------------------
> (略)
> int main () {
> fputc( '\n', stdout );
> _IO_putc ( '\n' , stdout ) ;
> putchar('\n');
>
> return 0;
> }
> -------------------------------------------
> という結果になりました。

Solarisちゃんは、#define _REENTRANT 1しとくと、

int main () {
fputc( '\n', (&__iob[1]) );
putc( '\n', (&__iob[1]) );
putchar('\n');

return 0;
}

でないと、

int main () {
fputc( '\n', (&__iob[1]) );
(--( (&__iob[1]) )->_cnt < 0 ? __flsbuf((unsigned char) ( '\n' ), ( (&__iob[1]) )) : (int)(*( (&__iob[1]) )->_ptr++ = ( '\n' ))) ;
(--( (&__iob[1]) )->_cnt < 0 ? __flsbuf((unsigned char) ( ( '\n' ) ), ( (&__iob[1]) )) : (int)(*( (&__iob[1]) )->_ptr++ = ( ( '\n' ) ))) ;

return 0;
}

です。


田中聡
TANAKA Satoshi
tan...@twcu.ac.jp

Kazuo Fox Dohzono

unread,
Dec 7, 2000, 9:54:35 PM12/7/00
to
堂園です.

In article <90oagl$92k$1...@news.tut.ac.jp>
sa...@smlab.tutkie.tut.ac.jp (Shiroh Sado) writes:

> vine Linux2.1パッケージのgcc(egcs-2.91.66)で試してみました。
>
> -------------------------------------------
> #include <stdio.h>
>
> int main () {
> fputc( '\n', stdout );
> putc( '\n', stdout );
> putchar('\n');
>
> return 0;
> }

ひょっとしたら -finline-functions の効果を狙っている可能性もあるかなと
思うんですけど, gcc -O3 -c して nm -p *.o するとどうなりますか?

--
Kazuo Fox Dohzono / doh...@hf.rim.or.jp

[12],(6,9),0,0,2
(4/1449/3742)

Masamichi Takatsu

unread,
Dec 10, 2000, 2:50:12 AM12/10/00
to
高津@ドーガです。

記事 <90uu0h$1a1j$2...@news2.rim.or.jp> で
Kazuo Fox Dohzonoさんは書きました

> > int main () {
> > fputc( '\n', stdout );
> > putc( '\n', stdout );
> > putchar('\n');
> >
> > return 0;
> > }
> ひょっとしたら -finline-functions の効果を狙っている可能性もあるかなと
> 思うんですけど, gcc -O3 -c して nm -p *.o するとどうなりますか?

 <90hjhj$aq8$1...@maha2.doga.higashiyodogawa.osaka.jp> でちょっと書きましたが、
gcc -O -S で確認する限りは putc は関数呼び出しのままでした。

上記ソースを gcc -O3 -S してみましたが、putchar の方は putc に
置き換えられてます。

main:
pushl %ebp
movl %esp,%ebp
pushl stdout
pushl $10
call fputc
pushl stdout
pushl $10
call _IO_putc
addl $16,%esp
pushl stdout
pushl $10
call _IO_putc
xorl %eax,%eax
leave
ret

こんな感じです。
(Laser5 Linux 6.0/Kernel 2.2.5-rh60_L5_2/gcc version egcs-2.91.66 で確認
libcのバージョンは不明)

PROJECT TEAM DoGA 高津正道 ta...@doga.co.jp
TBD0...@nifty.ne.jp
PROJECT TEAM DoGAのホームページ → http://www.doga.co.jp/ptdoga/

12月10日(日) 今日のマーフィーの法則 [ジレットの電話力学]
待ち続けていた電話は、部屋を出た瞬間にかかってくる。

Kazuo Fox Dohzono

unread,
Dec 10, 2000, 3:39:21 AM12/10/00
to
堂園です.

In article <90vcjk$9bt$1...@maha2.doga.higashiyodogawa.osaka.jp>
Masamichi Takatsu <ta...@doga.co.jp> writes:

>  <90hjhj$aq8$1...@maha2.doga.higashiyodogawa.osaka.jp> でちょっと書きましたが、
> gcc -O -S で確認する限りは putc は関数呼び出しのままでした。

すみません, 見落としていたようです…だけではナンですので.

ウチの古い FreeBSD では

putchar -> putc(stdout) への define
putc -> __sputc への define

で, __sputc はコンパイラが gcc なら inline 関数, そうでなければマクロ
で実装されており, マクロのコメントには

/*
* This has been tuned to generate reasonable code on the vax using pcc.
*/

と書かれていて, ちらと眺めた限りではマクロ版の第一引数は一度だけ評価さ
れるように注意深く書かれているようです.

# という話も出てたかな?

0 new messages