这是我在 stackoverflow 上的第一篇文章,我的母语不是英语。这篇文章给您带来的不便请见谅。可能有点长,期待您的耐心等待。提前致谢!
我有一个 C 语言代码片段。这项工作是计算两个文件中的单词数。我使用 pthreads 来解决这个问题。但我发现这两个语句的顺序
count_words(argv[1]);
pthread_create(&t1, NULL, count_words, (void *)argv[2]);
影响程序性能,这与我的预期相反。这是代码:
#include <stdio.h>
#include <pthread.h>
#include <ctype.h>
#include <stdlib.h>
int total_words;
int main(int argc, char *argv[]) {
pthread_t t1;
void *count_words(void *);
if (argc != 3) {
printf("usage: %s file1 file2\n", argv[0]);
exit(1);
}
total_words = 0;
count_words(argv[1]); // program runs faster when executing this first
pthread_create(&t1, NULL, count_words, (void *)argv[2]);
pthread_join(t1, NULL);
printf("%5d: total words\n", total_words);
return 0;
}
void *count_words(void *f) {
char *filename = (char *)f;
FILE *fp;
int c, prevc = '\0';
if ((fp = fopen(filename, "r")) == NULL) {
perror(filename);
exit(1);
}
while ((c = getc(fp)) != EOF) {
if (!isalnum(c) && isalnum(prevc))
total_words++;
prevc = c;
}
fclose(fp);
return NULL;
}
表现:
我在命令行上使用“test program_name”运行程序来测试运行速度。输出是:
如果是这样的顺序:
count_words(argv[1]);
pthread_create(&t1, NULL, count_words, (void *)argv[2]);
程序运行速度快:真实0.014s
如果是这样:
pthread_create(&t1, NULL, count_words, (void *)argv[2]);
count_words(argv[1]);
程序运行缓慢:真实0.026s
我所期望的:
在案例 1 中,程序首先运行 count_word()。完成计数作业后,它会继续运行 pthread_create()。届时,新线程将帮助完成计数工作。因此,新线程在原始线程完成作业后执行作业,这是顺序运行而不是并行运行。在情况 2 中,程序在任何计数之前首先运行 pthread_create(),因此之后有两个并行线程进行计数。所以我希望案例 2 比案例 1 快。但我错了。情况 2 较慢。有人可以给我一些有用的信息吗?
笔记
请忽略我没有在全局变量 total_words 上设置互斥锁。这不是我关心的部分。该程序仅用于测试。请原谅它的不完美之处。
编辑 1
以下是我阅读一些建议后的补充和改进。
a) 补充:处理器是 Intel® Celeron(R) CPU 420 @ 1.60GHz。一芯。
b)改进:我改进了我的例子,有两个变化:
1)我放大了文件。file1 是 2080651 字节(约 2M),file2 是 file1 的副本。
2) 我修改了 count_words()。到达文件末尾时,使用 fseek() 将 fp 设置为开头并再次计数。重复计数 COUNT 次。定义 COUNT 20。下面是更改后的代码:
#define COUNT 20
// other unchanged codes ...
void *count_words(void *f) {
// other unchanged codes ...
int i;
for (i = 0; i < COUNT; i++) {
while ((c = getc(fp)) != EOF) {
if (!isalnum(c) && isalnum(prevc))
total_words++;
prevc = c;
}
fseek(fp, 0, SEEK_SET);
}
fclose(fp);
return NULL;
}
fast_version(count_word() first)和slow_version(pthread_create() first)的输出:
管理员@ubuntu:~$ time ./fast_version file1 file2
12241560:总字数
真正的 0m5.057s
用户 0m4.960s
系统 0m0.048s
管理员@ubuntu:~$ time ./slow_version file1 file2
12241560:总字数
真正的 0m7.636s
用户 0m7.280s
系统 0m0.048s
我尝试了几次“time progname file1 file2”命令。每次运行可能会有十分之一或百分之一秒的差异。但差异并不大。
编辑 2
这部分是我根据一些提示做了一些实验后添加的——
当您在第一个线程完成执行后启动第二个线程时,没有上下文切换开销。
——用户315052。
实验是我改进了count_word():
void *count_word(void *f) {
// unchanged codes
// ...
for (i = 0; i < COUNT; i++) {
while ((c = getc(fp)) != EOF) {
if (!isalnum(c) && isalnum(prevc))
total_words++;
prevc = c;
}
fseek(fp, 0, SEEK_SET);
printf("from %s\n", filename); // This statement is newly added.
}
// unchanged codes
// ...
}
添加语句 " printf("from %s\n", filename); " ,这样我就可以知道当时哪个文件(或线程)正在运行。快速版的输出是20次“from file1”,然后是20次“from file2”,慢速版是“from file1”和“from file2”混合打印。
看起来快速版本更快,因为没有上下文切换。但事实是,count_word() 完成后,原来的线程并没有死掉,而是创建了一个新线程,等待它终止。新线程运行时是否没有上下文切换?我仔细观察了屏幕,发现“from file2”的打印速度明显比“from file1”慢。为什么?是因为从file2计数时发生了上下文切换吗?
对于慢速版本,我们可以从输出中看到“from file1”和“from file2”的打印速度甚至比“from file2”在快速版本中的打印速度还要慢,因为它的上下文切换在并行计数上花费了更多时间,而在快速版本中,上下文切换并没有那么繁重,因为其中一个线程已经完成了它的工作并且只是在等待。
所以我认为主要原因是快速版本相对于慢速版本具有轻松的上下文切换。但是“打印速度”是我观察到的,可能没有那么严格。所以我不确定。