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?
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).
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
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.