【问题标题】:fseek passing negative offset and SEEK_CURfseek 传递负偏移量和 SEEK_CUR
【发布时间】:2015-08-14 17:48:12
【问题描述】:

我在一个非常大的文件中运行 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))

【问题讨论】:

  • 什么是开放的?是“a+b”还是“r+b”……我猜不是“a+b”也不是“追加”。
  • 可能与底层文件系统的性能有关。
  • 由于其他原因我无法使用附加打开文件,所以我需要打开它“r+b”
  • fseek 是否在这种模式下工作?
  • 您描述的位置计算是标准指定的,但标准没有指定实现如何将流设置到该位置。我会惊讶地发现一个实现按照你的建议做了,但这是允许的。

标签: c posix fseek


【解决方案1】:

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

其次,fopen()/fseek()/fread() 是这种访问模式的错误工具。这些调用缓冲文件读取 - 通过读取ahead。那对你没有好处。你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 );
}

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

【讨论】:

    【解决方案2】:

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

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

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-03-16
      • 1970-01-01
      • 2018-04-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多