7
votes

Performances Windows Fsync (FlushfileBuffers) avec de grands fichiers

des informations sur la garantie des données est sur le disque ( http://winntfs.com/2012/11/29/windows-write-caching-par-2-an-overview-for-Application-Developers/ ), même dans le cas d'ex. Une panne de courant, il semble que sur les plates-formes Windows, vous devez compter sur sa version flushfilebuffers pour avoir la meilleure garantie que les tampons sont effectivement rincé des caches de périphérique de disque sur le support de stockage lui-même. La combinaison de file_flag_no_buffering avec file_flag_write_through ne garantit pas la rinçage du cache de périphérique, mais simplement avoir un effet sur le cache du système de fichiers, si cette information est correcte.

étant donné le Le fait que je travaillerai avec des fichiers assez volumineux, il faut mettre à jour "transactionnellement", cela signifie effectuer une "FSYNC" à la fin d'une transaction commit. J'ai donc créé une infime application pour tester la performance en le faisant. Il effectue essentiellement des écrivies séquentielles d'un lot de 8 octets aléatoires de la taille de la page de mémoire utilisant 8 écrit puis des flushes. Le lot est répété dans une boucle et après toutes nombreuses pages écrites, elle enregistre les performances. En outre, il dispose de deux options configurables: à FSYNC sur une affleurance et d'écrire un octet à la dernière position du fichier, avant de commencer la page écrit. xxx

Les résultats de la performance J'obtenue (64 bits Win 7, disque à broche lente) ne sont pas très encourageants. Il semble que "FSYNC" performance dépend beaucoup de la taille du fichier rinçage, de sorte que cela domine le temps, et non la quantité de données "sales" à rougir. Le graphique ci-dessous montre les résultats des 4 options de réglages différents de la petite application de référence.

référence Timing pour 4 Les scénarios

Comme vous pouvez le constater, les performances de" FSYNC "diminuent de manière exponentielle, car le fichier se développe (jusqu'à quelques Go, il est vraiment à une halte). En outre, le disque lui-même ne semble pas faire un lot entier (c'est-à-dire le moniteur de ressources indique son temps actif, à seulement quelques cent et sa file d'attente de disque vidamment vide pendant la plupart du temps).

i Évidemment attendait que la performance "FSYNC" soit très pire que de faire des flushes tamponnées normales, mais je m'attendais à ce qu'il soit plus ou moins constant et indépendant de la taille du fichier. Comme ceci, il semblerait suggérer qu'il n'est pas utilisable en combinaison avec un seul gros fichier.

Est-ce que quelqu'un a une explication, des expériences différentes ou une solution différente permettant de garantir que des données sont sur le disque et qui a un Performances plus ou moins constantes, prévisibles?

mise à jour Voir les nouvelles informations en réponse ci-dessous.


0 commentaires

4 Réponses :


1
votes

J'ai expérimenté et j'ai raccordé un peu de plus et j'ai trouvé une solution qui peut être acceptable pour moi (bien que je n'ai actuellement que des écrivies séquentielles testées). Dans le processus, j'ai découvert des comportements inattendus qui posent un certain nombre de nouvelles questions. Je posterai une nouvelle question à la question ( Explication / information recherché: Windows Ecrire des performances d'E / S avec "FSYNC" (flushfilebuffers) ) pour ceux-ci.

