繁体   English   中英

fseek传递负偏移和SEEK_CUR

[英]fseek passing negative offset and SEEK_CUR

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

  fseek(fp, -100, SEEK_CUR);

以前,我这样做:

  fseek(fp, (index)*100, SEEK_SET); // which makes basically the same...

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

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

  • 得到当前位置( cp

  • 添加否定指数( p = idx + cp

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

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

其次, fopen()/fseek()/fread()是此访问模式的错误工具。 这些调用缓冲文件读取 - 通过提前读取。 那对你没有好处。 fseek()偏移X,无论缓冲的数据现在是无用的,你fread() 100字节,缓冲的fread()读取更多 - 可能是8 kB。 您可能会阅读文件的几乎每个字节超过80次。 您可以使用setbuf()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 );
}

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

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

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

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

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

我认为最适合您的解决方案是使用操作系统提供的功能将文件映射到内存中。 在Linux上(可能在OSX上)或Windows上的文件映射中了解mmap() 它可以帮助您,但由于您使用的特定访问模式,改进可能不会很明显。 大多数情况下,程序从头到尾读取文件,处理文件和磁盘访问的代码针对此模式进行了优化。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM