2

我的Mac上fseek()/的写入性能有问题。fwrite()我正在处理最大 4 GB 的大文件,下面的测试是用一个只有 120 MB 的相当小的文件进行的。我的策略如下:

  • fopen()磁盘上的新文件
  • 用零填充文件(大约需要 3 秒)
  • 将小块数据写入随机位置(30.000 个块,每个 4k)

整个过程大约需要 120 秒。

写入策略绑定到图像旋转算法(请参阅我的问题here),除非有人为旋转问题提出更快的解决方案,否则我无法更改使用fseek()然后将 4k 或更少写入文件的策略.

我观察到的是:前几千个fseek()/fwrite()性能相当不错,但性能下降得非常快,比任何系统缓存被填满时所期望的要快。下图显示了fwrite()每秒 s 与以秒为单位的时间。如您所见,7 秒后fseek()/fwrite()速率达到约。每秒 200 次,仍在下降,直到在过程结束时达到每秒 100 次。

fwrite() 每秒 vs 时间

在该过程的中间(2 或 3 次),操作系统决定将文件内容刷新到磁盘,我可以从控制台输出中看到它挂了几秒钟,在此期间我大约有。在我的磁盘上写入 5 MB/s(这不算多)。在fclose()系统似乎写入整个文件后,我看到 20 MB/s 的磁盘活动持续了更长的时间。

如果我fflush()每 5.000fwrite()秒使用一次,则行为根本不会改变。放入fclose()/fopen()强制冲洗以某种方式加速了整个事情。10%。

我确实描述了这个过程(下面的截图),你看,几乎所有的时间都花在了里面fwrite()fseek()而且可以深入到__write_nocancel()他们两个人身上。

分析写入函数

完全荒谬的总结

想象一下我的输入数据完全适合我的缓冲区的情况,因此我能够线性写入我的旋转输出数据,而无需将写入过程分成片段。我仍然使用fseek()定位文件指针,只是因为写入函数的逻辑是这样的,但在这种情况下,文件指针被设置到它已经存在的相同位置。人们预计不会对性能产生影响。错了

荒谬的是,如果我删除fseek()对该特殊情况的调用,我的函数会在2.7 秒内完成,而不是 120 秒。

fseek()现在,经过很长的前言,问题是:即使我寻求相同的位置,为什么会对性能产生如此大的影响?我怎样才能加快速度(通过其他策略或其他函数调用,如果可能,禁用缓存,内存映射访问,......)?

作为参考,这是我的代码(未整理,未优化,包含大量调试输出):

