【发布时间】:2012-11-19 17:34:58
【问题描述】:
在我的 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 次。
在过程的中间(2 或 3 次),操作系统决定将文件内容刷新到磁盘,我可以从控制台输出中看到它挂了几秒钟,在此期间我大约有 3 次。在我的磁盘上写入 5 MB/s(这不算多)。在fclose() 系统似乎写入了整个文件后,我看到 20 MB/s 的磁盘活动持续了更长的时间。
如果我每 5.000 个fwrite()s 使用一次fflush(),则行为根本不会改变。放入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;
}
我有点迷茫,因为我不明白系统如何“优化”我的通话。任何意见表示赞赏。
【问题讨论】:
-
一旦数据不再适合文件系统缓存,您就开始测量磁盘写入性能。然后,是的,fseek() 告诉您磁盘写入头可以移动多快。这是非常慢。对 OSX 了解不够,但一般来说,64 位操作系统和大量 RAM 可以为您购买大缓存。
-
请再读一遍我的“完全荒谬的总结”:当我在没有 fseek 的情况下线性写出所有 30.000 个块时,整个过程需要不到 3 秒。如果我 fseek 到与文件指针相同的位置,性能下降近 40 倍。不要告诉我系统试图在后一种变体中移动磁盘头,因为这种行为在第一个 几兆字节并且没有缓存可能那么小(考虑到我在那台机器上仍然有大约 2 GB 的空闲、未使用的 RAM)。是的,我的 OSX 是 64 位,有 8 GB 的 RAM,足以满足上述要求。
标签: performance macos fwrite performance-testing fseek