web-dev-qa-db-fra.com

Pourquoi l'ouverture d'un fichier est-elle plus rapide que la lecture d'un contenu variable?

Dans un script bash, j'ai besoin de diverses valeurs de /proc/ des dossiers. Jusqu'à présent, j'ai des dizaines de lignes qui accueillent les fichiers directement comme ça:

grep -oP '^MemFree: *\K[0-9]+' /proc/meminfo

Dans un effort pour rendre cela plus efficace, j'ai enregistré le contenu du fichier dans une variable et j'ai salué que:

a=$(</proc/meminfo)
echo "$a" | grep -oP '^MemFree: *\K[0-9]+'

Au lieu d'ouvrir le fichier plusieurs fois, cela devrait simplement l'ouvrir une fois et grep le contenu variable, ce qui, je suppose, serait plus rapide - mais en fait, il est plus lent:

bash 4.4.19 $ time for i in {1..1000};do grep ^MemFree /proc/meminfo;done >/dev/null
real    0m0.803s
user    0m0.619s
sys     0m0.232s
bash 4.4.19 $ a=$(</proc/meminfo)
bash 4.4.19 $ time for i in {1..1000};do echo "$a"|grep ^MemFree; done >/dev/null
real    0m1.182s
user    0m1.425s
sys     0m0.506s

Il en va de même pour dash et zsh. Je soupçonnais l'état spécial de /proc/ fichiers comme raison, mais lorsque je copie le contenu de /proc/meminfo dans un fichier normal et utilisez les mêmes résultats:

bash 4.4.19 $ cat </proc/meminfo >meminfo
bash 4.4.19 $ time for i in $(seq 1 1000);do grep ^MemFree meminfo; done >/dev/null
real    0m0.790s
user    0m0.608s
sys     0m0.227s

L'utilisation d'une chaîne ici pour enregistrer le tuyau le rend légèrement plus rapide, mais toujours moins rapide qu'avec les fichiers:

bash 4.4.19 $ time for i in $(seq 1 1000);do <<<"$a" grep ^MemFree; done >/dev/null
real    0m0.977s
user    0m0.758s
sys     0m0.268s

Pourquoi l'ouverture d'un fichier est-elle plus rapide que la lecture du même contenu à partir d'une variable?

36
dessert

Ici, il ne s'agit pas d'ouvrir un fichier versus de lire le contenu d'une variable mais en savoir plus sur l'exécution d'un processus supplémentaire ou non.

grep -oP '^MemFree: *\K[0-9]+' /proc/meminfo Crée un processus qui exécute grep qui ouvre /proc/meminfo (Un fichier virtuel, en mémoire, aucune E/S disque impliquée) le lit et correspond à l'expression rationnelle.

La partie la plus coûteuse est de bifurquer le processus et de charger l'utilitaire grep et ses dépendances de bibliothèque, de faire la liaison dynamique, d'ouvrir la base de données locale, des dizaines de fichiers qui sont sur le disque (mais probablement mis en cache en mémoire).

La partie sur la lecture de /proc/meminfo Est insignifiante en comparaison, le noyau a besoin de peu de temps pour y générer les informations et grep a besoin de peu de temps pour les lire.

Si vous exécutez strace -c Sur cela, vous verrez les appels système open() et read() utilisés pour lire /proc/meminfo Sont des arachides par rapport à tout le reste grep fait pour commencer (strace -c ne compte pas la fourche).

Dans:

a=$(</proc/meminfo)

Dans la plupart des shells qui prennent en charge cet opérateur $(<...) ksh, le Shell ouvre simplement le fichier et lit son contenu (et supprime les caractères de fin de ligne). bash est différent et beaucoup moins efficace dans la mesure où il lance un processus pour effectuer cette lecture et transmet les données au parent via un canal. Mais ici, c'est fait une fois donc ça n'a pas d'importance.

Dans:

printf '%s\n' "$a" | grep '^MemFree'

Le shell doit générer deux processus, qui s'exécutent simultanément mais interagissent entre eux via un canal. La création, l'abattage, l'écriture et la lecture de tuyaux ont un coût minime. Le coût beaucoup plus élevé est le démarrage d'un processus supplémentaire. L'ordonnancement des processus a également un certain impact.

