A CPU é livre, mas o script bash não utiliza todos os recursos da CPU

4

Eu executei um script simples para gerar um arquivo csv grande (10000000 linhas) com 6 campos nos quais alguns campos foram alterados em cada linha / linha, usando um loop while . A máquina tinha todos (32) CPUs livres, muita memória RAM (~ 31 Gb) também estava livre.

Eu cronometrei o script com o comando

/usr/bin/time -v bash script.01.sh

Depois de correr por cerca de duas horas, recebi as seguintes estatísticas:

Command being timed: "bash script.01.sh"
User time (seconds): 1195.14
System time (seconds): 819.71
Percent of CPU this job got: 27%
Elapsed (wall clock) time (h:mm:ss or m:ss): 2:01:10
Average shared text size (kbytes): 0
Average unshared data size (kbytes): 0
Average stack size (kbytes): 0
Average total size (kbytes): 0
Maximum resident set size (kbytes): 4976
Average resident set size (kbytes): 0
Major (requiring I/O) page faults: 0
Minor (reclaiming a frame) page faults: 3131983488
Voluntary context switches: 22593141
Involuntary context switches: 10923348
Swaps: 0
File system inputs: 0
File system outputs: 2182920
Socket messages sent: 0
Socket messages received: 0
Signals delivered: 0
Page size (bytes): 4096
Exit status: 0

Eu quero saber por que meu script usou apenas 27% da CPU? O disco IO não era nada demais (viu na saída do vmstat). Então, o que causou a restrição? O código no script?

Aqui está o script:

#!/usr/bin/env bash
number=1

while [[ $number -lt 10000001 ]] ; do  
    fname="FirstName LastName $"
    lname=""  
    email="[email protected]"  
    password="1234567890"  
    altemail="[email protected]"  
    mobile="9876543210"      

    echo "$fname,$lname,$email,$password,$altemail,$mobile" >> /opt/list.csv
    number=$(expr $number + 1)  
done  
    
por Gautam Somani 01.01.2014 / 14:57

1 resposta

13

Usando strace , vi que a linha

number=$(expr $number + 1)

faz com que uma bifurcação, pesquisa de caminho e exec de expr . (Estou usando o bash 4.2.45 no Ubuntu). Esse sistema de arquivos, disco e sobrecarga de processo levaram a bater apenas 28% da CPU.

Quando eu mudei essa linha para usar apenas operações internas do shell

((number = number + 1))
O

bash usou cerca de 98% da CPU e o script foi executado em meia hora. Isso foi em um Celeron de 1.5GHz com CPU única.

O script como está não faz nada que roda em paralelo, então ter 32 CPUs livres não ajuda muito. No entanto, você pode certamente paralelizá-lo, por exemplo, dividindo-o em 10 loops de iteração de 1 milhão executados em paralelo, gravando em 10 arquivos diferentes e usando cat para combiná-los.

O seguinte programa de exemplo foi adicionado por @ Arthur2e5:

max=1000000 step=40000 tmp="$(mktemp -d)"
# Spawning. For loops make a bit more sense in a init-test-incr pattern.
for ((l = 0; l < max; l += step)); do (
    for ((n = l + 1, end = (step + l > max ? max : step + l);
      n <= end; n++)); do
        # Putting all those things into the 'buf' line gives you a 1.8x speedup.
        fname="FirstName LastName \$"
        lname=""  
        email="[email protected]"  
        password="1234567890"  
        altemail="[email protected]"  
        mobile="9876543210"
        buf+="$fname,$lname,$email,$password,$altemail,$mobile"$'\n'
    done
    printf '%s\n' "$buf" > "$tmp/$l" ) &
done # spawning..
wait
# Merging. The filename order from globbing will be a mess,
# since we didn't format $l to some 0-prefixed numbers.
# Let's just do the loop again.
for ((l = 0; l < max; l += step)); do
    printf '%s\n' "$(<"$tmp/$l")" >> /opt/list.csv
done # merging..
rm -rf -- "$tmp" # cleanup
    
por 01.01.2014 / 16:09