fgets()와 read()의 성능 차이가 많이 생기나요?

superkkt의 이미지

FTP 서버에서 ascii mode transfer 부분을 만들고 있습니다. 처음에는 단순하게 줄단위 연산이니까 fgets()를 사용하자 생각을하고 그대로 구현을 했습니다. 내용이 길어서 pseudo code로 적겠습니다.

s_offset = ftell(fp);
fgets(buf, 8192, fp);
f_offset = ftell(fp);
read_cnt = f_offset - s_offset;

if(buf[read_cnt - 1] == '\n' && buf[read_cnt - 2] != '\r') {
      buf[read_cnt - 1] = '\r';
      buf[read_cnt] = '\n';
}

write();

작동은 잘 되는데 속도가 좀 느리게 나오더군요. 그래서 proftpd 소스에서 ascii mode를 다루는대로 바꿔봤습니다.

#define INTERNAL_BUF 1024
cnt = read(fd, buf, 8192);
while(cnt) {
   if(cnt > INTERNAL_BUF) 
        in_buf_len = INTERNAL_BUF;
   else
        in_buf_len = cnt;
   memcpy(in_buf, buf, in_buf_len);
   for(i = 0; i < in_buf_len; i++) {
        if(in_buf[i] == '\n' && in_buf[i - 1] != '\r')
             memmove(&(in_buf[i + 1]), &(in_buf[i]), in_buf_len);
    }

     cnt -= in_buf_len;
     buf += in_buf_len;
    
     write()
}

이렇게 바꾸니 속도가 잘 나옵니다. LAN 상에서 테스트 했을때 전자는 속도가 200KB ~ 3MB/s 사이로 들쑥날쑥하는 반면에 후자는 4~5MB/s를 꾸준히 유지합니다.

이 성능 차이가 fgets와 read에서 나오는건가요?

pseudo code라서 그런데 실제론 후자가 전자보다 연산은 훨씬 많이 합니다. \n이 나올때마다 memmove도 하구요.. 처음엔 이게 더 느리지않을까 생각했는데 결과는 완전 정반대군요.

fox9의 이미지

그냥 예상으로는 fgets는 읽은 내용중에서 \n의 위치를 찾아서 그 부분을 리턴하기 때문에 그냥 데이터를 읽어오는 read 보다는 상당히 느릴듯 합니다만 fgets와 read의 직접 구현된 소스는 보지 않아서 장담은 못하겠습니다. :shock:

정태영의 이미지

fread 를 써보면 어떨까요?

오랫동안 꿈을 그리는 사람은 그 꿈을 닮아간다...

http://mytears.org ~(~_~)~
나 한줄기 바람처럼..

vacancy의 이미지

저도 fread에 한표. :roll:

superkkt의 이미지

fread를 사용해봤는데 read와 비슷한 성능이 나옵니다. 일단은 fgets가 제일 느리고 fread와 read는 비슷하게 나오네요. 그런데 fread를 쓰면 어차피 \n을 찾아서 \r\n으로 확장해주는 과정을 해줘야하고 또 fread에서 몇 바이트를 읽었는지 확인하는 부분이 추가되므로 그냥 read를 쓰기로 했습니다.

fgets로 한줄만 딱 읽어서 맨 마지막 글자가 \n이면 \r\n으로 확장하고 아니면 그대로 write하는 방식과..

일정 바이트를 read하고 읽은 데이터에서 \n이 나올때마다 확장을 해서 write 하는 방식..

전자가 연산이 더 적은것 같아서 빠를거라고 생각했는데.. fgets에서 속도가 많이 떨어지네요. 똑같이 파일스트림을 이용하는데 왜 fgets와 fread도 속도차이가 많이 발생하나요? fgets는 fgetc처럼 한글자씩 읽어서 메모리로 복사하다가 \n을 만나면 중단하도록 구현되어 있나요? 그렇다면 많이 느릴것 같긴한데..

======================
BLOG : http://superkkt.com

vacancy의 이미지

fgets가 동작할 때 최소한 디스크에서 한 sector는 읽겠죠.
근데 한 줄 읽고 다시 file pointer는 한 줄 읽은 다음으로 옮겨놔야하고,
다음에 읽을 때 또 아까 읽었던 sector를 또 읽겠죠.
물론 disk cache가 있으니까 매번 읽지야 않겠지만 :roll:
한 sector를 여러 차례 읽으려고 하는 것 자체가 큰 부하죠.

그리고 여러 다양한 시스템에서 돌리실 거라면,
read 보다는 fread를 권해드리고 싶습니다.
read 같은 system call은 OS에 따라서는 버퍼링 같은 걸
libc에 있는 fread만큼 못(or 안)해주는 경우가 있어서요. :?

superkkt의 이미지

vacancy wrote:
그리고 여러 다양한 시스템에서 돌리실 거라면,
read 보다는 fread를 권해드리고 싶습니다.
read 같은 system call은 OS에 따라서는 버퍼링 같은 걸
libc에 있는 fread만큼 못(or 안)해주는 경우가 있어서요. :?

이 경우에는 read()를 할때 파일에서 일정 바이트를 쭉 읽어들이기만 하는데 버퍼링을 해서 얻는 이득이 있나요?

======================
BLOG : http://superkkt.com

vacancy의 이미지

이 경우에는 버퍼링에서 얻어지는 이익은 없겠지만,
잘 구현된 libc의 경우에는 read ahead라고 해서,
다음에 읽을 곳을 미리 예측해서 읽어주기도 하죠.
뭐 다음 block을 미리 읽어놔 주는 정도는,
read system call에도 구현되어 있는 경우가 많긴 합니다만.