-(bool)writeRotatedRaw:(TIFF*)tiff toFile:(NSString*)strFile
{
    if(!tiff) return NO;
    if(!strFile) return NO;

    NSLog(@"Starting to rotate '%@'...", strFile);

    FILE *f = fopen([strFile UTF8String], "w");
    if(!f)
    {
        NSString *msg = [NSString stringWithFormat:@"Could not open '%@' for writing.", strFile];
        NSRunAlertPanel(@"Error", msg, @"OK", nil, nil);
        return NO;
    }

#define LINE_CACHE_SIZE (1024*1024*256)

    int h = [tiff iImageHeight];
    int w = [tiff iImageWidth];
    int iWordSize = [tiff iBitsPerSample]/8;
    int iBitsPerPixel = [tiff iBitsPerSample];
    int iLineSize = w*iWordSize;
    int iLinesInCache = LINE_CACHE_SIZE / iLineSize;
    int iLinesToGo = h, iLinesToRead;

    NSLog(@"Creating temporary file");
    double time = CACurrentMediaTime();
    double lastTime = time;
    unsigned char *dummy = calloc(iLineSize, 1);
    for(int i=0; i<h; i++) fwrite(dummy, 1, iLineSize, f);
    free(dummy);
    fclose(f);
    f = fopen([strFile UTF8String], "w");
    NSLog(@"Created temporary file (%.1f MB) in %.1f seconds", (float)iLineSize*(float)h/1024.0f/1024.0f, CACurrentMediaTime()-time);
    fseek(f, 0, SEEK_SET);

    lastTime = CACurrentMediaTime();
    time = CACurrentMediaTime();
    int y=0;
    unsigned char *ucRotatedPixels = malloc(iLinesInCache*iWordSize);
    unsigned short int *uRotatedPixels = (unsigned short int*)ucRotatedPixels;
    unsigned char *ucLineCache = malloc(w*iWordSize*iLinesInCache);
    unsigned short int *uLineCache = (unsigned short int*)ucLineCache;
    unsigned char *uc;
    unsigned int uSizeCounter=0, uMaxSize = iLineSize*h, numfwrites=0, lastwrites=0;
    while(iLinesToGo>0)
    {
        iLinesToRead = iLinesToGo;
        if(iLinesToRead>iLinesInCache) iLinesToRead = iLinesInCache;

        for(int i=0; i<iLinesToRead; i++)
        {
            // read as much lines as fit into buffer
            uc = [tiff getRawLine:y+i withBitsPerPixel:iBitsPerPixel];
            memcpy(ucLineCache+i*iLineSize, uc, iLineSize);
        }

        for(int x=0; x<w; x++)
        {
            if(iBitsPerPixel==8)
            {
                for(int i=0; i<iLinesToRead; i++)
                {
                    ucRotatedPixels[iLinesToRead-i-1] = ucLineCache[i*w+x];
                }
                fseek(f, w*x+(h-y-1), SEEK_SET);
                fwrite(ucRotatedPixels, 1, iLinesToRead, f);
                numfwrites++;
                uSizeCounter += iLinesToRead;
                if(CACurrentMediaTime()-lastTime>1.0)
                {
                    lastTime = CACurrentMediaTime();
                    NSLog(@"Progress: %.1f %%, x=%d, y=%d, iLinesToRead=%d\t%d", (float)uSizeCounter * 100.0f / (float)uMaxSize, x, y, iLinesToRead, numfwrites);
                }
            }
            else
            {
                for(int i=0; i<iLinesToRead; i++)
                {
                    uRotatedPixels[iLinesToRead-i-1] = uLineCache[i*w+x];
                }
                fseek(f, (w*x+(h-y-1))*2, SEEK_SET);
                fwrite(uRotatedPixels, 2, iLinesToRead, f);
                uSizeCounter += iLinesToRead*2;
                if(CACurrentMediaTime()-lastTime>1.0)
                {
                    lastTime = CACurrentMediaTime();
                    NSLog(@"Progress: %.1f %%, x=%d, y=%d, iLinesToRead=%d\t%d", (float)uSizeCounter * 100.0f / (float)uMaxSize, x, y, iLinesToRead, numfwrites);
                }
            }
        }
        y += iLinesInCache;
        iLinesToGo -= iLinesToRead;
    }

    free(ucLineCache);
    free(ucRotatedPixels);
    fclose(f);

    NSLog(@"Finished, %.1f s", (CACurrentMediaTime()-time));

    return YES;
}

我有点迷茫,因为我不明白系统如何“优化”我的通话。任何输入表示赞赏。

4

1 回答 1

1

只是为了以某种方式结束这个问题,我会自己回答并分享我的解决方案。

尽管我无法提高fseek()调用的性能,但我确实实现了一个性能良好的解决方法。fseek()目的是不惜一切代价避免。因为我需要将数据片段写入目标文件的不同位置,但这些片段出现的距离相等,并且这些片段之间的间隙将被稍后写入的其他片段填充,我将写入拆分为多个文件。我写入与生成片段流一样多的文件,然后在最后一步中,重新打开所有这些临时文件,旋转读取它们并将数据块线性写入目标文件。这个性能很好,达到大约。上面给出的示例需要 4 秒。

于 2012-11-19T09:34:58.237 回答