J'ai ajouté les deux autres options supplémentaires à mon indice de référence:

  • Utilisez des écrivies non coupées / writesthrough (c'est-à-dire spécifier le file_flag_no_buffering et file_flag_write_through drapeaux)
  • Écrivez indirectement au fichier, via un fichier mappé de mémoire.

    Cela m'a fourni des résultats inattendus, dont l'un me donne une solution plus ou moins acceptable à mon problème. Lorsque "FSGNCCING" en combinaison avec des E / S ignorés / WritethRough, je n'observe pas une décroissance exponentielle de la vitesse d'écriture. Ainsi (bien que ce ne soit pas très rapide), cela me fournit une solution permettant de garantir que les données sont sur le disque, ce qui a une performance prévisible constante qui n'est pas affectée par la taille du fichier.

    Quelques autres résultats inattendus étaient les suivants:

    • Si un octet est écrit à la dernière position dans le fichier, avant d'effectuer la page écrit avec les options "FSYNC" et "non coupées / writethRough", le débit d'écriture est presque double.
    • La performance de nonuffer / écrit avec ou sans FSYNC est presque identique, sauf lorsqu'un octet a été écrit à la dernière position du fichier. Le débit d'écriture du scénario «nonuffered / écrit» sans «FSYNC» sur un fichier vide est d'environ 12,5 Mo / s, alors que dans le même scénario sur un fichier qui comporte un octet écrit dans la dernière position dans le fichier que le débit est trois fois plus haut à 37 Mo / s.
    • Écrire à un fichier indirectement via un fichier mail de mémoire en combinaison avec "FSYNC" indique la même diminution de débit exponentielle que celle observée dans les écrivies tamponnées directement à un fichier, même si "nonuffer / écraser" est défini sur le fichier. < / li>

      J'ai ajouté le code mis à jour que j'ai utilisé pour la référence à ma question initiale.

      Le graphique ci-dessous montre certains des nouveaux résultats supplémentaires.

      débit pour différentes combinaisons d'options


0 commentaires

0
votes

[mal; Voir les commentaires.]

Je pense que l'article que vous référence est incorrect pour indiquer que les flushfilebuffers ont un effet utile sur les E / S non avancé. Il fait référence à un document Microsoft, mais le papier en question ne fait aucune revendication de ce type.

Selon la documentation, l'utilisation d'E / S non défigé a le même effet que, mais est plus efficace que, appelant le flushfilebuffer après chaque écriture. Donc, la solution pratique consiste à utiliser des E / S non volés plutôt que de l'utilisation de FlushfileBuffer.

Notez cependant que l'utilisation d'un fichier mappé de mémoire défaite les paramètres de mémoire tampon. Je ne recommanderais pas d'utiliser un fichier mail de mémoire si vous essayez de repousser les données sur le disque dès que possible.


3 commentaires

De ce que je comprends que l'article est correct. Également confirmé par ex. Ici: support.microsoft.com/kb/332023 (voir la section "Plus d'informations"). Flushfilebuffers Traduit sur Synchroniser le cache Commande pour les périphériques SCSI et FLUSH CACHE Commande des appareils IDE / ATAPI. Ecrire via est émis sous la forme ForceUnittaccess , ce qui n'est apparemment pas manifesté par des périphériques IDE / ITAPI. Je crois que c'est aussi ce qui est mentionné dans l'article référencé FTP. recherche.microsoft.com/pub/tr/tr-2008-36.pdf .


@alex Ces articles parlent de cache à l'intérieur du disque / contrôleur, et non du niveau du système d'exploitation. Lorsque vous utilisez WritThRough, cela aurait dû le faire à cette "dans le cache de disque", mais ce n'est pas réellement "sur le disque". Pour les tests tels que votre Q, ce cache peut expliquer la baisse d'EXP lorsque votre volume IO augmente. Ce cache est une grosse affaire pour les bases de données lorsque ces caches "dans le disque" échouent sous condition PowerFail avant d'être écrites dans un secteur, voir support.microsoft.com/kb/234656


Je ne trouve rien dans ce document qui dit quoi que ce soit sur l'API Win32. Cependant, l'article de KB fait certainement!



4
votes

Votre test indique une diminution exponentielle de la vitesse sur la synchronisation des exécutions car vous recréez le fichier à chaque fois. Dans ce cas, ce n'est plus une écriture purement séquentielle - chaque écriture augmente également le fichier, ce qui nécessite plusieurs cherchent à mettre à jour les métadonnées de fichier dans le système de fichiers. Si vous rencontrez tous ces travaux à l'aide d'un fichier préexistant entièrement attribué, vous verriez un résultat beaucoup plus rapide car aucune de ces mises à jour de métadonnées ne serait interférée.

J'ai rencontré un test similaire sur ma boîte Linux. Les résultats tout en recréant le fichier à chaque fois: p> xxx pré>

Les résultats à l'aide du fichier préexistant (évidemment, l'affaire Last_Byte n'est pas pertinente ici. En outre, le tout premier résultat a également dû créer le fichier): p> xxx pré>

(Notez que je n'ai utilisé que 10 000 morceaux pas de 25 000 morceaux, il ne s'agit donc que de 320 Mo, à l'aide d'un système de fichiers ext2. Je n'ai pas eu de plus grand ext2fs pratique, mon plus grand FS est XFS et il a refusé d'autoriser MMAP + I / O direct.) P>

Voici le code, si vous êtes intéressé: P>

#define _GNU_SOURCE 1

#include <malloc.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>

#define USE_MMAP    8
#define USE_DIRECT  4
#define USE_LAST    2
#define USE_SYNC    1

#define PAGE    4096
#define CHUNK   (8*PAGE)
#define NCHUNKS 10000
#define STATI   1000

#define FSIZE   (NCHUNKS*CHUNK)

main()
{
    int i, j, fd, rc, stc;
    char *data = valloc(CHUNK);
    char *map, *dst;
    char sfname[8];
    struct timeval start, end, stats[NCHUNKS/STATI+1];
    FILE *sfile;

    printf("mmap\tdirect\tlast\tsync\ttime\n");
    for (i=0; i<16; i++) {
        int oflag = O_CREAT|O_RDWR|O_TRUNC;

        if (i & USE_DIRECT)
            oflag |= O_DIRECT;
        fd = open("dummy", oflag, 0666);
        ftruncate(fd, FSIZE);
        if (i & USE_LAST) {
            lseek(fd, 0, SEEK_END);
            write(fd, data, 1);
            lseek(fd, 0, SEEK_SET);
        }
        if (i & USE_MMAP) {
            map = mmap(NULL, FSIZE, PROT_WRITE, MAP_SHARED, fd, 0);
            if (map == (char *)-1L) {
                perror("mmap");
                exit(1);
            }
            dst = map;
        }
        sprintf(sfname, "%x.csv", i);
        sfile = fopen(sfname, "w");
        stc = 1;
        printf("%d\t%d\t%d\t%d\t",
            (i&USE_MMAP)!=0, (i&USE_DIRECT)!=0, (i&USE_LAST)!=0, i&USE_SYNC);
        fflush(stdout);
        gettimeofday(&start, NULL);
        stats[0] = start;
        for (j = 1; j<=NCHUNKS; j++) {
            if (i & USE_MMAP) {
                memcpy(dst, data, CHUNK);
                if (i & USE_SYNC)
                    msync(dst, CHUNK, MS_SYNC);
                dst += CHUNK;
            } else {
                write(fd, data, CHUNK);
                if (i & USE_SYNC)
                    fdatasync(fd);
            }
            if (!(j % STATI)) {
                gettimeofday(&end, NULL);
                stats[stc++] = end;
            }
        }
        end.tv_usec -= start.tv_usec;
        if (end.tv_usec < 0) {
            end.tv_sec--;
            end.tv_usec += 1000000;
        }
        end.tv_sec -= start.tv_sec;
        printf(" %d.%06ds\n", (int)end.tv_sec, (int)end.tv_usec);
        if (i & USE_MMAP)
            munmap(map, FSIZE);
        close(fd);
        for (j=NCHUNKS/STATI; j>0; j--) {
            stats[j].tv_usec -= stats[j-1].tv_usec;
            if (stats[j].tv_usec < 0) {
                stats[j].tv_sec--;
                stats[j].tv_usec+= 1000000;
            }
            stats[j].tv_sec -= stats[j-1].tv_sec;
        }
        for (j=1; j<=NCHUNKS/STATI; j++)
            fprintf(sfile, "%d\t%d.%06d\n", j*STATI*CHUNK,
                (int)stats[j].tv_sec, (int)stats[j].tv_usec);
        fclose(sfile);
    }
}


1 commentaires

Merci d'avoir regardé dans ce Howard. Je vais étudier ces résultats et remédier à mes tests à l'aide du scénario de fichier préexistant que vous mentionnez. Les résultats sur un boîtier de Linux et une boîte de fenêtres peuvent être différents, car je soupçonne que le cache de fichier de système d'exploitation et / ou la couche de pagination du noyau peut jouer un rôle important dans les performances dégradantes. Mais peut-être, comme vous le mentionnez, cela est lié aux mises à jour des métadonnées de fichiers.



2
votes

Voici une version Windows de mon code synchtonisé. Je ne le faisais que dans une Virtualbox VM, donc je ne pense pas avoir de numéros utiles à titre de comparaison, mais vous pourriez lui donner un tir à comparer à vos numéros C # sur votre machine. Je passe Open_alway à Createfile, il va donc réutiliser le fichier existant. Changer ce drapeau pour créer_always si vous souhaitez tester à nouveau avec un fichier vide à chaque fois.

Une chose que j'ai remarquée, c'est que les résultats étaient beaucoup plus rapides la première fois que je rencontrais ce programme. Peut-être que NTFS n'est pas très efficace pour écraser les données existantes et les effets de fragmentation de fichiers apparaissent sur les exécutions suivantes. P>

#include <windows.h>
#include <stdio.h>

#define USE_MMAP    8
#define USE_DIRECT  4
#define USE_LAST    2
#define USE_SYNC    1

#define PAGE    4096
#define CHUNK   (8*PAGE)
#define NCHUNKS 10000
#define STATI   1000

#define FSIZE   (NCHUNKS*CHUNK)

static LARGE_INTEGER cFreq;

int gettimeofday(struct timeval *tv, void *unused)
{
    LARGE_INTEGER count;
    if (!cFreq.QuadPart) {
        QueryPerformanceFrequency(&cFreq);
    }
    QueryPerformanceCounter(&count);
    tv->tv_sec = count.QuadPart / cFreq.QuadPart;
    count.QuadPart %= cFreq.QuadPart;
    count.QuadPart *= 1000000;
    tv->tv_usec = count.QuadPart / cFreq.QuadPart;
    return 0;
}

main()
{
    int i, j, rc, stc;
    HANDLE fd;
    char *data = _aligned_malloc(CHUNK, PAGE);
    char *map, *dst;
    char sfname[8];
    struct timeval start, end, stats[NCHUNKS/STATI+1];
    FILE *sfile;
    DWORD len;

    printf("mmap\tdirect\tlast\tsync\ttime\n");
    for (i=0; i<16; i++) {
        int oflag = FILE_ATTRIBUTE_NORMAL;

        if (i & USE_DIRECT)
            oflag |= FILE_FLAG_NO_BUFFERING|FILE_FLAG_WRITE_THROUGH;
        fd = CreateFile("dummy", GENERIC_READ|GENERIC_WRITE, 0, NULL,
            OPEN_ALWAYS, oflag, NULL);
        SetFilePointer(fd, FSIZE, NULL, FILE_BEGIN);
        SetEndOfFile(fd);
        if (i & USE_LAST)
            WriteFile(fd, data, 1, &len, NULL);
        SetFilePointer(fd, 0, NULL, FILE_BEGIN);
        if (i & USE_MMAP) {
            HANDLE mh;
            mh = CreateFileMapping(fd, NULL, PAGE_READWRITE,
                0, FSIZE, NULL);
            map = MapViewOfFile(mh, FILE_MAP_WRITE, 0, 0,
                FSIZE);
            CloseHandle(mh);
            dst = map;
        }
        sprintf(sfname, "%x.csv", i);
        sfile = fopen(sfname, "w");
        stc = 1;
        printf("%d\t%d\t%d\t%d\t",
            (i&USE_MMAP)!=0, (i&USE_DIRECT)!=0, (i&USE_LAST)!=0, i&USE_SYNC);
        fflush(stdout);
        gettimeofday(&start, NULL);
        stats[0] = start;
        for (j = 1; j<=NCHUNKS; j++) {
            if (i & USE_MMAP) {
                memcpy(dst, data, CHUNK);
                FlushViewOfFile(dst, CHUNK);
                dst += CHUNK;
            } else {
                WriteFile(fd, data, CHUNK, &len, NULL);
            }
            if (i & USE_SYNC)
                FlushFileBuffers(fd);
            if (!(j % STATI)) {
                gettimeofday(&end, NULL);
                stats[stc++] = end;
            }
        }
        end.tv_usec -= start.tv_usec;
        if (end.tv_usec < 0) {
            end.tv_sec--;
            end.tv_usec += 1000000;
        }
        end.tv_sec -= start.tv_sec;
        printf(" %d.%06ds\n", (int)end.tv_sec, (int)end.tv_usec);
        if (i & USE_MMAP)
            UnmapViewOfFile(map);
        CloseHandle(fd);
        for (j=NCHUNKS/STATI; j>0; j--) {
            stats[j].tv_usec -= stats[j-1].tv_usec;
            if (stats[j].tv_usec < 0) {
                stats[j].tv_sec--;
                stats[j].tv_usec+= 1000000;
            }
            stats[j].tv_sec -= stats[j-1].tv_sec;
        }
        for (j=1; j<=NCHUNKS/STATI; j++)
            fprintf(sfile, "%d\t%d.%06d\n", j*STATI*CHUNK,
                (int)stats[j].tv_sec, (int)stats[j].tv_usec);
        fclose(sfile);
    }
}


1 commentaires

Merci Howard, je vais également courir celui-là. Il peut éclairer des conclusions de mes réducteurs à l'aide d'un fichier entièrement écrit. Je suis juste en train de les ajouter à mes questions. Vous verrez que des comportements inexpliqués et inattendus demeurent, alors que l'explication que vous avez fournie vous convient bien à certains des changements de performance observés dans ce test.