velocidade de pipe e redirecionamento, `pv` e UUOC
Eu estava testando métodos diferentes para produzir lixo aleatório e comparando sua velocidade canalizando a saída para pv, como em:
$ cmd | pv -s "$size" -S > /dev/null
Eu também queria uma "referência de linha de base", então medi o "gerador" mais rápido cat, com a fonte mais rápida /dev/zero:
$ cat /dev/zero | pv -s 100G -S > /dev/null
100GiB 0:00:33 [2,98GiB/s] [=============================>] 100%
3 GB / s , isso é bastante impressionante, especialmente em comparação com os ~ 70 MB que eu recebo /dev/urandom.
Mas ei, para o caso especial de /dev/zeronão preciso cat! Apenas por diversão, removi este livro UUOC :
$ < /dev/zero pv -s 100G -S > /dev/null
100GiB 0:00:10 [9,98GiB/s] [=============================>] 100%
O que??? Quase 10 GB / s ? Como remover catum tubo pode mais do que triplicar a velocidade? Se estiver usando uma fonte mais lenta, como /dev/urandoma diferença é insignificante. Está pvfazendo alguma magia vodu? Então eu testei:
$ 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 GB / s ! Mesmo aproximado pve 4 vezes mais rápido do que usar tubos.
É cata culpa? Os tubos são muito diferentes do redirecionamento? Afinal, ambos vão para pvas stdin, certo? O que pode explicar essa enorme diferença?
Respostas
O assassino é o uso de dois processos.
Com cat | pv, catlê e grava e pvlê e grava, e ambos os processos precisam ser executados:
$ 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
Com pvapenas, há apenas pvleitura e escrita, nenhuma troca de contexto necessária (ou quase nenhuma):
$ 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
Há algum paralelismo (“1.267 CPUs utilizadas”), mas isso não compensa a enorme diferença no número de trocas de contexto.
As coisas poderiam ser piores, considerando o caminho dos dados - no primeiro caso, os dados parecem fluir do kernel ( /dev/zero) para cat, de volta para o kernel (para o tubo), para pv, para o kernel ( /dev/null). No segundo, os dados fluem do kernel pvpara o kernel. Mas no primeiro cenário, pvusa splicepara copiar dados do pipe, evitando uma viagem pela memória do kernel.