Performance MacOS上奇怪的fseek()/fwrite()性能
我在Mac上的Performance MacOS上奇怪的fseek()/fwrite()性能,performance,macos,fwrite,performance-testing,fseek,Performance,Macos,Fwrite,Performance Testing,Fseek,我在Mac上的fseek()/fwrite()写入性能有问题。我操作的是高达4GB的大文件,下面的测试是用一个只有120MB的小文件进行的。我的策略如下: fopen()磁盘上的新文件 用零填充文件(大约需要3秒钟) 将小块数据写入随机位置(30000块,每个4k) 整个过程大约需要120秒 写入策略绑定到图像旋转算法(请参见我的问题),除非有人为旋转问题提出更快的解决方案,否则我无法更改使用fseek()然后向文件写入4k或更少的策略 我观察到的是:前几千个fseek()/fwrite()
fseek()
/fwrite()
写入性能有问题。我操作的是高达4GB的大文件,下面的测试是用一个只有120MB的小文件进行的。我的策略如下:
磁盘上的新文件fopen()
- 用零填充文件(大约需要3秒钟)
- 将小块数据写入随机位置(30000块,每个4k)
fseek()
然后向文件写入4k或更少的策略
我观察到的是:前几千个fseek()
/fwrite()
执行得相当好,但性能下降得非常快,比任何系统缓存被填满时的预期速度都要快。下表显示了每秒的fwrite()
s与以秒为单位的时间。如您所见,7秒钟后,fseek()
/fwrite()
速率达到约每秒200次,但仍在下降,直到过程结束时达到每秒100次
在这个过程中(2到3次),操作系统决定将文件内容刷新到磁盘上,我可以从控制台输出中看到这些内容挂起几秒钟,在此期间,我的磁盘上有大约5 MB/s的写操作(这并不是很多)。在fclose()
系统似乎写入了整个文件之后,我看到20 MB/s的磁盘活动持续了更长的时间
如果每5.000次使用fflush()
fwrite()s,则行为根本不会改变。放入fclose()
/fopen()
强制刷新会以某种方式将整个过程加快约10%
我确实描述了这个过程(下面的屏幕截图),您可以看到,几乎所有的时间都花在fwrite()
和fseek()
中,这两个过程都可以深入到\u write\u nocancel()
完全荒谬的总结
想象一下这样的情况,我的输入数据完全适合我的缓冲区,因此我能够线性地写入旋转的输出数据,而不需要将写入过程分割成片段。我仍然使用fseek()。人们预计不会对性能产生影响错误
荒谬的是,如果我删除对该特殊情况下的fseek()
的调用,我的函数将在2.7秒内完成,而不是在120秒内完成
现在,在冗长的前言之后,问题是:为什么fseek()
对性能有如此大的影响,即使我寻求相同的职位?我如何加速它(通过另一种策略或其他函数调用、禁用缓存(如果可能)、内存映射访问等等)
作为参考,以下是我的代码(未整理、未优化,包含大量调试输出):
-(bool)writeRotatedRaw:(TIFF*)TIFF-toFile:(NSString*)strFile
{
如果(!tiff)返回否;
如果(!strFile)返回否;
NSLog(@“开始旋转“@”…”,strFile);
文件*f=fopen([strFile UTF8String],“w”);
如果(!f)
{
NSString*msg=[NSString stringWithFormat:@“无法打开“@”进行写入。”,strFile];
NSRunAlertPanel(@“Error”,msg,@“OK”,nil,nil);
返回否;
}
#定义行缓存大小(1024*1024*256)
int h=[tiff iImageHeight];
int w=[tiff iImageWidth];
int iWordSize=[tiff iBitsPerSample]/8;
int-ibitsperpoixel=[tiff-ibitsperpsample];
int-iLineSize=w*iWordSize;
int-iLinesInCache=行缓存大小/iLineSize;
int-iLinesToGo=h,iLinesToRead;
NSLog(@“创建临时文件”);
double time=CACurrentMediaTime();
双倍lastTime=时间;
unsigned char*dummy=calloc(iLineSize,1);
对于(int i=0;i0)
{
iLinestored=iLinesToGo;
如果(iLinesToRead>iLinesInCache)iLinesToRead=iLinesInCache;
对于(int i=0;i,为了以某种方式结束这个问题,我将自己回答并分享我的解决方案
虽然我无法提高fseek()调用的性能,但我确实实现了一个性能良好的变通方法。目的是避免fseek()
不惜任何代价。因为我需要将数据片段写入目标文件的不同位置,但这些片段之间的距离相等,并且这些片段之间的间隙将由稍后在该过程中写入的其他片段填充,所以我将写入内容拆分为多个文件。我写入的文件数量与片段流的数量相同生成,然后在最后一步中,重新打开所有这些临时文件,读取它们并将数据块线性写入目标文件。此操作的性能良好,在上面给出的示例中达到约4秒。一旦数据不再适合文件系统缓存,您就开始测量磁盘写入性能,并且恩,是的,fseek()告诉你磁盘写入头的移动速度有多快。这是非常慢的。我对OSX知之甚少,但一般来说,64位操作系统和大量RAM可以为你购买一个大缓存。请再次阅读我的“完全荒谬的摘要”:当我在没有fseek的情况下线性写出所有30000个块时,整个过程只需3秒钟。如果我fseek到与文件指针相同的位置,则性能会下降近40倍。不要告诉我系统会尝试在后一种变体中移动磁头,因为这种行为是在前几个变量之后开始的兆字节和无缓存可能会这么小(考虑到我在那台机器上还有~2GB的空闲、未使用的RAM),是的,我的OSX是64位的,有8GB的RAM,这足以满足我的需要
-(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;
}