pipe and redirection speed, `pv` and UUOC
I was testing different methods to produce random garbage and comparing their speed by piping output to pv, as in:
$ cmd | pv -s "$size" -S > /dev/null
I also wanted a "baseline reference", so I measured the the fastest "generator", cat, with the fastest source, /dev/zero:
$ cat /dev/zero | pv -s 100G -S > /dev/null
100GiB 0:00:33 [2,98GiB/s] [=============================>] 100%
3GB/s, that's pretty impressive, specially compared to ~70MB I get from /dev/urandom.
But hey, for the special case of /dev/zero I don't need cat! Just for the kicks I removed this textbook UUOC:
$ < /dev/zero pv -s 100G -S > /dev/null
100GiB 0:00:10 [9,98GiB/s] [=============================>] 100%
Quoi??? Près de 10 Go / s ? Comment le retrait catet un tuyau peuvent-ils plus que tripler la vitesse? Si vous utilisez une source plus lente telle que /dev/urandomla différence est négligeable. Est- pvce que faire de la magie vaudou? J'ai donc testé:
$ dd if=/dev/zero iflag=count_bytes count=200G of=/dev/null status=progress
205392969728 bytes (205 GB, 191 GiB) copied, 16 s, 12,8 GB/s
12,8 Go / s ! Même approximation que pv, et 4 fois plus rapide que l'utilisation de tuyaux.
Est-ce catà blâmer? Les tuyaux sont-ils si différents de la redirection? Après tout, les deux vont pvcomme stdin, non? Qu'est-ce qui peut expliquer cette énorme différence?
Réponses
Le tueur est l'utilisation de deux processus.
Avec cat | pv, catlit et écrit, pvlit et écrit, et les deux processus doivent s'exécuter:
$ perf stat sh -c 'cat /dev/zero | pv -s 100G -S > /dev/null'
100GiB 0:00:26 [3.72GiB/s] [====================================================================================>] 100%
Performance counter stats for 'sh -c cat /dev/zero | pv -s 100G -S > /dev/null':
34,048.63 msec task-clock # 1.267 CPUs utilized
1,676,706 context-switches # 0.049 M/sec
3,678 cpu-migrations # 0.108 K/sec
304 page-faults # 0.009 K/sec
119,270,941,758 cycles # 3.503 GHz (74.89%)
137,822,862,590 instructions # 1.16 insn per cycle (74.94%)
32,379,369,104 branches # 950.974 M/sec (75.14%)
216,658,446 branch-misses # 0.67% of all branches (75.04%)
26.865741948 seconds time elapsed
1.257950000 seconds user
38.893870000 seconds sys
Avec pvseulement, il n'y a que la pvlecture et l'écriture, pas de changement de contexte nécessaire (ou presque pas):
$ perf stat sh -c '< /dev/zero pv -s 100G -S > /dev/null'
100GiB 0:00:07 [13.3GiB/s] [====================================================================================>] 100%
Performance counter stats for 'sh -c < /dev/zero pv -s 100G -S > /dev/null':
7,501.68 msec task-clock # 1.000 CPUs utilized
37 context-switches # 0.005 K/sec
0 cpu-migrations # 0.000 K/sec
198 page-faults # 0.026 K/sec
27,916,420,023 cycles # 3.721 GHz (75.00%)
62,787,377,126 instructions # 2.25 insn per cycle (74.99%)
15,361,951,954 branches # 2047.801 M/sec (75.03%)
51,741,595 branch-misses # 0.34% of all branches (74.98%)
7.505304560 seconds time elapsed
1.768600000 seconds user
5.733786000 seconds sys
Il existe un certain parallélisme («1.267 CPUs utilisés»), mais cela ne compense pas l'énorme différence dans le nombre de changements de contexte.
Les choses pourraient être pires, compte tenu du chemin des données - dans le premier cas, les données semblent couler du noyau ( /dev/zero), vers cat, vers le noyau (pour le tube), vers pv, vers le noyau ( /dev/null). Dans le second, les données circulent du noyau pvvers le noyau. Mais dans le premier scénario, pvutilise splicepour copier les données du tube, évitant un voyage dans la mémoire appartenant au noyau.