(아마도) libparquet/libarrow의 StreamWriter를 사용하여 주기적으로 파일에 쓰는 프로세스가 있습니다(~65MB마다 써야 함). 예를 들어 쉘을 통해 파일을 쿼리할 때 watch du -sh {file_name}
크기가 증가하지 않는 것 같습니다. 그러나 프로세스가 완료되면 파일이 완전히 작성된 것처럼 보입니다.
여기서 무슨 일이 일어나고 있는 걸까요? 프로세스가 파일을 열고 끝날 때까지 계속해서 쓴 것으로 추측하는데, 파일의 실제 크기는 언제 사용자(또는 OS)에 반영됩니까? 파일이 최종적으로 닫힌 이후인가요?
예를 들어 Python에서 파일에 반복적으로 쓸 때 셸 with open('file.txt','w') as fp
을 통해 또는 셸에서 파일을 쿼리하면 파일의 실제 크기가 반영됩니다. 이 Python은 단지 배후에 추가 단계를 추가하는 것입니까?ls
du
답변1
프로그램에서 어떤 방법을 사용하는지 지정하지 않으면 I/O를 수행하는 방법이 많고 그 과정에서 많은 문제가 발생할 수 있으므로 대답은 "매우 빠르거나 전혀 그렇지 않음"입니다. 또한 다양한 운영 체제나 파일 시스템으로 인해 상황이 복잡해질 수 있으므로 여기서는 논의하지 않습니다.
일부 시스템에서는 아래에 표시된 테스트 프로그램을 컴파일하기 위해 임의의 매직 문자열을 정의해야 할 수도 있습니다. 예를 들어
feature_test_macros(7)
일부 Linux 버전을 참조하세요. 현재 많은(모든 사람은 아니지만...) 제공하는 C99 컴파일러가 필요할 수도 있습니다.
쓰다
write(2)
매우 간단합니다. 버퍼링이 부족하고 이 코드에서 다른 일이 발생하지 않기 때문에 이 호출의 출력은 스펙트럼의 더 빠른 끝 부분에 표시되어야 합니다(모든 것이 잘 진행되는 경우). 이러한 스크립트를 ktrace
다른
strace
유사한 시스템 호출 추적 유틸리티 에서 실행하는 것이 교육적일 수 있습니다.
// write.c -- `rm out; make write && ./write > out`
#include <stdio.h>
#include <unistd.h>
int main(void) {
char buf[] = "this is some output\n";
//while(1) {
write(STDOUT_FILENO, buf, sizeof(buf) / sizeof(char));
//}
return 0;
}
더 많은 출력을 위해 무한 루프의 주석 처리를 제거하고 sleep(3)
"파일 시스템이 무엇을 표시합니까?" 테스트에 도움이 되도록 속도를 늦추는 호출을 추가합니다.
버퍼링...버퍼링...버퍼링...
실제 프로그램(TM)은 버퍼링된 I/O를 사용할 수 있으며 다른 일(부풀림)이 발생할 수 있습니다. 여기서는 버퍼링에 대해서만 설명하겠습니다. 첫째, 출력을 전혀 내보내지 않는 프로그램:
// never.c -- `rm out; make never && ./never > out`
#include <stdio.h>
#include <unistd.h>
int main(void) {
setvbuf(stdout, 0, _IOFBF, 0);
fputs("x", stdout);
_exit(0);
}
이는 전체 버퍼링을 선택하지만 아무것도 플러시하지 않고 종료됩니다. 이를 달성하는 데는 다른 많은 방법이 있습니다. 예를 들어 사람들은 kill -9
segfaults 등에 관심이 있습니다. 듀얼 프로세서 Linux와 같이 시스템이 매우, 매우, 매우 바쁜 경우 시스템의 CPU 로드가 약 5,000이면 일반적으로 빠른 출력이 "거의 없음"으로 바뀔 수 있습니다.
여기서 간단한 수정은 파일 시스템에 대한 지연된 출력을 시뮬레이션하기 _exit
전에 호출 exit
을 추가하도록 변경한 다음 버퍼 크기를 채우기 위해 출력을 지연시키기 위해 절전 모드가 포함된 무한 루프를 추가하는 것입니다. sleep
그럼에도 불구하고 버퍼링을 통한 파일 시스템의 출력 크기는 프로그램 쓰기 크기보다 지연될 수 있습니다.
임시 파일 이름 바꾸기
또 다른 방법은 임시 파일에 쓴 다음 I/O가 완료되면 임시 파일의 이름을 실제 파일로 바꾸는 것입니다. 이렇게 하면 판독기가 절반만 작성된 출력 파일을 보는 문제를 피할 수 있습니다. 이 경우 출력 파일은 호출 이후에만 새 크기를 반영합니다 rename(2)
.
// rename.c -- `rm out*; make rename && ./rename & sleep 1; ls -l out*`
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(void) {
char *tmpbuf = strdup("out.XXXXXXXXXX");
char *tmpfile = mktemp(tmpbuf);
if (!tmpfile) err(1, "mktemp failed");
FILE *fh = fopen(tmpfile, "w");
if (!fh) err(1, "fopen failed");
fputs("this is some renamed output\n", fh);
fclose(fh);
sleep(9);
rename(tmpfile, "out");
//free(tmpbuf);
return 0;
}
여기서 out
파일은 약 9초 후에 새 크기를 반영해야 합니다. 또는 시스템이 매우 바쁜 경우에는 아주 많은 초가 남습니다. 또는 이름 바꾸기가 완료되기 전에 어떤 일이 발생하지 않도록 하세요.
메모리 맵
I/O를 수행하는 또 다른 방법은 메모리 매핑을 이용하는 것입니다. 그러나 이 동작을 변경하는 플래그가 있을 수 있지만 버퍼링의 징후는 표시되지 않습니다.
// mmap.c -- `make mmap && ./mmap & sleep 1; od -bc out`
#include <sys/mman.h>
#include <err.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(void) {
char buf[] = "this is some mmap'd output\n";
size_t buflen = sizeof(buf) / sizeof(char);
int fd = open("out", O_RDWR | O_CREAT, 0666);
ftruncate(fd, buflen);
if (fd < 0) err(1, "open failed");
void *ptr = mmap(0, buflen, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (ptr == MAP_FAILED) err(1, "mmap failed");
sleep(9);
memcpy(ptr, buf, buflen);
munmap(ptr, buflen);
close(fd);
return 0;
}
I/O가 완료된 후 메모리 매핑된 파일의 이름을 임시 파일에서 예상 파일로 바꿀 수 있습니다(이전 팁 참조). 여기에는 타당한 이유가 있을 수 있습니다.
다른
위와 다른 시스템 호출을 사용하는 다른 형태의 I/O가 있을 수 있습니다. strace
무엇을 찾아야 할지 알고 있다면 그 내용이 드러날 것입니다. 파일 시스템의 출력 표시를 지연시킬 수 있는 버퍼링이 있을 수 있습니다. 간단한 스크립트를 작성하면 호출이 어떻게 작동하는지 보여주는 데 도움이 되며, 이는 더 큰 프로그램에서 무엇을 찾아야 하는지 보여주는 데 도움이 됩니다 strace
. 문서가 있기를 바랍니다.
더 높이, 더 깊게 쌓아올려
표준 라이브러리 위에 구축된 언어는 운영 체제에서 제공하는 것 외에도 자체 버퍼를 가질 수 있지만 일반적으로 비트를 디스크로 전송하려고 시도하는 호출이 하나 이상 있을 flush
수 있습니다. 사용자가 할 수 있는 것처럼 기본 동작을 조정하는 방법입니다 . 아니면. 일부 언어나 프로그래밍 가이드에서는 운영 체제와 다른 기본값을 설정할 수 있습니다. 버퍼링된 출력은 파일 시스템에 도달하는 데 지연이 표시되거나 이름 바꾸기 트릭이 사용되고 있을 수 있습니다.(finish-output)
sync
setvbuf(3)