fseek传递负偏移和SEEK_CUR

ATL*_*ATL 5 c posix fseek

fseek(..)在一个非常大的文件中运行性能不佳.每次调用fseek函数时,我都需要向后移动文件指针位置100 bytes:

  fseek(fp, -100, SEEK_CUR);
Run Code Online (Sandbox Code Playgroud)

以前,我这样做:

  fseek(fp, (index)*100, SEEK_SET); // which makes basically the same...
Run Code Online (Sandbox Code Playgroud)

我的问题是fseek如何将指针移动到文件中并将文件指针设置在特定位置.

我认为它需要文件指针并将其向后移动,但现在我认为它真正起作用的是

  • 得到当前位置(cp)

  • 添加否定索引(p = idx + cp)

  • 并将文件指针从文件的开头移动到该位置(fseek(fp, p, SEEK_SET))

And*_*nle 5

首先,您使用什么操作系统?如果是 Linux,请运行您的应用程序strace以查看它实际进行的系统调用。

其次,fopen()/fseek()/fread()这种访问模式的工具是错误的。这些调用通过预读来缓冲文件读取。这对你没有好处。您fseek()要偏移 X,缓冲的任何数据现在都无用了,您fread()100 个字节,并且缓冲的fread()读取更多 - 可能 8 kB。您可能会读取该文件的几乎每个字节超过 80 次。您可以使用 usesetbuf()setvbuf()来禁用缓冲,但随后您将在向后浏览文件时执行 100 字节读取。它应该更快,但不会快到你能跑的那么快。

要尽可能快地完成此操作(无需进入多线程和/或异步 IO):

  1. 使用open()/pread()。您不需要寻找 -pread()直接从任意偏移量读取。

  2. 读取更大的块 - 例如 8192 x 100。甚至更大。像以前一样向后读取,但自己进行缓冲,并从文件中的偏移量开始,该偏移量是您正在读取的大尺寸的倍数 - 第一次读取的数据可能会少于 819,200 字节。首先处理缓冲区中的最后 100 个字节,然后向后处理缓冲区。处理完缓冲区中的前 100 个字节后,使用pread()从文件中读取前 819,200 个字节(甚至更大)。

  3. 如果可用,请使用直接 IO。文件系统优化可能会尝试通过预读并将数据(您已经处理过的数据)放入页面缓存中来“优化”您的访问。因此,如果可能的话,请绕过页面缓存(并非所有操作系统都支持直接 IO,并且并非支持直接 IO 的操作系统上的所有文件系统都实现它。)

像这样的东西:

#define DATA_SIZE 100
#define NUM_CHUNKS (32UL * 1024UL)
#define READ_SIZE ( ( size_t ) DATA_SIZE * NUM_CHUNKS )

void processBuffer( const char *buffer, ssize_t bytes )
{
    if ( bytes <= 0 ) return;
    // process a buffer backwards...
}

void processFile( const char *filename )
{
    struct stat sb;
    // get page-aligned buffer for direct IO
    char *buffer = valloc( READ_SIZE );
    // Linux-style direct IO
    int fd = open( filename, O_RDONLY | O_DIRECT );
    fstat( fd, &sb );    
    // how many read operations?
    // use lldiv() to get quotient and remainder in one op
    lldiv_t numReads = lldiv( sb.st_size, READ_SIZE );
    if ( numReads.rem )
    {
        numReads.quot++;
    }
    while ( numReads.quot > 0 )
    {
        numReads.quot--;
        ssize_t bytesRead = pread( fd, buffer,
            READ_SIZE, numReads.quot * READ_SIZE );
        processBuffer( buffer, bytesRead );
    }
    free( buffer );
    close( fd );
}
Run Code Online (Sandbox Code Playgroud)

您需要为其添加错误处理。


axi*_*iac 3

在用户应用程序级别,您将文件视为一个大内存块,并像简单的内存操作一样移动文件指针(递增或递减指针以达到文件中所需的偏移量)。

但在运行时库和操作系统级别,情况完全不同。处理文件的运行时库代码不会将文件的全部内容加载到内存中。也许文件非常大,也许您只需要从中读取几个字节,原因有很多。

运行时库(以及操作系统管理的文件缓存)仅从内存缓冲区中的文件加载一些数据。您使用该数据(读取、写入),当您想要访问尚未加载到缓冲区中的信息时,文件管理代码会为您加载它;也许它会扩大缓冲区,或者可能会将缓冲区写入文件(如果已修改),或者只是丢弃先前加载的数据(如果未修改)并在缓冲区中加载另一块数据。

当您使用fseek()跳转到文件的不同部分时,通常文件指针到达的区域尚未位于内存中。我想它从文件指针的新位置开始加载数据(在操作系统级别,文件缓存将数据加载到多个磁盘块中)。因为您向后运行文件,所以我猜想文件指针新位置的数据几乎从未加载到内存中。它会触发磁盘访问,这会导致速度变慢。

我认为最好的解决方案是使用操作系统提供的功能将文件映射到内存中。阅读有关mmap()Linux(也许还有 OSX)或Windows 上的文件映射的信息。它可以对您有所帮助,但由于您使用的特定访问模式,改进可能不会很显着。大多数时候,程序从头到尾读取文件,并且处理文件和磁盘访问的代码针对这种模式进行了优化。