2

Macでのfseek()/の書き込みパフォーマンスに問題があります。fwrite()私は最大4GBのサイズの大きなファイルを操作しています。以下のテストは、わずか120MBのかなり小さなファイルで行われました。私の戦略は次のとおりです。

  • fopen()ディスク上の新しいファイル
  • ファイルをゼロで埋めます(約3秒かかります)
  • データの小さなブロックをランダムな位置に書き込みます(30.000ブロック、各4k)

全体の手順は約120秒かかります。

書き込み戦略は画像回転アルゴリズムにバインドされており(ここで私の質問を参照)、誰かが回転問題のより高速な解決策を考え出さない限り、fseek()4k以下を使用してファイルに書き込む戦略を変更することはできません。

私が観察しているのは、これです。最初の数千fseek()/fwrite()は非常に良好に機能しますが、パフォーマンスは非常に速く低下し、システムキャッシュがいっぱいになると予想されるよりも速くなります。下のグラフはfwrite()、1秒あたりの秒数と秒単位の時間の関係を示しています。ご覧のとおり、7秒後にfseek()/fwrite()レートは約に達します。1秒あたり200、プロセスの最後で1秒あたり100に達するまで下降します。

1秒あたりのfwrite()と時間

プロセスの途中(2〜3回)に、OSはファイルの内容をディスクにフラッシュすることを決定します。これは、コンソールの出力から数秒ハングしていることがわかります。ディスクへの書き込みは5MB/秒です(それほど多くはありません)。システムがファイル全体を書き込んだように見えた後fclose()、20MB/秒のディスクアクティビティが長期間表示されます。

fflush()5.000秒ごとに使用してfwrite()も、動作はまったく変わりません。どういうわけかフラッシングを強制するためにfclose()/fopen()を入れると、全体が約2倍スピードアップします。10%。

プロセスのプロファイルを作成しました(下のスクリーンショット)。事実上すべての時間が内部で費やされfwrite()ており、両方にfseek()ドリルダウンできます。__write_nocancel()

書き込み機能のプロファイリング

完全にばかげた要約

入力データがバッファに完全に収まり、書き込みプロセスをフラグメントに分割することなく、回転した出力データを線形に書き込むことができる場合を想像してみてください。書き込み関数のロジックがそのように動作するという理由だけで、私はまだfseek()ファイルポインターを配置するために使用しますが、この場合のファイルポインターは、以前と同じ位置に設定されます。パフォーマンスへの影響はないと予想されます。間違っています。

fseek()ばかげているのは、その特殊なケースの呼び出しを削除すると、関数が120秒ではなく2.7秒以内に終了することです。

さて、長い前置きの後で、質問は次のとおりです。同じ位置を探しても、なぜ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 に答える