익명 사용자의 이미지

ftell(3)이 문제인 것으로 보입니다.
ftell(3)이 tell(2)이나 lseek(2) 등의 system call을 호출할 것으로 보이는데 system call을 호출하면서 kernel mode로 들어가는 데에 드는 overhead가 상당합니다.

코드를 보면 ftell(3)을 호출하는 이유가 단지 몇 바이트 읽었는지 정도 뿐인데 굳이 그 방법을 고집하실 필요가 있으신가요? 아니라면 strlen(3)을 쓰시는게 나을 것 같습니다.

그리고 여기에서 성능을 떨어뜨리는 원인은 아니지만, fgets(3)가 최적의 효율이 아닌 이유는 read(2) system call을 호출할 때 쓰이는 버퍼는 libc가 관리하는 버퍼(FILE structure내에 어딘가에서 가리키고 있을 겁니다.) fgets(3)을 호출할 때 쓰이는 버퍼(이건 사용자가 넘겨주는 버퍼겠죠.)가 서로 다르기 때문에 이들 간에 추가적인 memory copy가 일어나기 때문입니다.

하지만 이 경우 proftpd에서도 이 추가적인 copy를 하고 있으므로 이것이 성능 저하의 원인은 아닙니다. 또한 이것을 개선하려면 코드가 커지고 복잡해지는데 성능 향상은 거의 없습니다. (그만큼 user mode에서 copy 한번 하는 것 정도는 가벼운 작업이라는 얘기겠지요.)

그리고 순차 읽기의 경우 read(2)나 fread(3)의 성능 차가 없을 것으로 보입니다. fread(3)가 내부 버퍼를 건드리지 않은 채로 caller가 넘겨준 버퍼에만 쓴다면 다행이지만, 내부 버퍼를 건드린다면 다시는 읽지 않을 것을 괜히 버퍼에 copy는 작업을 하게 됩니다. (물론, 가벼운 작업이긴 합니다.)

작은 부하이지만 user mode 내의 memory copy 마저도 없애고 싶으시다면 writev(2)를 쓰시는게 어떨까 싶습니다. 과연 성능 차이가 나기는 할지 의심스럽기는 합니다만...

superkkt의 이미지

객 wrote:
코드를 보면 ftell(3)을 호출하는 이유가 단지 몇 바이트 읽었는지 정도 뿐인데 굳이 그 방법을 고집하실 필요가 있으신가요? 아니라면 strlen(3)을 쓰시는게 나을 것 같습니다.

처음엔 strlen를 사용해서 구현했었습니다. 그런데 바이너리 파일을 아스키 모드로 전송을 해보니 10메가 파일이 1메가로 줄어들더군요. 디버깅해보니 바이너리 파일에 있는 널문자 때문에 strlen가 실제로 읽어들인 문자수보다 훨씬 적은 값을 리턴하면서 생긴 문제였습니다.

뭐.. 바이너리 파일을 아스키모드로 전송하는게 정상적인 FTP 사용은 아니지만 그래도 바이너리 파일을 그대로 전송하면서 \n -> \r\n만 되도록, 즉 파일 사이즈가 \n갯수만큼 늘어나도록, 만들려고 하다보니 ftell을 쓰게되었네요.

지금은 proftpd 코드에서 쓰는 방법을 사용중입니다. 또 신기한건 internal buf 크기가 1024일때 최적의 성능이 나옵니다. 조금만 크게 만들면 바로 속도가 떨어지더군요. 아마 memmove를 할때 버퍼 크기가 클수록 부하가 많이 걸려서 그런듯한데..

성능향상이란게 참 신기하면서도 재밌네요..

======================
BLOG : http://superkkt.com

ㅡ,.ㅡ;;의 이미지


read_cnt = fgets(buf, 8192, fp); 

if( read_cnt < 0 ) 어쩌고...저쩌고..ㅡ,.ㅡ;;

if(buf[read_cnt - 1] == '\n' && buf[read_cnt - 2] != '\r') { 
      buf[read_cnt - 1] = '\r'; 
      buf[read_cnt] = '\n'; 
} 

write(); 

로 해보세요..


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

superkkt의 이미지

ㅡ,.ㅡ;; wrote:

read_cnt = fgets(buf, 8192, fp); 

if( read_cnt < 0 ) 어쩌고...저쩌고..ㅡ,.ㅡ;;

if(buf[read_cnt - 1] == '\n' && buf[read_cnt - 2] != '\r') { 
      buf[read_cnt - 1] = '\r'; 
      buf[read_cnt] = '\n'; 
} 

write(); 

로 해보세요..

char *fgets(char *restrict s, int n, FILE *restrict stream);

======================
BLOG : http://superkkt.com

ㅡ,.ㅡ;;의 이미지

superkkt wrote:
ㅡ,.ㅡ;; wrote:

read_cnt = fgets(buf, 8192, fp); 

if( read_cnt < 0 ) 어쩌고...저쩌고..ㅡ,.ㅡ;;

if(buf[read_cnt - 1] == '\n' && buf[read_cnt - 2] != '\r') { 
      buf[read_cnt - 1] = '\r'; 
      buf[read_cnt] = '\n'; 
} 

write(); 

로 해보세요..

char *fgets(char *restrict s, int n, FILE *restrict stream);


아.. 제가 착각했군요..ㅎㅎ
그런데 바이너리파일을 적용하실거면 애초부터 fgets 는 적합한함수가 아닙니다.
애초 만들어진목적부터 파일로부터 스트링 을가져온다는 뜻이죠..
바이너리는 fread 혹은 read를....


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

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.