Como escrever strings de forma livre repetidas em um arquivo, tão rápido quanto 'dd'?

6

dd pode gravar repetidamente printf "%${1}s" | sed -e "s/ /${2}/g" bytes em um arquivo muito rápido, mas não pode gravar seqüências arbitrárias repetidas.
Existe um método bash-shell para escrever repetidamente strings tão rapidamente quanto 'dd' (incluindo dd )?

Todas as sugestões que encontrei em 6 meses de Linux são coisas como sed , mas isso é dolorosamente lento em comparação com sed , como mostrado abaixo, e dd falha após aproximadamente 384 MB (na minha caixa ) - na verdade isso não é ruim para uma única linha de comprimento :) - mas caiu!
Eu suponho que isso não seria um problema para printf , se a string contivesse uma nova linha.

Comparação de velocidade de sed vs. %code% + %code% :

                            real        user        sys       
WRITE 384 MB: 'dd'          0m03.833s   0m00.004s   0m00.548s
WRITE 384 MB: 'printf+sed'  1m39.551s   1m34.754s   0m02.968s

# the two commands used   
dd if=/dev/zero bs=1024 count=$((1024*384))
printf "%$((1024*1024*384))s" |sed -e "s/ /x/g"

Eu tenho uma idéia de como fazer isso em um script bash-shell , mas não adianta reinventar a roda. :)

    
por Peter.O 16.04.2011 / 19:04

8 respostas

5

$ time perl -e \
    '$count=1024*1024; while ($count>0) { print "x" x 384; $count--; }' > out
real    0m1.284s
user    0m0.316s
sys 0m0.961s
$ ls -lh out
-rw-r--r-- 1 me group 384M Apr 16 19:47 out

Substitua "x" x 384 (que produz uma string de 384 x s) com o que você quiser.

Você pode otimizar ainda mais isso usando uma string maior em cada loop e ignorando o buffer de saída padrão normal.

$ perl -e \
   '$count=384; while ($count>0) {
      syswrite(STDOUT, "x" x (1024*1024),  1024*1024);
      $count--;
    }' > out

Nesse caso, as chamadas syswrite passarão 1M de uma vez para o write syscall subjacente, o que está ficando muito bom. (Eu estou ficando em torno de 0,940s usuário com isso.)

Dica: certifique-se de chamar sync entre cada teste para evitar que o fluxo da execução anterior interfira na E / S da execução atual.

Para referência, eu recebo desta vez:

$ time dd if=/dev/zero bs=1024 count=$((1024*384)) of=./out
393216+0 records in
393216+0 records out
402653184 bytes (403 MB) copied, 1.41404 s, 285 MB/s

real    0m1.480s
user    0m0.054s
sys 0m1.410s
    
por 16.04.2011 / 19:49
2

Geralmente é esperado que os shells sejam lentos no processamento de grandes pedaços de dados. Para a maioria dos scripts, você sabe antecipadamente quais bits de dados provavelmente serão pequenos e quais bits de dados provavelmente serão grandes.

  • Prefere confiar nos built-ins do shell para dados pequenos, porque o processo de forking e execução de um processo externo induz uma sobrecarga constante.
  • Prefere confiar em ferramentas externas de propósito específico para dados grandes, porque as ferramentas compiladas para fins especiais são mais eficientes do que uma linguagem de propósito geral interpretada.

dd faz as chamadas read e write que usam o tamanho do bloco. Você pode observar isso com strace (ou truss, trace,… dependendo do seu sistema operacional):

