你的想法本身并不差。如果我们假设有一个换行符分隔的文件(即:可以在没有斑点的行之间切换),可以找到类似块的块(从另一个程序中删除,因此请先检查)
// just in case
#define _LARGEFILE_SOURCE
#define _BSD_SOURCE
#define _POSIX_C_SOURCE 200112L
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
// TODO: should be calculated
#define FILE_PARTS 100
// TODO: should not be global
off_t positions[FILE_PARTS + 1];
int slice_file(FILE * fp)
{
off_t curr_pos = 0;
off_t filesize = 0;
off_t chunk_size = 0;
int fd;
int i, res;
char c;
struct stat sb;
// get size of file
fd = fileno(fp);
if (fd == -1) {
fprintf(stderr, "EBADF in prepare_and_backup() for data-file pointer\n");
return 0;
}
if (fstat(fd, &sb) == -1) {
fprintf(stderr, "fstat() failed\n");
return 0;
}
// check if it is a regular file
if ((sb.st_mode & S_IFMT) != S_IFREG) {
fprintf(stderr, "Not a regular file\n");
return 0;
}
// TODO: check if filesize and chunksize >> 1
filesize = sb.st_size;
chunk_size = filesize/((off_t) FILE_PARTS);
positions[0] = 0;
curr_pos = 0;
for (i = 1; i < FILE_PARTS; i++) {
res = fseeko(fp, curr_pos, SEEK_SET);
if (res == -1) {
fprintf(stderr, "Error in fseeko(): %s\n",
strerror(errno));
return 0;
}
curr_pos += chunk_size;
// look for the end of the line to cut at useful places
while ((c = fgetc(fp)) != EOF) {
curr_pos++;
// TODO: add code to honor Apple's special needs
if (c == '\n') {
c = fgetc(fp);
if (c == EOF) {
break;
}
curr_pos++;
break;
}
}
positions[i] = curr_pos - 1;
}
// Position of the end of the file
positions[i] = filesize;
// Is that even needed?
rewind(fp);
return 1;
}
现在你可以开始一个线程,给它开始和结束它应该工作的块(你可能或可能没有用上面的函数计算),并且在单个线程内进行(m)映射而不用担心。如果输出与块的大小相同,甚至可以在原地工作。
编辑
的mmap
声明是
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
如果你不关心你将它设置为NULL
一个特定的地址。
length
是您希望映射初始化到的字节数,即在这种情况下:填充文件描述符fd
中的内容。
该填充的开始由offset
设置,带有一个不舒服的警告:它需要是页面大小的倍数(请询问sysconf(_SC_PAGE_SIZE)
确切数字)。没有什么问题,只需在开始之前将其设置到页面并在实际启动时开始工作,就可以获得所有必要的信息。您可以(并且必须!)忽略该页面的其余部分。
或者你把整个文件和映射它,并使用它,因为你会在驱动器上使用一个文件:给每个线程一块该地图(必要的信息在positions
),并从那里工作。
第一个优点:您有几块内存可以被操作系统更容易地推动,并且您可能会或可能不会有多个CPU的缓存未命中。如果你运行一个集群或任何其他架构,每个CPU /一组CPU具有它自己的RAM或至少一个非常大的缓存,这是必须的。
后者的优点:更简单的实现,但你有一大块地图。这可能会或可能不会影响运行时间。
提示:我对现代快速固态硬盘的体验:现在的阅读速度非常高,您可以轻松地从直接文件访问开始,而非映射。即使使用相当慢的“普通”硬盘,也可以获得合理的速度。我从上面的代码片段中找到的程序必须搜索超过120 GB的大型CSV文件,并且没有足够的RAM来加载它,甚至没有足够的空间将其加载到某个数据库中(是的,那是几个多年前)。这是一个关键 - >“很多,不同的值”文件,幸好已经排序。所以我用上面的方法(KEY-> position)为它创建了一个小的(尽可能大的)驱动器索引文件,虽然比我的示例中的100多了更多的块。索引文件中的键也进行了排序,因此如果您搜索的键较大(数据按升序排序)比索引条目找到了正确的块,这意味着该键位于该块之前的块中如果存在的话。这些块足够小,可以将它们中的一部分保存在RAM中以作为缓存使用,但收益不大,传入请求相当均匀。
一个穷人的数据库可以这么说,而且速度足够快,可以在没有用户投诉的情况下完成这项工作。
有趣的一面是:键是字母数字,排序算法将它们排序为“aAbBcC ...”,这意味着您不能直接使用strcmp
。让我挠了一阵头,但解决办法很简单:比较忽略大小写(例如:strcasecmp
如果可用),如果它是不是等于 返回该结果,否则返回正常结果的相反strncmp
(例如只是return -strcmp(a,b);
)。
你对于需要处理的数据的种类非常沉默,所以上面可能没有丝毫兴趣。
你想让不同的线程同时读取文件的不同部分? – yano
你想阅读'\ n'分隔记录?如果这些行跨越页面边界会发生什么? – wildplasser
@yano Yesss如你所说,有没有办法做到这一点? – superrman777