Vous pouvez constater que l'utilisation de l'opérateur zsh <<< Le rend légèrement plus rapide:

grep '^MemFree' <<< "$a"

Dans zsh et bash, cela se fait en écrivant le contenu de $a Dans un fichier temporaire, ce qui est moins cher que de générer un processus supplémentaire, mais ne vous procurera probablement aucun gain par rapport à l'obtention directe des données /proc/meminfo. C'est encore moins efficace que votre approche qui copie /proc/meminfo Sur le disque, car l'écriture du fichier temporaire se fait à chaque itération.

dash ne prend pas en charge les chaînes ici, mais ses heredocs sont implémentés avec un canal qui n'implique pas la création d'un processus supplémentaire. Dans:

 grep '^MemFree' << EOF
 $a
 EOF

Le Shell crée un tuyau, lance un processus. L'enfant exécute grep avec son stdin comme extrémité de lecture du tuyau, et le parent écrit le contenu à l'autre extrémité du tuyau.

Mais la gestion des tuyaux et la synchronisation des processus sont toujours plus coûteuses que la simple extraction des données /proc/meminfo.

Le contenu de /proc/meminfo Est court et ne prend pas beaucoup de temps à produire. Si vous souhaitez enregistrer certains cycles CPU, vous voulez supprimer les parties coûteuses: les processus de forking et l'exécution de commandes externes.

Comme:

IFS= read -rd '' meminfo < /proc/meminfo
memfree=${meminfo#*MemFree:}
memfree=${memfree%%$'\n'*}
memfree=${memfree#"${memfree%%[! ]*}"}

Évitez bash mais dont la correspondance de motifs est très inefficace. Avec zsh -o extendedglob, Vous pouvez le raccourcir à:

memfree=${${"$(</proc/meminfo)"##*MemFree: #}%%$'\n'*}

Notez que ^ Est spécial dans de nombreux shells (Bourne, fish, rc, es et zsh avec l'option extendedglob au moins), je vous recommande de le citer. Notez également que echo ne peut pas être utilisé pour générer des données arbitraires (d'où mon utilisation de printf ci-dessus).

47
Stéphane Chazelas

Dans votre premier cas, vous utilisez simplement l'utilitaire grep et trouvez quelque chose dans le fichier /proc/meminfo, /proc est un système de fichiers virtuel donc /proc/meminfo le fichier est en mémoire et il faut très peu de temps pour récupérer son contenu.

Mais dans le second cas, vous créez un canal, puis passez la sortie de la première commande à la deuxième commande à l'aide de ce canal, ce qui est coûteux.

La différence est due à /proc (car il est en mémoire) et pipe, voir l'exemple ci-dessous:

time for i in {1..1000};do grep ^MemFree /proc/meminfo;done >/dev/null

real    0m0.914s
user    0m0.032s
sys     0m0.148s


cat /proc/meminfo > file
time for i in {1..1000};do grep ^MemFree file;done >/dev/null

real    0m0.938s
user    0m0.032s
sys     0m0.152s


time for i in {1..1000};do echo "$a"|grep ^MemFree; done >/dev/null

real    0m1.016s
user    0m0.040s
sys     0m0.232s
6
Prvt_Yadav

Vous appelez une commande externe dans les deux cas (grep). L'appel externe nécessite un sous-shell. Forking que Shell est la cause fondamentale du retard. Les deux cas sont similaires, donc: un délai similaire.

Si vous souhaitez lire le fichier externe une seule fois et l'utiliser (à partir d'une variable) plusieurs fois, ne sortez pas du shell:

meminfo=$(< /dev/meminfo)    
time for i in {1..1000};do 
    [[ $meminfo =~ MemFree:\ *([0-9]*)\ *.B ]] 
    printf '%s\n' "${BASH_REMATCH[1]}"
done

Ce qui prend seulement environ 0,1 seconde au lieu de la pleine 1 seconde pour l'appel grep.

1
Isaac