$ strace -s9 dd if=/dev/zero of=/dev/null ibs=1024k obs=2048k count=4
✄
read(0, "
$ strace -s9 dd if=/dev/zero of=/dev/null ibs=1024k obs=2048k count=4
✄
read(0, "%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%"..., 1048576) = 1048576
read(0, "%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%"..., 1048576) = 1048576
write(1, "%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%"..., 2097152) = 2097152
read(0, "%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%"..., 1048576) = 1048576
read(0, "%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%"..., 1048576) = 1048576
write(1, "%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%"..., 2097152) = 2097152
✄
%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%"..., 1048576) = 1048576 read(0, "%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%"..., 1048576) = 1048576 write(1, "%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%"..., 2097152) = 2097152 read(0, "%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%"..., 1048576) = 1048576 read(0, "%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%"..., 1048576) = 1048576 write(1, "%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%%pre%"..., 2097152) = 2097152 ✄

A maioria das outras ferramentas tem um limite muito menor no tamanho máximo do buffer, portanto, elas criariam mais syscalls e, portanto, levariam mais tempo. Mas note que este é um benchmark irreal: se você estivesse escrevendo para um arquivo regular ou um pipe ou um socket, o kernel provavelmente não escreveria mais do que alguns kilobytes por syscall de qualquer maneira.

    
por 16.04.2011 / 19:56
2

Você pode usar dd para isso! Primeiro escreva a string no começo do arquivo. Então faça:

dd if=$FILE of=$FILE bs=$STRING_LENGTH seek=1 count=$REPEAT_TIMES

Nota: se o seu $ STRING_LENGTH é pequeno, você pode fazer algo como

dd if=$FILE of=$FILE bs=$STRING_LENGTH seek=1 count=$((1024/$REPEAT_TIMES))
dd if=$FILE of=$FILE bs=1024 seek=1 count=$(($REPEAT_TIMES/1024))

(Este exemplo só funciona se STRING_LENGTH for uma potência de 2 e REPEAT_TIMES for um múltiplo de 1024, mas você entender)

Se você quiser usar isso para substituir um arquivo (por exemplo, purgar), use conv=notrunc

    
por 26.05.2014 / 19:58
1

Versão em Python:

import sys

CHAR = sys.argv[1] if len(sys.argv) > 1 else "x"

block = CHAR * 1024
count = 1024 * 384

with open("testout.bin", "w") as outf:
    for i in xrange(count):
        outf.write(block)

python2.7 writestr.py x
Sistema 0.67s 0.67s usuário 99% cpu 0.963 total

dd if = / dev / zero de = testout.bin bs = 1024 count = $ ((1024 * 384))
Sistema de usuário 0.05s 1.05s 94% cpu 1.167 total

O Python tem um custo de inicialização maior, mas no geral bateu dd no meu sistema.

    
por 17.04.2011 / 09:47
1

Eu finalmente tive a minha ideia de como fazer isso funcionar ... Ele usa uma corrente tee | tee | tee , que corre a uma velocidade próxima a dd ..

# ============================================================================
# repstr
#
# Brief:
#   Make multiple (repeat) copies of a string.
#   Option -e, --eval is used as in 'echo -e'
#
# Return:
#   The resulting string is sent to stdout
#
#   Args:       Option      $1         $2
#             -e, --eval   COUNT      STRING
#     repstr             $((2**40))    "x"       # 1 TB:     xxxxxxxxx...
# eg. repstr  -e            7         "AB\tC\n"  # 7 lines:  AB<TAB>C
#     repstr                2         "ऑढळ|a"   # 2 copies:  ऑढळ|aऑढळ|a 
#

[[ "$1" == "-e" || "$1" == "--eval" ]] && { e="-e"; shift 1; }|| e=""
 count="$1"
string="$2"
[[ "${count}" == ""         ]] && exit 1 # $count must be an integer
[[ "${count//[0-9]/}" != "" ]] && exit 2 # $count is not an integer
[[ "${count}" == "0"        ]] && exit 0 # nothing to do
[[ "${string}" == ""        ]] && exit 0 # nothing to do
#
# ========================================================================
# Find the highest 'power of 2' which, when calculated**, is <= count
#   ie. check ascending 'powers of 2'
((leqXpo=0))  # Exponent which makes 2** <= count 
((leqCnt=1))  # A count which is <= count
while ((count>=leqCnt)) ;do
  ((leqXpo+=1))
  ((leqCnt*=2))
done
((leqXpo-=1))
((leqCnt/=2))
#   
# ======================================================================================
# Output $string to 'tee's which are daisy-chained in groups of descending 'powers of 2'
todo=$count
for ((xpo=leqXpo ;xpo>0 ;xpo--)) ;do
  tchain="" 
  floor=$((2**xpo))
  if ((todo>=(2**xpo))) ; then
    for ((t=0 ;t<xpo ;t++)) ;do tchain="$tchain|tee -" ;done
    eval echo -n $e \"'$string'\" $tchain # >/dev/null
    ((todo-=floor))
  fi
done
if ((todo==1)) ;then 
  eval echo -n $e \"'$string'\" # >/dev/null
fi
#

Aqui estão alguns resultados de testes. Eu passei para 32 GB porque é o tamanho de um arquivo de teste que eu queria criar (e foi o que me fez começar com essa questão)

NOTE: (2**30), etc. refers to the number of strings (to achieve a particular GB filesize)
-----
dd method (just for reference)                              real/user/sys
* 8GB                                                       =================================
    if=/dev/zero bs=1024 count=$(((1024**2)*8))         #   2m46.941s / 00m3.828s / 0m56.864s

tee method: fewer tests, because it didn't overflow, and the number-of-strings:time ratio is linear
tee method:              count        string                real/user/sys  
* 8GB                    ==========   ============          =================================
  tee(2**33)>stdout      $((2**33))   "x"               #   1m50.605s / 0m01.496s / 0m27.774s
  tee(2**30)>stdout  -e  $((2**30))   "xxx\txxx\n"      #   1m49.055s / 0m01.560s / 0m27.750s
* 32GB                                                     
  tee(2**35)>stdout  -e  $((2**35))   "x"               #   
  tee(2**32)>stdout  -e  $((2**32))   "xxx\txxx\n"      #   7m34.867s / 0m06.020s / 1m52.459s

python method: '.write'  uses 'file.write()' 
               '>stcout' uses 'sys.stdout.write()'. It handles \n in args (but I know very little python)
                            count   string                   real/user/sys
* 8GB                       =====   ===================      =================================
  python(2**33)a .write     2**33    "x"                 # OverflowError: repeated string is too long
  python(2**33)a >stdout    2**33    "x"                 # OverflowError: repeated string is too long
  python(2**30)b .write     2**30   '"xxxxxxxX" *2**0'   #   6m52.576s / 6m32.325s / 0m19.701s
  python(2**30)b >stdout    2**30   '"xxxxxxxX" *2**0'   #   8m11.374s / 7m49.101s / 0m19.573s
  python(2**30)c .write     2**20   '"xxxxxxxX" *2**10'  #   2m14.693s / 0m03.464s / 0m22.585s 
  python(2**30)c >stdout    2**20   '"xxxxxxxX" *2**10'  #   2m32.114s / 0m03.828s / 0m22.497s
  python(2**30)d .write     2**10   '"xxxxxxxX" *2**20'  #   2m16.495s / 0m00.024s / 0m12.029s
  python(2**30)d >stdout    2**10   '"xxxxxxxX" *2**20'  #   2m24.848s / 0m00.060s / 0m11.925s
  python(2**30)e .write     2**0    '"xxxxxxxX" *2**30'  # OverflowError: repeated string is too long
  python(2**30)e >stdout    2**0    '"xxxxxxxX" *2**30'  # OverflowError: repeated string is too long
* 32GB
  python(2**32)f.write      2**12   '"xxxxxxxX" *2**20'  #   7m58.608s / 0m00.160s / 0m48.703s
  python(2**32)f>stdout     2**12   '"xxxxxxxX" *2**20'  #   7m14.858s / 0m00.136s / 0m49.087s

perl method:
                           count   string                    real      / user       / sys
* 8GB                      =====   ===================       =================================
  perl(2**33)a .syswrite>  2**33    "a"        x 2**0    # Sloooooow! It would take 24 hours.   I extrapolated after 1 hour.   
  perl(2**33)a >stdout     2**33    "a"        x 2**0    #  31m46.405s / 31m13.925s /  0m22.745s
  perl(2**30)b .syswrite>  2**30    "aaaaaaaA" x 2**0    # 100m41.394s / 11m11.846s / 89m27.175s
  perl(2**30)b >stdout     2**30    "aaaaaaaA" x 2**0    #   4m15.553s /  3m54.615s /  0m19.949s
  perl(2**30)c .syswrite>  2**20    "aaaaaaaA" x 2**10   #   1m47.996s /  0m10.941s /  0m15.017s
  perl(2**30)c >stdout     2**20    "aaaaaaaA" x 2**10   #   1m47.608s /  0m12.237s /  0m23.761s
  perl(2**30)d .syswrite>  2**10    "aaaaaaaA" x 2**20   #   1m52.062s /  0m10.373s /  0m13.253s
  perl(2**30)d >stdout     2**10    "aaaaaaaA" x 2**20   #   1m48.499s /  0m13.361s /  0m22.197s
  perl(2**30)e .syswrite>  2**0     "aaaaaaaA" x 2**30   # Out of memory during string extend at -e line 1.   
  perl(2**30)e >stdout     2**0     "aaaaaaaA" x 2**30   # Out of memory during string extend at -e line 1.   
* 32GB
  perl(2**32)f .syswrite>  2**12    "aaaaaaaA" x 2**20   #   7m34.241s /  0m41.447s / 0m51.727s
  perl(2**32)f >stdout     2**12    "aaaaaaaA" x 2**20   #  10m58.444s /  0m53.771s / 1m28.498s
    
por 17.04.2011 / 11:17
0

Crie um arquivo pequeno com uma string - aqui eu uso "fussball" porque soa um pouco como 'foobar', mas tem 8 bytes de comprimento:

echo -n "fussball" >f3

Eu agora dobro constantemente o tamanho do arquivo produzido, e vejo em qual iteração eu estou (echo $ i). O nome inicial é 3, porque 2 ^ 3 = 8 e 2 ^ 4 é a duplicação de f3, que é f4 e assim por diante.

for i in {3..32}; do time cat f${i} f${i} > f$((i+1)) ; echo $i ; done
real    0m34.029s
user    0m0.016s
sys 0m3.868s
28

Eu então interrompo se o tempo estiver acima de 10 segundos e abaixo de um minuto (2 ^ 28 bytes). Então eu faço um teste similar para o dd, que termina no mesmo tamanho de arquivo:

for i in {1..16}; do time  dd if=/dev/zero of=g${i} bs=${i}M count=${i}  ; echo $i ; done
16+0 Datensätze ein
16+0 Datensätze aus
268435456 Bytes (268 MB) kopiert, 6,67487 s, 40,2 MB/s

real    0m6.683s
user    0m0.004s
sys 0m1.364s
16

Para um arquivo de tamanho 2 ^ 28, são necessários cerca de 35 seg. contra 7 seg. em um laptop de 5 anos com IDE-hdd, não ajustado ou configuração especial - uma máquina aleatória. De acordo com o meu cálculo. A velocidade dd de 40MB / s é o máximo que eu experimentei até agora. Se 1/5 dessa velocidade é ruim para você, cabe a você decidir. Mas tenho que emitir um aviso: A velocidade do cat a a > b -Test não estava aumentando linearmente com o tamanho. Houve às vezes os mesmos tempos para um tamanho duplicado, e às vezes demorava 10 * para 2 * do tamanho.

E uma segunda invocação fez uso pesado do cache, então eu tive que tocar no arquivo de origem, para evitar que o gato trapaceie. :) E eu acho que isso depende da memória da sua máquina e dos tamanhos do cache, onde os limites são, onde as coisas pioram e onde eles funcionam bem.

    
por 17.04.2011 / 01:35
0

Você já tentou usar yes ( algumas versões de yes são altamente otimizados )? Também fique de olho no truque yes ... | tr ... , que por sua vez pode ser usado como entrada dd (como mencionado por @ user23127) ! Do link :

dd if=<(yes $'\xFF' | LANG=C tr -d "\n") of=file count=1024 bs=1024

(supondo que seu shell é como bash).

    
por 12.11.2018 / 07:57
0

Para mantê-lo rápido, mantenha-o simples:

yes "arbitrary string" | dd iflag=fullblock bs=1024 count=$((1024*386)) of=file
    
por 12.11.2018 / 13:08