Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

dd can write repeating \0 bytes to a file very fast, but it can't write repeating arbitary strings.
Is there a bash-shell method to write repeating arbitary strings equally as fast as 'dd' (including \0 ) ?

All the suggestions I've encountered in 6 months of linux are things like printf "%${1}s" |sed -e "s/ /${2}/g", but this is painfully slow compared to dd, as shown bel402653184ow.. and sed crashes after approx 384 MB (on my box).. actually that's not bad for a single line-length :) but it did crash!
I suppose that wouldn't be an issue for 'sed', if the string contained a newline).

Speed Comparison 'dd' vs 'printf+sed'

                            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"

I have an idea how to do this in a bash-shell script. but there's no point re-inventing the wheel :)

share|improve this question
add comment

5 Answers

It's generally expected that shells are slow at processing large pieces of data. For most scripts, you know in advance which bits of data are likely to be small and which bits of data are likely to be large.

  • Prefer to rely on shell built-ins for small data, because forking and exec'ing an external process induces a constant overhead.
  • Prefer to rely on external, special-purpose tools for large data, because special-purpose compiled tools are more efficient than an interpreted general-purpose language.

dd makes read and write calls that use the block size. You can observe this with strace (or truss, trace, … depending on your OS):

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

Most other tools have a much lower cap on the maximum buffer size, so they would make more syscalls, and hence take more time. But note that this is an unrealistic benchmark: if you were writing to a regular file or a pipe or a socket, the kernel would probably not write more than a few kilobytes per syscall anyway.

share|improve this answer
    
Another inciteful answer, thanks... I really like your bullet-point maxims about when to "Prefer".... I'm starting to differentiate between shell built-ins and the externals... I've close to finished my alternative method.. it's speed is very close dd, and seems to be rather indifferent to the string size... (I'll try to post it sometime tomorrow, once I get it ship-shape :) ... –  Peter.O Apr 16 '11 at 19:37
    
@fred.bear, spelling tip: I suppose you meant "insightful" rather than "inciteful" (which does not exist, but could be linked to "to incite"). –  asoundmove Apr 16 '11 at 21:48
    
@asoundmove: Thanks. I'm quite happy with such alerts.. but I definitely(?) meant 'inciteful' :) oxforddictionaries.com/view/entry/m_en_gb0404940#m_en_gb0404940 (but not to incite to illegal actions, as the strict sense of the word implies... I may have got the two cross wired.. I recall both sentiments; "insight" and "being spurred on"... Actually, I'll concede.. Hey, :) my excuse is: not a lot of sleep last night. too much Q&A.... I think I did mean mainly "insight".. but I definitely recall thinking of both words. (a bit off topic, but a change is as good as a holiday :) –  Peter.O Apr 16 '11 at 22:40
    
@fred.bear: oh it does exist! New one on me. Learn something new everyday. –  asoundmove Apr 17 '11 at 3:43
add comment
$ 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

Replace "x" x 384 (which produces a string of 384 xs) with whatever you like.

You can optimize this further by using a bigger string in each loop, and bypassing normal standard out buffering.

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

In this case, the syswrite calls will pass down 1M at a time to the underlying write syscall, which is getting pretty good. (I'm getting around 0.940s user with this.)

Hint: make sure you call sync between each test to avoid having the previous run's flushing interfere with the current run's I/O.

For reference, I get this time:

$ 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
share|improve this answer
    
Interesting and useful.. As the string length reduces, the time increases ..On my box your exact command took real/user/sys **0m4.565s**/0m0.804s/0m0.904s ..with a string "x\n", it took r/u/s **0m30.227s**/0m29.202s/0m0.880s... but that's still certainly faster than printf--sed ... The 384 byte string version is about the same speed as dd on my system too...(it's funny how things vary... I actually got a slower dd speed this time... –  Peter.O Apr 16 '11 at 19:48
    
I've included some test times in my answer (so that all times relate to the same hardware). –  Peter.O Apr 18 '11 at 9:59
add comment

Python version:

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
0.27s user 0.69s system 99% cpu 0.963 total

dd if=/dev/zero of=testout.bin bs=1024 count=$((1024*384))
0.05s user 1.05s system 94% cpu 1.167 total

Python has a higher initialization cost, but overall beat dd on my system.

share|improve this answer
    
This is looking very good.. It think that the actual number of repeats (xrange) would depend on system resources, but it can get several GB of strings from xrange alone... (easily dealt with wit a bit of bounds checking)... I've included some test times in my answer.. Both your method, and my method are close to 'dd', timewise.. –  Peter.O Apr 17 '11 at 21:51
add comment

I've finally got my idea on how to do this working... It uses a tee |tee |tee chain, which runs at close to dd's speed..

# ============================================================================
# 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
#

Here are some time test results.. I've gone to 32 GB because thats the about the size of a test file I wanted to create (which is what started me off on this issue)

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
share|improve this answer
    
For better comparability you should perform all of your tests on /dev/shm to avoid interfering with the cache of your harddisk. Of course only if you have enough RAM in your machine. –  erik Jun 6 '13 at 9:02
add comment

Create a small file with a string - here I use "fussball" because it sounds a bit like 'foobar' but is 8 byte long:

echo -n "fussball" >f3

I now constantly double the the size of the produced file, and watch which iteration I am in (echo $i). The starting name is 3, because 2^3=8, and 2^4 is the doubling of f3 which is f4 and so on.

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

I then interrupt if the time is above 10 seconds and below one minute (2^28 bytes). Then I do a similar test for dd, which ends in the same filesize:

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

For a file of size 2^28 it takes about 35 sec vs. 7 sec on a 5 year old laptop with IDE-hdd, not tuned or special setup - a random machine. According to my calculation. The dd-speed of 40MB/s is about the maximum I experienced so far. Whether 1/5 of that speed is to bad for you is up to you to decide. But I have to issue a warning: The speed of the cat a a > b-Test wasn't increasing linearly with size. There was sometimes the same times for a doubled size, and sometimes it took 10* the time for 2* the size.

And a second invocation made heavy use of the cache, so I had to touch the source file, to prevent cat from cheating. :) And I guess it depends on the memory of your machine and cache sizes, where the limits are, where things get worse, and where they perform well.

share|improve this answer
    
In essence that's what I've done too..(but differently)... I'll check this later (busy now), and I've "answered" the question with my "tee" versoon... –  Peter.O Apr 17 '11 at 9:08
    
@user-unknown: I've looked at it again.. I think the idea is good (but I would, as we have both used a binary doubling :).. It creates a lot of files.. which then have to be selectively catd again to get the final desired number of strings. eg 987654321 ... repeats of your string... and as you said it slows downa lot with larger numbers of repeating strings... It has been running for aprox 40 mins to make a 32GB file, so I killed it. (I'm after a 35 GB file..) ... The tee process I've used takes 7-9 minutes... but I all for the binary idea.. binay splits and doublings are powerful tools –  Peter.O Apr 17 '11 at 10:54
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.