提问人:Cardinal 提问时间:6/10/2023 最后编辑:chqrlieCardinal 更新时间:6/10/2023 访问量:66
为什么通过创建新线程读取文件比不使用新线程花费更多时间?
Why does reading a file by creating a new thread take more time than not using a new thread?
问:
所以我正在阅读一段时间,长度为 3.5 GB(实际上这是文件大小的一半。我正在阅读文件的一半)。我最初的想法是将 7GB 分成两半,并在两个单独的线程中读取一半,看看我是否可以在没有任何线程的情况下一次性读取整个文件来提升性能。
但是,仅仅在新创建的线程中读取一半文件比在没有任何线程的情况下读取整个文件花费的时间要多得多。为什么会有这样的差异?
这是没有任何线程的代码:-
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
int main(int argc, char **argv) {
if (argc < 2) {
printf("Usage: %s <filepath>\n", argv[0]);
exit(1);
}
struct stat info;
if (stat(argv[1], &info) < 0) {
perror("stat()");
exit(1);
}
long size = info.st_size;
long count = 0;
FILE *fptr = fopen(argv[1], "r");
if (fptr == NULL) {
perror("fopen()");
exit(1);
}
int ch = 0;
while (count != size / 2) {
ch = fgetc(fptr);
count++;
}
printf("read bytes: %ld\n", count);
}
以上代码平均需要 8-10 毫秒才能完成。
现在,使用 pthreads 正在做同样的事情,
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
FILE *fptr1;
long size = 0;
long by = 0;
void *read_file(void *param) {
long count = 0;
int ch = 0;
while (count != size / 2) {
ch = fgetc(fptr1);
count++;
}
by = count;
return NULL;
}
int main(int argc, char **argv) {
if (argc < 2) {
exit(1);
}
struct stat info;
if (stat(argv[1], &info) < 0) {
perror("stat()");
exit(1);
}
size = info.st_size;
pthread_t thread1;
fptr1 = fopen(argv[1], "r");
if (pthread_create(&thread1, NULL, &read_file, NULL) < 0) {
perror("thread()");
exit(1);
}
pthread_join(thread1, NULL);
printf("bytes: %ld\n", by);
}
此代码执行完全相同的操作,平均需要 65-70 秒。
为什么与非螺纹版本相比,螺纹外壳需要这么多时间?用两个线程将文件分成两半有什么意义吗?
另外,我知道会是一个更好的选择,完全同意。我故意使用,因为我不想设置缓冲区等等。由于在两个版本中都使用了,我想答案在于线程和 .fread()
fgetc
fgetc()
fgetc()
谢谢你的帮助。
答:
以下是线程代码速度较慢的多种原因:
不建议在多线程应用程序中使用标准流,并且有明显的缺点:针对不需要锁定结构的非线程程序进行了优化。在多线程应用程序中,为了允许对结构进行一致的并发访问,所有标准流函数都必须使用锁来序列化对流结构的访问,即使它在单个线程中使用也是如此,因为库无法断言这一点。与从缓冲区读取单个字节所涉及的最小任务相比,这非常慢。
fgetc()
FILE
FILE
如果流在单个线程中使用,则可以使用 来绕过此开销,但对于更复杂的流函数(如 )没有等效项。 如果一次读取大块,应该不是问题。
fgetc_unlocked()
fgetc()
fgets
fread
此外,线程版本似乎实现了完全相同的任务,但它对 和 使用了全局变量,这对生成的代码有影响。此外,该函数需要在每次迭代时从内存中重新加载值,因为编译器不能假定这不会更改它们。这可能会阻止此版本中的进一步优化。
size
fptr1
fgetc()
请注意,通过全局变量将值传递给线程是糟糕的设计,您应该使用分配结构并传递指针作为参数。
param
请注意,如果为每个线程创建多个线程来处理文件的单独部分,则会遇到另一个问题:线程将竞争从存储设备上不同位置的文件中读取块,这在某些设备(如硬盘)上可能效率非常低,因为这些访问将导致磁头移动,每次访问的延迟通常为 10 毫秒。按顺序读取文件可能会效率提高一个数量级。文件系统布局和缓存、特定设备特征和其他因素可能会影响性能并使其不可重现。
为了进行快速测试,请尝试将函数更改为:read_file
void *read_file(void *param) {
long local_size = size;
FILE *local_fptr = fptr1;
long count = 0;
int ch;
while (count != local_size / 2) {
ch = fgetc_unlocked(local_fptr);
count++;
}
by = count;
return NULL;
}
评论