web-dev-qa-db-fra.com

Comment fonctionne vraiment le fread?

La déclaration de fread est la suivante:

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

La question qui se pose est la suivante: existe-t-il une différence dans les performances de lecture de deux appels de ce type à fread:

char a[1000];
  1. fread(a, 1, 1000, stdin);
  2. fread(a, 1000, 1, stdin);

Lira-t-il 1000 Octets en une fois à chaque fois?

71
Beginner

Il peut y avoir ou non une différence de performance. Il y a une différence en sémantique.

fread(a, 1, 1000, stdin);

tente de lire 1000 éléments de données, chacun ayant une longueur de 1 octet.

fread(a, 1000, 1, stdin);

tente de lire 1 élément de données d'une longueur de 1000 octets.

Ils sont différents car fread() renvoie le nombre d'éléments de données qu'il a été capable de lire, pas le nombre d'octets. Si elle atteint la fin du fichier (ou une condition d'erreur) avant de lire les 1000 octets complets, la première version doit indiquer exactement le nombre d'octets lus; la seconde échoue et renvoie 0.

En pratique, il va probablement appeler une fonction de niveau inférieur qui tente de lire 1 000 octets et indique le nombre d'octets réellement lus. Pour les lectures plus volumineuses, il peut faire plusieurs appels de niveau inférieur. Le calcul de la valeur à renvoyer par fread() est différent, mais le coût du calcul est trivial.

Il peut y avoir une différence si l'implémentation peut dire, avant d'essayer de lire les données, qu'il n'y a pas assez de données à lire. Par exemple, si vous lisez un fichier de 900 octets, la première version lira tous les 900 octets et en renverra 900, tandis que la seconde ne lira peut-être pas. Dans les deux cas, l’indicateur de position du fichier est avancé du nombre caractères lu avec succès, c’est-à-dire 900.

Mais en général, vous devriez probablement choisir comment l'appeler en fonction des informations dont vous avez besoin. Lire un seul élément de données si une lecture partielle n'est pas meilleure que de ne rien lire du tout. Lire par petits morceaux si des lectures partielles sont utiles.

99
Keith Thompson

Selon la spécification , les deux peuvent être traités différemment par la mise en œuvre.

Si votre fichier contient moins de 1000 octets, fread(a, 1, 1000, stdin) (lire 1000 éléments de 1 octet chacun) continuera à copier tous les octets jusqu'à EOF. Par contre, le résultat de fread(a, 1000, 1, stdin) (élément 1 1000 octets) stocké dans a n’est pas spécifié, car il n’ya pas assez de données pour terminer la lecture du 'premier' (et seulement ) Élément de 1000 octets.

Bien entendu, certaines implémentations peuvent toujours copier l'élément 'partial' dans autant d'octets que nécessaire.

17
ArjunShankar

Ce serait détail de la mise en œuvre. Dans la glibc, les performances des deux systèmes sont identiques, car elles sont implémentées de la manière suivante (réf. http://sourceware.org/git/?p=glibc.git;a=blob;f=libio/iofread.c ):

size_t fread (void* buf, size_t size, size_t count, FILE* f)
{
    size_t bytes_requested = size * count;
    size_t bytes_read = read(f->fd, buf, bytes_requested);
    return bytes_read / size;
}

Notez que le C et POSIX standard ne garantit pas qu'un objet complet de taille size doit être lu à chaque fois. Si un objet complet ne peut pas être lu (par exemple, stdin ne contient que 999 octets mais vous avez demandé size == 1000), Le fichier sera laissé dans un état interdéterminé (C99 §7.19.8.1/2) .

Edit: Voir les autres réponses à propos de POSIX.

13
kennytm

fread appelle getc en interne. dans Minix le nombre de fois que getc est appelé est simplement size*nmemb, le nombre de fois que getc sera appelé dépend du produit de ces deux. Ainsi, fread(a, 1, 1000, stdin) et fread(a, 1000, 1, stdin) exécuteront getc1000=(1000*1) fois. Voici la simple implémentation de fread de Minix

size_t fread(void *ptr, size_t size, size_t nmemb, register FILE *stream){
register char *cp = ptr;
register int c;
size_t ndone = 0;
register size_t s;

if (size)
    while ( ndone < nmemb ) {
    s = size;
    do {
        if ((c = getc(stream)) != EOF)
            *cp++ = c;
        else
            return ndone;
    } while (--s);
    ndone++;
}

return ndone;
}
3
Neel Basu

Il peut ne pas y avoir de différence de performances, mais ces appels ne sont pas les mêmes.

  • fread renvoie le nombre d'éléments lus, donc ces appels renverront des valeurs différentes.
  • Si un élément ne peut pas être lu complètement, sa valeur est indéterminée:

Si une erreur se produit, la valeur résultante de l'indicateur de position du fichier pour le flux est indéterminée. Si un élément partiel est lu, sa valeur est indéterminée. (ISO/IEC 9899: TC2 7.19.8.1)

Il n'y a pas beaucoup de différence dans glibc implementation , qui multiplie simplement la taille de l'élément par le nombre d'éléments pour déterminer le nombre d'octets à lire et divise le montant lu par la taille du membre à la fin. Mais la version spécifiant une taille d'élément de 1 vous indiquera toujours le nombre correct d'octets lus. Toutefois, si vous ne vous souciez que de lire complètement les éléments d’une certaine taille, l’utilisation de l’autre formulaire vous évite d’effectuer une division.

3
Artefacto

Une autre forme de phrase http://pubs.opengroup.org/onlinepubs/000095399/functions/fread.html est remarquable

La fonction fread () doit lire dans le tableau pointé par ptr jusqu'à des éléments nitems dont la taille est spécifiée par taille en octets, à partir du flux pointé par flux. Pour chaque objet, des appels de taille doivent être effectués vers la fonction fgetc () et les résultats stockés , dans l'ordre de lecture, dans un tableau de caractères non signés. superposer l'objet.

Une petite quantité dans les deux cas sera accessible par fgetc () ...!

1
Jeegar Patel

Je voulais clarifier les réponses ici. fread effectue des entrées/sorties tamponnées. La taille réelle des blocs de lecture utilisés par Fread est déterminée par l'implémentation C utilisée.

Toutes les bibliothèques C modernes auront la même performance avec les deux appels:

fread(a, 1, 1000, file);
fread(a, 1000, 1, file);

Même quelque chose comme:

for (int i=0; i<1000; i++)
  a[i] = fgetc(file)

Cela devrait aboutir aux mêmes modèles d’accès au disque, bien que fgetc soit plus lent en raison du nombre d’appels dans les bibliothèques c standard et, dans certains cas, de la nécessité pour un disque d’effectuer des recherches supplémentaires qui auraient autrement été optimisées.

Revenons à la différence entre les deux formes de fread. Le premier retourne le nombre réel d'octets lus. Ce dernier renvoie 0 si la taille du fichier est inférieure à 1000, sinon il renvoie 1. Dans les deux cas, la mémoire tampon serait remplie avec les mêmes données, c'est-à-dire le contenu du fichier jusqu'à 1000 octets.

En général, vous souhaiterez probablement conserver le deuxième paramètre (taille) défini sur 1 afin d'obtenir le nombre d'octets lus.

0
Claris