为何Python统计大文件行数比C(mmap实现)更快?
嘿,别担心,你的对比思路没问题,新手遇到这种“C居然比Python慢”的反直觉情况太正常了!我来帮你拆解一下背后的原因,以及怎么优化你的C代码~
核心原因:Python的文件迭代器藏了很多优化
你以为Python的for line in file是逐字符找换行符?其实不是!Python的文件对象迭代器底层用了缓冲IO:它会一次性从磁盘读取一大块数据(比如几KB甚至几十KB)到内存缓冲区,然后在缓冲区里批量查找换行符,而不是每次只读一个字节。这种批量处理的方式能大幅减少磁盘IO次数,同时利用CPU的缓存优势,效率自然比逐字节遍历高很多。
而你的C代码是最朴素的逐字节检查:*(addr+i) == '\n',这种方式完全没有利用现代CPU的批量处理能力,也没做任何缓存友好的优化,所以速度就慢下来了。
你的C代码还有这些可以优化的点
1. 用标准库的高效字符串查找函数代替逐字节遍历
标准库的strchr函数是经过高度优化的,很多实现会用SIMD指令(比如SSE、AVX)一次检查多个字节里的换行符,或者用更高效的块查找算法。试试把你的遍历逻辑改成用strchr循环查找:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <fcntl.h> #include <sys/mman.h> #include <sys/stat.h> #include <unistd.h> void handle_error(const char *msg) { perror(msg); exit(EXIT_FAILURE); } int main() { int fd; struct stat sb; char *addr; int count = 0; const char *ptr; fd = open("processed_data/crawl-300d-2M.vec", O_RDONLY); if (fd == -1) handle_error("open"); if (fstat(fd, &sb) < 0) { handle_error("fstat"); close(fd); } addr = mmap(NULL, sb.st_size, PROT_READ, MAP_SHARED, fd, 0); if (addr == MAP_FAILED) { handle_error("mmap"); close(fd); } clock_t begin = clock(); ptr = addr; while ((ptr = strchr(ptr, '\n')) != NULL) { count++; ptr++; // 跳过找到的换行符,继续找下一个 } clock_t end = clock(); printf("%d\n", count); double time_spent = (double)(end - begin) / CLOCKS_PER_SEC; printf("%f\n", time_spent); munmap(addr, sb.st_size); close(fd); return 0; }
这个版本的速度应该会比你原来的代码快很多,甚至能超过Python的实现。
2. 编译时开启优化选项
别忘了给C代码加上编译优化,比如gcc -O3 your_code.c -o count_lines。-O3会让编译器做很多优化,比如循环展开、向量化,进一步提升效率。
3. 也可以试试缓冲IO的方式
如果不用mmap,用fopen配合fread批量读入缓冲区处理,效率也会不错。比如:
#include <stdio.h> #include <time.h> #include <stdlib.h> #define BUFFER_SIZE 1024*1024 // 1MB缓冲区 int main() { FILE *fp = fopen("processed_data/crawl-300d-2M.vec", "r"); if (!fp) { perror("fopen"); exit(EXIT_FAILURE); } char buffer[BUFFER_SIZE]; int count = 0; size_t bytes_read; clock_t begin = clock(); while ((bytes_read = fread(buffer, 1, BUFFER_SIZE, fp)) > 0) { for (size_t i = 0; i < bytes_read; i++) { if (buffer[i] == '\n') count++; } } clock_t end = clock(); printf("%d\n", count); double time_spent = (double)(end - begin) / CLOCKS_PER_SEC; printf("%f\n", time_spent); fclose(fp); return 0; }
这种方式和Python的缓冲IO思路类似,批量读入后再处理,效率也会比逐字节遍历高。
总结
Python的标准库在文件操作上已经做了很多底层优化,而你的C代码是最基础的逐字节实现,没有利用这些优化点。只要调整C代码的处理逻辑,配合编译器优化,C的性能优势就能体现出来啦~
内容的提问来源于stack exchange,提问作者Ujan

