Skip site navigation (1)Skip section navigation (2)

FreeBSD Manual Pages

  
 
  

home | help
PARALLEL_DESIGN(7)		    parallel		      PARALLEL_DESIGN(7)

Design of GNU Parallel
     This  document  describes	design	decisions made in the development of GNU
     parallel and the reasoning behind them. It will give  an  overview  of  why
     some  of  the code looks the way it does, and will help new maintainers un-
     derstand the code better.

   One file program
     GNU parallel is a Perl script in a single file. It is object oriented,  but
     contrary  to normal Perl scripts each class is not in its own file. This is
     due to user experience: The goal is that in a pinch the user will	be  able
     to  get  GNU  parallel  working simply by copying a single file: No need to
     mess around with environment variables like PERL5LIB.

   Choice of programming language
     GNU parallel is designed to be able to run on old systems. That means  that
     it  cannot depend on a compiler being installed - and especially not a com-
     piler for a language that is younger than 20 years old.

     The goal is that you can use GNU parallel on any system, even  if	you  are
     not allowed to install additional software.

     Of  all the systems I have experienced, I have yet to see a system that had
     GCC installed that did not have Perl. The same goes for Rust, Go,	Haskell,
     and  other younger languages. I have, however, seen systems with Perl with-
     out any of the mentioned compilers.

     Most modern systems also have either Python2 or Python3 installed, but  you
     still  cannot  be certain which version, and since Python2 cannot run under
     Python3, Python is not an option.

     Perl has the added benefit that implementing the {= perlexpr =} replacement
     string was fairly easy.

     The primary drawback is that Perl is slow. So there is an overhead of  3-10
     ms/job and 1 ms/MB output (and even more if you use --tag).

   Old Perl style
     GNU parallel uses some old, deprecated constructs. This is due to a goal of
     being  able to run on old installations. Currently the target is CentOS 3.9
     and Perl 5.8.0.

   Scalability up and down
     The smallest system GNU parallel is tested on is a 32 MB ASUS WL500gP.  The
     largest  is  a 2 TB 128-core machine. It scales up to around 100 machines -
     depending on the duration of each job.

   Exponentially back off
     GNU parallel busy waits. This is because  the  reason  why  a  job  is  not
     started  may  be  due to load average (when using --load), and thus it will
     not make sense to just wait for a job to finish. Instead the  load  average
     must be rechecked regularly. Load average is not the only reason: --timeout
     has a similar problem.

     To  not  burn  up too much CPU GNU parallel sleeps exponentially longer and
     longer if nothing happens, maxing out at 1 second.

   Shell compatibility
     It is a goal to have GNU parallel work equally well in any shell.	However,
     in  practice  GNU	parallel  is being developed in bash and thus testing in
     other shells is limited to reported bugs.

     When an incompatibility is found there is often not an easy fix: Fixing the
     problem in csh often breaks it in bash. In these cases the fix is often  to
     use a small Perl script and call that.

   env_parallel
     env_parallel  is  a dummy shell script that will run if env_parallel is not
     an alias or a function and tell the user how to activate the alias/function
     for the supported shells.

     The alias or function will copy the current environment and run the command
     with GNU parallel in the copy of the environment.

     The problem is that you cannot access all of the current environment inside
     Perl. E.g. aliases, functions and unexported shell variables.

     The idea is therefore to take the environment and put it  in  $PARALLEL_ENV
     which GNU parallel prepends to every command.

     The  only way to have access to the environment is directly from the shell,
     so the program must be written in a shell script that will be  sourced  and
     there has to deal with the dialect of the relevant shell.

     env_parallel.*

     These  are the files that implements the alias or function env_parallel for
     a given shell. It could be argued that these should be put in some  obscure
     place  under  /usr/lib, but by putting them in your path it becomes trivial
     to find the path to them and source them:

       source `which env_parallel.foo`

     The beauty is that they can be put anywhere in the path  without  the  user
     having   to   know   the	location.   So	 if  the  user's  path	includes
     /afs/bin/i386_fc5	  or	/usr/pkg/parallel/bin	 or    /usr/local/paral-
     lel/20161222/sunos5.6/bin	the  files can be put in the dir that makes most
     sense for the sysadmin.

     env_parallel.bash / env_parallel.sh / env_parallel.ash /  env_parallel.dash
     / env_parallel.zsh / env_parallel.ksh / env_parallel.mksh

     env_parallel.(bash|sh|ash|dash|ksh|mksh|zsh)  defines the function env_par-
     allel. It uses alias and typeset to dump the configuration (with a few  ex-
     ceptions) into $PARALLEL_ENV before running GNU parallel.

     After GNU parallel is finished, $PARALLEL_ENV is deleted.

     env_parallel.csh

     env_parallel.csh has two purposes: If env_parallel is not an alias: make it
     into  an  alias  that  sets  $PARALLEL  with arguments and calls env_paral-
     lel.csh.

     If env_parallel is an alias, then env_parallel.csh uses  $PARALLEL  as  the
     arguments for GNU parallel.

     It  exports  the environment by writing a variable definition to a file for
     each variable.  The definitions of aliases are appended to this  file.  Fi-
     nally the file is put into $PARALLEL_ENV.

     GNU parallel is then run and $PARALLEL_ENV is deleted.

     env_parallel.fish

     First all functions definitions are generated using a loop and functions.

     Dumping the scalar variable definitions is harder.

     fish  can represent non-printable characters in (at least) 2 ways. To avoid
     problems all scalars are converted to \XX quoting.

     Then commands to generate the definitions are made and separated by NUL.

     This is then piped into a Perl script that quotes all values. List elements
     will be appended using two spaces.

     Finally \n is converted into \1 because fish variables cannot  contain  \n.
     GNU parallel will later convert all \1 from $PARALLEL_ENV into \n.

     This is then all saved in $PARALLEL_ENV.

     GNU parallel is called, and $PARALLEL_ENV is deleted.

   parset (supported in sh, ash, dash, bash, zsh, ksh, mksh)
     parset  is  a  shell  function. This is the reason why parset can set vari-
     ables: It runs in the shell which is calling it.

     It is also the reason why parset does not work, when data is piped into it:
     ... | parset ... makes parset start in a subshell, and any changes in envi-
     ronment can therefore not make it back to the calling shell.

   Job slots
     The easiest way to explain what GNU parallel does is to assume  that  there
     are a number of job slots, and when a slot becomes available a job from the
     queue  will  be run in that slot. But originally GNU parallel did not model
     job slots in the code. Job slots have been added to make it possible to use
     {%} as a replacement string.

     While the job sequence number can be computed in advance, the job slot  can
     only be computed the moment a slot becomes available. So it has been imple-
     mented  as  a  stack with lazy evaluation: Draw one from an empty stack and
     the stack is extended by one. When a job is done, push  the  available  job
     slot back on the stack.

     This implementation also means that if you re-run the same jobs, you cannot
     assume  jobs will get the same slots. And if you use remote executions, you
     cannot assume that a given job slot will remain on the same remote  server.
     This  goes  double since number of job slots can be adjusted on the fly (by
     giving --jobs a file name).

   Rsync protocol version
     rsync 3.1.x uses protocol 31 which is unsupported by  version  2.5.7.  That
     means  that  you cannot push a file to a remote system using rsync protocol
     31, if the remote system uses 2.5.7. rsync does not automatically downgrade
     to protocol 30.

     GNU parallel does not require protocol 31, so if the rsync  version  is  >=
     3.1.0  then --protocol 30 is added to force newer rsyncs to talk to version
     2.5.7.

   Compression
     GNU parallel buffers output in temporary files.  --compress compresses  the
     buffered  data.   This  is a bit tricky because there should be no files to
     clean up if GNU parallel is killed by a power outage.

     GNU parallel first selects a compression program. If the user has	not  se-
     lected  one,  the	first  of  these  that is in $PATH is used: pzstd lbzip2
     pbzip2 zstd pixz lz4 pigz lzop plzip lzip gzip lrz pxz bzip2 lzma xz clzip.
     They are sorted by speed on a 128 core machine.

     Schematically the setup is as follows:

       command started by parallel | compress > tmpfile
       cattail tmpfile | uncompress | parallel which reads the output

     The setup is duplicated for both standard output (stdout) and standard  er-
     ror (stderr).

     GNU parallel pipes output from the command run into the compression program
     which saves to a tmpfile. GNU parallel records the pid of the compress pro-
     gram.   At  the  same  time  a  small Perl script (called cattail above) is
     started: It basically does cat followed by tail -f, but it also removes the
     tmpfile as soon as the first byte is read, and it	continuously  checks  if
     the  pid  of  the	compression  program is dead. If the compress program is
     dead, cattail reads the rest of tmpfile and exits.

     As most compression programs write out a header when they start,  the  tmp-
     file in practice is removed by cattail after around 40 ms.

     More detailed it works like this:

       bash ( command ) |
	 sh ( emptywrapper ( bash ( compound compress ) ) >tmpfile )
       cattail ( rm tmpfile; compound decompress ) < tmpfile

     This  complex  setup  is  to  make sure compress program is only started if
     there is input. This means each job will cause 8 processes to run. If  com-
     bined  with  --keep-order	these  processes will run until the job has been
     printed.

   Wrapping
     The command given by the user can be wrapped in  multiple	templates.  Tem-
     plates can be wrapped in other templates.

     $COMMAND	    the command to run.

     $INPUT	    the input to run.

     $SHELL	    the shell that started GNU Parallel.

     $SSHLOGIN	    the sshlogin.

     $WORKDIR	    the working dir.

     $FILE	    the file to read parts from.

     $STARTPOS	    the first byte position to read from $FILE.

     $LENGTH	    the number of bytes to read from $FILE.

     --shellquote   echo Double quoted $INPUT

     --nice pri     Remote: See The remote system wrapper.

		    Local: setpriority(0,0,$nice)

     --cat
		      cat > {}; $COMMAND {};
		      perl -e '$bash = shift;
			$csh = shift;
			for(@ARGV) { unlink;rmdir; }
			if($bash =~ s/h//) { exit $bash;  }
			exit $csh;' "$?h" "$status" {};

		    {}	is  set  to  $PARALLEL_TMP  which is a tmpfile. The Perl
		    script saves the exit value, unlinks the  tmpfile,	and  re-
		    turns   the   exit	value  -  no  matter  if  the  shell  is
		    bash/ksh/zsh (using $?) or *csh/fish (using $status).

     --fifo
		      perl -e '($s,$c,$f) = @ARGV;
			# mkfifo $PARALLEL_TMP
			system "mkfifo", $f;
			# spawn $shell -c $command &
			$pid = fork || exec $s, "-c", $c;
			open($o,">",$f) || die $!;
			# cat > $PARALLEL_TMP
			while(sysread(STDIN,$buf,131072)){
			   syswrite $o, $buf;
			}
			close $o;
			# waitpid to get the exit code from $command
			waitpid $pid,0;
			# Cleanup
			unlink $f;
			exit $?/256;' $SHELL -c $COMMAND $PARALLEL_TMP

		    This is an elaborate way of: mkfifo {}; run $COMMAND in  the
		    background	using  $SHELL;	copying STDIN to {}; waiting for
		    background to complete; remove {} and  exit  with  the  exit
		    code from $COMMAND.

		    It is made this way to be compatible with *csh/fish.

     --pipepart
		      < $FILE perl -e 'while(@ARGV) {
			  sysseek(STDIN,shift,0) || die;
			  $left = shift;
			  while($read =
				sysread(STDIN,$buf,
					($left > 131072 ? 131072 : $left))){
			    $left -= $read;
			    syswrite(STDOUT,$buf);
			  }
			}' $STARTPOS $LENGTH

		    This  will read $LENGTH bytes from $FILE starting at $START-
		    POS and send it to STDOUT.

     --sshlogin $SSHLOGIN
		      ssh $SSHLOGIN "$COMMAND"

     --transfer
		      ssh $SSHLOGIN mkdir -p ./$WORKDIR;
		      rsync --protocol 30 -rlDzR \
			    -essh ./{} $SSHLOGIN:./$WORKDIR;
		      ssh $SSHLOGIN "$COMMAND"

		    Read about --protocol 30 in the section Rsync protocol  ver-
		    sion.

     --transferfile file
		    <<todo>>

     --basefile     <<todo>>

     --return file
		      $COMMAND; _EXIT_status=$?; mkdir -p $WORKDIR;
		      rsync --protocol 30 \
			--rsync-path=cd\ ./$WORKDIR\;\ rsync \
			-rlDzR -essh $SSHLOGIN:./$FILE ./$WORKDIR;
		      exit $_EXIT_status;

		    The  --rsync-path=cd  ...  is needed because old versions of
		    rsync do not support --no-implied-dirs.

		    The $_EXIT_status trick is to postpone the exit value.  This
		    makes  it  incompatible with *csh and should be fixed in the
		    future. Maybe a wrapping 'sh -c' is enough?

     --cleanup	    $RETURN is the wrapper from --return

		      $COMMAND; _EXIT_status=$?; $RETURN;
		      ssh $SSHLOGIN \(rm\ -f\ ./$WORKDIR/{}\;\
				      rmdir\ ./$WORKDIR\ \>\&/dev/null\;\);
		      exit $_EXIT_status;

		    $_EXIT_status: see --return above.

     --pipe
		      perl -e 'if(sysread(STDIN, $buf, 1)) {
			    open($fh, "|-", "@ARGV") || die;
			    syswrite($fh, $buf);
			    # Align up to 128k block
			    if($read = sysread(STDIN, $buf, 131071)) {
				syswrite($fh, $buf);
			    }
			    while($read = sysread(STDIN, $buf, 131072)) {
				syswrite($fh, $buf);
			    }
			    close $fh;
			    exit ($?&127 ? 128+($?&127) : 1+$?>>8)
			}' $SHELL -c $COMMAND

		    This small wrapper makes sure that $COMMAND  will  never  be
		    run if there is no data.

     --tmux	    <<TODO Fixup with '-quoting>> mkfifo /tmp/tmx3cMEV &&
		      sh  -c  'tmux  -S  /tmp/tmsaKpv1 new-session -s p334310 -d
		    "sleep .2" >/dev/null 2>&1'; tmux -S /tmp/tmsaKpv1	new-win-
		    dow   -t   p334310	 -n  wc\  10  \(wc\  10\)\;\  perl\  -e\
		    \'while\(\$t++\<3\)\{\  print\  \$ARGV\[0\],\"\\n\"\   \}\'\
		    \$\?h/\$status\ \>\>\ /tmp/tmx3cMEV\&echo\ wc\\\ 10\;\ echo\
		    \Job\  finished\  at:\  \`date\`\;sleep\  10;  exec  perl -e
		    '$/="/";$_=<>;$c=<>;unlink	   $ARGV;      /(\d+)h/      and
		    exit($1);exit$c' /tmp/tmx3cMEV

		    mkfifo  tmpfile.tmx;  tmux	-S  <tmpfile.tms> new-session -s
		    pPID -d 'sleep .2' >&/dev/null; tmux -S  <tmpfile.tms>  new-
		    window  -t	pPID  -n <<shell quoted input>> \(<<shell quoted
		    input>>\)\;\   perl\   -e\	 \'while\(\$t++\<3\)\{\   print\
		    \$ARGV\[0\],\"\\n\"\   \}\'\   \$\?h/\$status\   \>\>\  tmp-
		    file.tmx\&echo\ <<shell double quoted  input>>\;echo\  \Job\
		    finished\	at:\   \`date\`\;sleep\   10;	exec   perl   -e
		    '$/="/";$_=<>;$c=<>;unlink	   $ARGV;      /(\d+)h/      and
		    exit($1);exit$c' tmpfile.tmx

		    First  a  FIFO  is made (.tmx). It is used for communicating
		    exit value. Next a new tmux session is made. This  may  fail
		    if	there is already a session, so the output is ignored. If
		    all job slots finish at the same time, then tmux will  close
		    the  session.  A  temporary socket is made (.tms) to avoid a
		    race condition in tmux. It is cleaned up when  GNU	parallel
		    finishes.

		    The  input	is used as the name of the windows in tmux. When
		    the job inside tmux finishes, the exit value is  printed  to
		    the  FIFO (.tmx).  This FIFO is opened by perl outside tmux,
		    and perl then removes the FIFO. Perl blocks until the  first
		    value  is read from the FIFO, and this value is used as exit
		    value.

		    To make it compatible with csh and bash the  exit  value  is
		    printed as: $?h/$status and this is parsed by perl.

		    There  is  a  bug  that makes it necessary to print the exit
		    value 3 times.

		    Another bug in tmux requires the length of	the  tmux  title
		    and  command  to not have certain limits.  When inside these
		    limits, 75 '\ ' are added to the title to  force  it  to  be
		    outside the limits.

		    You can map the bad limits using:

		      perl -e 'sub r { int(rand(shift)).($_[0] && "\t".r(@_)) } print map { r(@ARGV)."\n" } 1..10000' 1600 1500 90 |
			perl -ane '$F[0]+$F[1]+$F[2] < 2037 and print ' |
			parallel --colsep '\t' --tagstring '{1}\t{2}\t{3}' tmux -S /tmp/p{%}-'{=3 $_="O"x$_ =}' \
			  new-session -d -n '{=1 $_="O"x$_ =}' true'\ {=2 $_="O"x$_ =};echo $?;rm -f /tmp/p{%}-O*'

		      perl -e 'sub r { int(rand(shift)).($_[0] && "\t".r(@_)) } print map { r(@ARGV)."\n" } 1..10000' 17000 17000 90 |
			parallel --colsep '\t' --tagstring '{1}\t{2}\t{3}' \
		      tmux -S /tmp/p{%}-'{=3 $_="O"x$_ =}' new-session -d -n '{=1 $_="O"x$_ =}' true'\ {=2 $_="O"x$_ =};echo $?;rm /tmp/p{%}-O*'
		      > value.csv 2>/dev/null

		      R -e 'a<-read.table("value.csv");X11();plot(a[,1],a[,2],col=a[,4]+5,cex=0.1);Sys.sleep(1000)'

		    For tmux 1.8 17000 can be lowered to 2100.

		    The  interesting areas are title 0..1000 with (title + whole
		    command) in 996..1127 and 9331..9636.

     The ordering of the wrapping is important:

     *	  $PARALLEL_ENV which is set in env_parallel.* must be prepended to  the
	  command  first, as the command may contain exported variables or func-
	  tions.

     *	  --nice/--cat/--fifo should be done on the remote machine

     *	  --pipepart/--pipe should be done on the local machine inside --tmux

   Convenience options --nice --basefile --transfer  --return  --cleanup  --tmux
     --group --compress --cat --fifo --workdir --tag --tagstring
     These  are  all  convenience  options that make it easier to do a task. But
     more importantly: They are tested to work on corner cases, too. Take --nice
     as an example:

       nice parallel command ...

     will work just fine. But when run remotely, you need to move the nice  com-
     mand so it is being run on the server:

       parallel -S server nice command ...

     And  this	will  again  work just fine, as long as you are running a single
     command. When you are running a composed command you need nice to apply  to
     the whole command, and it gets harder still:

       parallel -S server -q nice bash -c 'command1 ...; cmd2 | cmd3'

     It  is  not  impossible, but by using --nice GNU parallel will do the right
     thing for you. Similarly when transferring files: It  starts  to  get  hard
     when the file names contain space, :, `, *, or other special characters.

     To  run the commands in a tmux session you basically just need to quote the
     command. For simple commands that is easy, but when commands  contain  spe-
     cial characters, it gets much harder to get right.

     --compress  not  only compresses standard output (stdout) but also standard
     error (stderr); and it does so into files, that are open but deleted, so  a
     crash will not leave these files around.

     --cat  and  --fifo  are  easy to do by hand, until you want to clean up the
     tmpfile and keep the exit code of the command.

     The real killer comes when you try to combine several of these: Doing  that
     correctly for all corner cases is next to impossible to do by hand.

   --shard
     The simple way to implement sharding would be to:

     1.   start n jobs,

     2.   split each line into columns,

     3.   select the data from the relevant column

     4.   compute a hash value from the data

     5.   take the modulo n of the hash value

     6.   pass the full line to the jobslot that has the computed value

     Unfortunately Perl is rather slow at computing the hash value (and somewhat
     slow at splitting into columns).

     One  solution  is to use a compiled language for the splitting and hashing,
     but that would go against the design criteria of not depending  on  a  com-
     piler.

     Luckily  those tasks can be parallelized. So GNU parallel starts n sharders
     that do step 2-6, and passes blocks of 100k to each of  those  in	a  round
     robin  manner.  To  make sure these sharders compute the hash the same way,
     $PERL_HASH_SEED is set to the same value for all sharders.

     Running n sharders poses a new problem: Instead of having	n  outputs  (one
     for  each	computed value) you now have n outputs for each of the n values,
     so in total n*n outputs; and you need to merge these n*n  outputs	together
     into n outputs.

     This  can	be  done  by  simply  running  'parallel  -j0  --lb cat ::: out-
     puts_for_one_value', but that is rather inefficient, as it spawns a process
     for each file. Instead the core code from 'parcat' is run, which is also  a
     bit faster.

     All  the  sharders and parcats communicate through named pipes that are un-
     linked as soon as they are opened.

   Shell shock
     The shell shock bug in bash did not affect GNU parallel, but the  solutions
     did. bash first introduced functions in variables named: BASH_FUNC_myfunc()
     and  later  changed that to BASH_FUNC_myfunc%%. When transferring functions
     GNU parallel reads off the function and changes that into a function defin-
     ition, which is copied to the remote system and executed before the  actual
     command  is  executed. Therefore GNU parallel needs to know how to read the
     function.

     From version 20150122 GNU	parallel  tries  both  the  ()-version	and  the
     %%-version,  and  the function definition works on both pre- and post-shell
     shock versions of bash.

   The remote system wrapper
     The remote system wrapper does some initialization before starting the com-
     mand on the remote system.

     Make quoting unnecessary by hex encoding everything

     When you run ssh server foo then foo has to be quoted once:

       ssh server "echo foo; echo bar"

     If you run ssh server1 ssh server2 foo then foo has to be quoted twice:

       ssh server1 ssh server2 \'"echo foo; echo bar"\'

     GNU parallel avoids this by packing everyting into hex values and running a
     command that does not need quoting:

       perl -X -e GNU_Parallel_worker,eval+pack+q/H10000000/,join+q//,@ARGV

     This command reads hex from the command line and  converts  that  to  bytes
     that are then eval'ed as a Perl expression.

     The string GNU_Parallel_worker is not needed. It is simply there to let the
     user know, that this process is GNU parallel working.

     Ctrl-C and standard error (stderr)

     If the user presses Ctrl-C the user expects jobs to stop. This works out of
     the  box  if the jobs are run locally. Unfortunately it is not so simple if
     the jobs are run remotely.

     If remote jobs are run in a tty using ssh -tt, then Ctrl-C works,	but  all
     output to standard error (stderr) is sent to standard output (stdout). This
     is not what the user expects.

     If  remote  jobs are run without a tty using ssh (without -tt), then output
     to standard error (stderr) is kept on stderr, but Ctrl-C does not kill  re-
     mote jobs. This is not what the user expects.

     So  what  is  needed  is a way to have both. It seems the reason why Ctrl-C
     does not kill the remote jobs is because the shell does not  propagate  the
     hang-up signal from sshd. But when sshd dies, the parent of the login shell
     becomes  init  (process id 1). So by exec'ing a Perl wrapper to monitor the
     parent pid and kill the child if the parent  pid  becomes	1,  then  Ctrl-C
     works and stderr is kept on stderr.

     Ctrl-C  does, however, kill the ssh connection, so any output from a remote
     dying process is lost.

     To be able to kill all (grand)*children a new process group is started.

     --nice

     niceing the remote process is done by  setpriority(0,0,$nice).  A	few  old
     systems do not implement this and --nice is unsupported on those.

     Setting $PARALLEL_TMP

     $PARALLEL_TMP  is	used by --fifo and --cat and must point to a non-exitent
     file in $TMPDIR. This file name is computed on the remote system.

     The wrapper

     The wrapper looks like this:

       $shell = $PARALLEL_SHELL || $SHELL;
       $tmpdir = $TMPDIR || $PARALLEL_REMOTE_TMPDIR;
       $nice = $opt::nice;
       $termseq = $opt::termseq;

       # Check that $tmpdir is writable
       -w $tmpdir ||
	   die("$tmpdir is not writable.".
	     " Set PARALLEL_REMOTE_TMPDIR");
       # Set $PARALLEL_TMP to a non-existent file name in $TMPDIR
       do {
	   $ENV{PARALLEL_TMP} = $tmpdir."/par".
	     join"", map { (0..9,"a".."z","A".."Z")[rand(62)] } (1..5);
       } while(-e $ENV{PARALLEL_TMP});
       # Set $script to a non-existent file name in $TMPDIR
       do {
	   $script = $tmpdir."/par".
	     join"", map { (0..9,"a".."z","A".."Z")[rand(62)] } (1..5);
       } while(-e $script);
       # Create a script from the hex code
       # that removes itself and runs the commands
       open($fh,">",$script) || die;
       # ' needed due to rc-shell
       print($fh("rm \'$script\'\n",$bashfunc.$cmd));
       close $fh;
       my $parent = getppid;
       my $done = 0;
       $SIG{CHLD} = sub { $done = 1; };
       $pid = fork;
       unless($pid) {
	   # Make own process group to be able to kill HUP it later
	   eval { setpgrp };
	   # Set nice value
	   eval { setpriority(0,0,$nice) };
	   # Run the script
	   exec($shell,$script);
	   die("exec failed: $!");
       }
       while((not $done) and (getppid == $parent)) {
	   # Parent pid is not changed, so sshd is alive
	   # Exponential sleep up to 1 sec
	   $s = $s < 1 ? 0.001 + $s * 1.03 : $s;
	   select(undef, undef, undef, $s);
       }
       if(not $done) {
	   # sshd is dead: User pressed Ctrl-C
	   # Kill as per --termseq
	   my @term_seq = split/,/,$termseq;
	   if(not @term_seq) {
	     @term_seq = ("TERM",200,"TERM",100,"TERM",50,"KILL",25);
	   }
	   while(@term_seq && kill(0,-$pid)) {
	     kill(shift @term_seq, -$pid);
	     select(undef, undef, undef, (shift @term_seq)/1000);
	   }
       }
       wait;
       exit ($?&127 ? 128+($?&127) : 1+$?>>8)

   Transferring of variables and functions
     Transferring of variables and functions given by --env is done by running a
     Perl script remotely that calls the actual command. The  Perl  script  sets
     $ENV{variable}  to  the correct value before exec'ing a shell that runs the
     function definition followed by the actual command.

     The function env_parallel copies the full current environment into the  en-
     vironment variable PARALLEL_ENV. This variable is picked up by GNU parallel
     and used to create the Perl script mentioned above.

   Base64 encoded bzip2
     csh  limits  words of commands to 1024 chars. This is often too little when
     GNU parallel encodes environment variables and wraps the command with  dif-
     ferent  templates.  All  of  these  are combined and quoted into one single
     word, which often is longer than 1024 chars.

     When the line to run is > 1000 chars, GNU parallel  therefore  encodes  the
     line  to run. The encoding bzip2s the line to run, converts this to base64,
     splits the base64 into 1000  char	blocks	(so  csh  does	not  fail),  and
     prepends  it with this Perl script that decodes, decompresses and evals the
     line.

	 @GNU_Parallel=("use","IPC::Open3;","use","MIME::Base64");
	 eval "@GNU_Parallel";

	 $SIG{CHLD}="IGNORE";
	 # Search for bzip2. Not found => use default path
	 my $zip = (grep { -x $_ } "/usr/local/bin/bzip2")[0] || "bzip2";
	 # $in = stdin on $zip, $out = stdout from $zip
	 my($in, $out,$eval);
	 open3($in,$out,">&STDERR",$zip,"-dc");
	 if(my $perlpid = fork) {
	     close $in;
	     $eval = join "", <$out>;
	     close $out;
	 } else {
	     close $out;
	     # Pipe decoded base64 into 'bzip2 -dc'
	     print $in (decode_base64(join"",@ARGV));
	     close $in;
	     exit;
	 }
	 wait;
	 eval $eval;

     Perl and bzip2 must be installed on the remote system,  but  a  small  test
     showed  that  bzip2  is installed by default on all platforms that runs GNU
     parallel, so this is not a big problem.

     The added bonus of this is that much bigger environments can now be  trans-
     ferred as they will be below bash's limit of 131072 chars.

   Which shell to use
     Different	shells	behave differently. A command that works in tcsh may not
     work in bash.  It is therefore important that the	correct  shell	is  used
     when GNU parallel executes commands.

     GNU  parallel  tries hard to use the right shell. If GNU parallel is called
     from tcsh it will use tcsh.  If it is called from bash it will use bash. It
     does this by looking at the (grand)*parent process: If  the  (grand)*parent
     process  is  a  shell, use this shell; otherwise look at the parent of this
     (grand)*parent. If none of the (grand)*parents are shells, then  $SHELL  is
     used.

     This will do the right thing if called from:

     * an interactive shell

     * a shell script

     * a Perl script in `` or using system if called as a single string.

     While these cover most cases, there are situations where it will fail:

     * When run using exec.

     * When  run  as  the last command using -c from another shell (because some
       shells use exec):

	 zsh% bash -c "parallel 'echo {} is not run in bash; \
	      set | grep BASH_VERSION' ::: This"

       You can work around that by appending '&& true':

	 zsh% bash -c "parallel 'echo {} is run in bash; \
	      set | grep BASH_VERSION' ::: This && true"

     * When run in a Perl script using system with parallel as the first string:

	 #!/usr/bin/perl

	 system("parallel",'setenv a {}; echo $a',":::",2);

       Here it depends on which shell is used to call the Perl	script.  If  the
       Perl  script  is  called  from  tcsh it will work just fine, but if it is
       called from bash it will fail, because the command setenv is not known to
       bash.

     If GNU parallel guesses wrong in these situation, set the shell using $PAR-
     ALLEL_SHELL.

   Always running commands in a shell
     If the command is a simple command with no redirection and setting of vari-
     ables, the command could be run without spawning a shell. E.g. this  simple
     grep matching either 'ls ' or ' wc >> c':

       parallel "grep -E 'ls | wc >> c' {}" ::: foo

     could be run as:

       system("grep","-E","ls | wc >> c","foo");

     However,  as  soon  as  the  command  is a bit more complex a shell must be
     spawned:

       parallel "grep -E 'ls | wc >> c' {} | wc >> c" ::: foo
       parallel "LANG=C grep -E 'ls | wc >> c' {}" ::: foo

     It is impossible to tell how | wc >> c should be interpreted without  pars-
     ing  the  string (is the | a pipe in shell or an alternation in a grep reg-
     exp?  Is LANG=C a command in csh or setting a variable in bash? Is >> redi-
     rection or part of a regexp?).

     On top of this, wrapper scripts will often require a shell to be spawned.

     The downside is that you need to quote special shell chars twice:

       parallel echo '*' ::: This will expand the asterisk
       parallel echo "'*'" ::: This will not
       parallel "echo '*'" ::: This will not
       parallel echo '\*' ::: This will not
       parallel echo \''*'\' ::: This will not
       parallel -q echo '*' ::: This will not

     -q will quote all special chars,  thus  redirection  will	not  work:  this
     prints '* > out.1' and does not save '*' into the file out.1:

       parallel -q echo "*" ">" out.{} ::: 1

     GNU  parallel  tries  to live up to Principle Of Least Astonishment (POLA),
     and the requirement of using -q is hard to understand, when you do not  see
     the whole picture.

   Quoting
     Quoting depends on the shell. For most shells '-quoting is used for strings
     containing special characters.

     For  tcsh/csh  newline  is  quoted  as \ followed by newline. Other special
     characters are also \-quoted.

     For rc everything is quoted using '.

   --pipepart vs. --pipe
     While --pipe and --pipepart look much the same to the user, they are imple-
     mented very differently.

     With --pipe GNU parallel reads the  blocks  from  standard  input	(stdin),
     which  is	then  given  to  the command on standard input (stdin); so every
     block is being processed by GNU parallel itself. This  is	the  reason  why
     --pipe maxes out at around 500 MB/sec.

     --pipepart,  on  the  other  hand, first identifies at which byte positions
     blocks start and how long they are. It does that by seeking into  the  file
     by  the size of a block and then reading until it meets end of a block. The
     seeking explains why GNU parallel does not know the  line	number	and  why
     -L/-l and -N do not work.

     With  a  reasonable block and file size this seeking is more than 1000 time
     faster than reading the full file. The byte positions are then given  to  a
     small  script  that reads from position X to Y and sends output to standard
     output (stdout). This small script is prepended to the command and the full
     command is executed just as if GNU parallel had been in  its  normal  mode.
     The script looks like this:

       < file perl -e 'while(@ARGV) {
	  sysseek(STDIN,shift,0) || die;
	  $left = shift;
	  while($read = sysread(STDIN,$buf,
				($left > 131072 ? 131072 : $left))){
	    $left -= $read; syswrite(STDOUT,$buf);
	  }
       }' startbyte length_in_bytes

     It delivers 1 GB/s per core.

     Instead  of the script dd was tried, but many versions of dd do not support
     reading from one byte to another and might cause partial data. See this for
     a surprising example:

       yes | dd bs=1024k count=10 | wc

   --block-size adjustment
     Every time GNU parallel detects a record bigger than  --block-size  it  in-
     creases the block size by 30%. A small --block-size gives very poor perfor-
     mance; by exponentially increasing the block size performance will not suf-
     fer.

     GNU  parallel  will waste CPU power if --block-size does not contain a full
     record, because it tries to find a full record and will fail to do so.  The
     recommendation  is  therefore to use a --block-size > 2 records, so you al-
     ways get at least one full record when you read one block.

     If you use -N then  --block-size  should  be  big	enough	to  contain  N+1
     records.

   Automatic --block-size computation
     With  --pipepart GNU parallel can compute the --block-size automatically. A
     --block-size of -1 will use a block size so that each jobslot will  receive
     approximately  1  block.  --block -2 will pass 2 blocks to each jobslot and
     -n will pass n blocks to each jobslot.

     This can be done because --pipepart reads from files, and	we  can  compute
     the total size of the input.

   --jobs and --onall
     When  running the same commands on many servers what should --jobs signify?
     Is it the number of servers to run on in parallel?  Is  it  the  number  of
     jobs run in parallel on each server?

     GNU  parallel lets --jobs represent the number of servers to run on in par-
     allel. This is to make it possible to run a sequence of commands (that can-
     not be parallelized) on each server, but run the same sequence on	multiple
     servers.

   --shuf
     When  using  --shuf  to  shuffle the jobs, all jobs are read, then they are
     shuffled, and finally executed. When using SQL this makes	the  --sqlmaster
     be  the  part  that shuffles the jobs. The --sqlworkers simply executes ac-
     cording to Seq number.

   --csv
     --pipepart is incompatible with --csv because you can have records like:

       a,b,c
       a,"
       a,b,c
       a,b,c
       a,b,c
       ",c
       a,b,c

     Here the second record contains a multi-line field that looks like records.
     Since --pipepart does not read then whole file when  searching  for  record
     endings,  it  may	start  reading	in this multi-line field, which would be
     wrong.

   Buffering on disk
     GNU parallel buffers output, because if output is not buffered you have  to
     be  ridiculously careful on sizes to avoid mixing of outputs (see excellent
     example on https://catern.com/posts/pipes.html).

     GNU parallel buffers on disk in $TMPDIR using files, that	are  removed  as
     soon  as they are created, but which are kept open. So even if GNU parallel
     is killed by a power outage, there will be no files to clean up afterwards.
     Another advantage is that the file system is aware that these files will be
     lost in case of a crash, so it does not need to sync them to disk.

     It gives the odd situation that a disk can be fully used, but there are  no
     visible files on it.

     Partly buffering in memory

     When  using  output  formats  SQL and CSV then GNU Parallel has to read the
     whole output into memory. When run normally it will only  read  the  output
     from a single job. But when using --linebuffer every line printed will also
     be buffered in memory - for all jobs currently running.

     If memory is tight, then do not use the output format SQL/CSV with --lineb-
     uffer.

     Comparing to buffering in memory

     gargs  is	a  parallelizing  tool that buffers in memory. It is therefore a
     useful way of comparing the advantages and disadvantages  of  buffering  in
     memory to buffering on disk.

     On  an  system with 6 GB RAM free and 6 GB free swap these were tested with
     different sizes:

       echo /dev/zero | gargs "head -c $size {}" >/dev/null
       echo /dev/zero | parallel "head -c $size {}" >/dev/null

     The results are here:

       JobRuntime      Command
	    0.344      parallel_test 1M
	    0.362      parallel_test 10M
	    0.640      parallel_test 100M
	    9.818      parallel_test 1000M
	   23.888      parallel_test 2000M
	   30.217      parallel_test 2500M
	   30.963      parallel_test 2750M
	   34.648      parallel_test 3000M
	   43.302      parallel_test 4000M
	   55.167      parallel_test 5000M
	   67.493      parallel_test 6000M
	  178.654      parallel_test 7000M
	  204.138      parallel_test 8000M
	  230.052      parallel_test 9000M
	  255.639      parallel_test 10000M
	  757.981      parallel_test 30000M
	    0.537      gargs_test 1M
	    0.292      gargs_test 10M
	    0.398      gargs_test 100M
	    3.456      gargs_test 1000M
	    8.577      gargs_test 2000M
	   22.705      gargs_test 2500M
	  123.076      gargs_test 2750M
	   89.866      gargs_test 3000M
	  291.798      gargs_test 4000M

     GNU parallel is pretty much limited by the speed of the disk: Up  to  6  GB
     data is written to disk but cached, so reading is fast. Above 6 GB data are
     both  written and read from disk. When the 30000MB job is running, the disk
     system is slow, but usable: If you are not using the disk,  you  almost  do
     not feel it.

     gargs  has  a speed advantage up until 2500M where it hits a wall. Then the
     system starts swapping like crazy and is completely unusable. At  5000M  it
     goes out of memory.

     You can make GNU parallel behave similar to gargs if you point $TMPDIR to a
     tmpfs-filesystem:	It  will  be faster for small outputs, but may kill your
     system for larger outputs and cause you to lose output.

   Disk full
     GNU parallel buffers on disk. If the disk is full, data  may  be  lost.  To
     check  if	the disk is full GNU parallel writes a 8193 byte file every sec-
     ond. If this file is written successfully, it is removed immediately. If it
     is not written successfully, the disk is full. The size 8193 was chosen be-
     cause 8192 gave wrong result on some file systems,  whereas  8193	did  the
     correct thing on all tested filesystems.

   Memory usage
     Normally  GNU parallel will use around 17 MB RAM constantly - no matter how
     many jobs or how much output there is. There are a few  things  that  cause
     the memory usage to rise:

     *	Multiple  input  sources.  GNU parallel reads an input source only once.
	This is by design, as an input source can be a stream (e.g. FIFO,  pipe,
	standard  input  (stdin))  which  cannot be rewound and read again. When
	reading a single input source, the memory is freed as soon as the job is
	done - thus keeping the memory usage constant.

	But when reading multiple input sources GNU parallel keeps  the  already
	read values for generating all combinations with other input sources.

     *	Computing  the	number	of  jobs.  --bar,  --eta, and --halt xx% use to-
	tal_jobs() to compute the total number of jobs. It does this by generat-
	ing the data structures for all jobs. All these job data structures will
	be stored in memory and take up around 400 bytes/job.

     *	Buffering a full line. --linebuffer will read a full  line  per  running
	job. A very long output line (say 1 GB without \n) will increase RAM us-
	age  temporarily:  From  when the beginning of the line is read till the
	line is printed.

     *	Buffering the full output of a single job. This happens when using --re-
	sults *.csv/*.tsv or --sql*. Here GNU parallel will read the whole  out-
	put of a single job and save it as csv/tsv or SQL.

   Argument separators ::: :::: :::+ ::::+
     The argument separator ::: was chosen because I have never seen ::: used in
     any  command. The natural choice -- would be a bad idea since it is not un-
     likely that the template command will contain --. I have seen  ::	used  in
     programming  languanges to separate classes, and I did not want the user to
     be confused that the separator had anything to do with classes.

     ::: also makes a visual separation, which is good	if  there  are	multiple
     :::.

     When ::: was chosen, :::: came as a fairly natural extension.

     Linking input sources meant having to decide for some way to indicate link-
     ing  of ::: and ::::. :::+ and ::::+ were chosen, so that they were similar
     to ::: and ::::.

     In 2022 I realized that /// would have been an even better choice,  because
     you cannot have an file named /// whereas you can have a file named :::.

   Perl replacement strings, {= =}, and --rpl
     The  shorthands  for  replacement strings make a command look more cryptic.
     Different users will need different replacement strings. Instead of invent-
     ing more shorthands you get more flexible replacement strings if  they  can
     be programmed by the user.

     The language Perl was chosen because GNU parallel is written in Perl and it
     was easy and reasonably fast to run the code given by the user.

     If a user needs the same programmed replacement string again and again, the
     user  may want to make his own shorthand for it. This is what --rpl is for.
     It works so well, that even GNU parallel's own shorthands	are  implemented
     using --rpl.

     In  Perl code the bigrams {= and =} rarely exist. They look like a matching
     pair and can be entered on all keyboards. This made  them	good  candidates
     for  enclosing the Perl expression in the replacement strings. Another can-
     didate ,, and ,, was rejected because they do  not  look  like  a	matching
     pair.  --parens was made, so that the users can still use ,, and ,, if they
     like: --parens ,,,,

     Internally, however, the {= and =} are replaced by \257< and \257>. This is
     to make it simpler to make regular expressions. You only need to  look  one
     character ahead, and never have to look behind.

   Test suite
     GNU parallel uses its own testing framework. This is mostly due to histori-
     cal  reasons. It deals reasonably well with tests that are dependent on how
     long a given test runs (e.g. more than 10 secs is a pass,	but  less  is  a
     fail). It parallelizes most tests, but it is easy to force a test to run as
     the  single  test (which may be important for timing issues). It deals rea-
     sonably well with tests that fail intermittently. It  detects  which  tests
     failed  and  pushes these to the top, so when running the test suite again,
     the tests that failed most recently are run first.

     If GNU parallel should adopt a real testing framework then  those	elements
     would be important.

     Since  many  tests  are dependent on which hardware it is running on, these
     tests break when run on a different hardware than what the test was written
     for.

     When most bugs are fixed a test is added, so this bug will not reappear. It
     is, however, sometimes hard to create the	environment  in  which	the  bug
     shows up - especially if the bug only shows up sometimes. One of the harder
     problems  was  to	make  a machine start swapping without forcing it to its
     knees.

   Median run time
     Using a percentage for --timeout causes GNU parallel to compute the  median
     run  time	of  a  job. The median is a better indicator of the expected run
     time than average, because there will often be outliers taking  way  longer
     than the normal run time.

     To avoid keeping all run times in memory, an implementation of remedian was
     made (Rousseeuw et al).

   Error messages and warnings
     Error  messages  like:  ERROR,  Not found, and 42 are not very helpful. GNU
     parallel strives to inform the user:

     * What went wrong?

     * Why did it go wrong?

     * What can be done about it?

     Unfortunately it is not always possible to predict the root  cause  of  the
     error.

   Determine number of CPUs
     CPUs  is  an  ambiguous term. It can mean the number of socket filled (i.e.
     the number of physical chips). It can mean the number of  cores  (i.e.  the
     number  of physical compute cores). It can mean the number of hyperthreaded
     cores (i.e. the number of virtual cores - with some of them possibly  being
     hyperthreaded).

     On ark.intel.com Intel uses the terms cores and threads for number of phys-
     ical cores and the number of hyperthreaded cores respectively.

     GNU  parallel  uses  uses CPUs as the number of compute units and the terms
     sockets, cores, and threads to specify how the number of compute  units  is
     calculated.

   Computation of load
     Contrary  to  the	obvious --load does not use load average. This is due to
     load average rising too slowly. Instead it uses ps to list  the  number  of
     threads  in  running  or blocked state (state D, O or R). This gives an in-
     stant load.

     As remote calculation of load can be slow, a process is spawned to  run  ps
     and put the result in a file, which is then used next time.

   Killing jobs
     GNU  parallel  kills  jobs. It can be due to --memfree, --halt, or when GNU
     parallel meets a condition from which  it	cannot	recover.  Every  job  is
     started  as  its  own process group. This way any (grand)*children will get
     killed, too. The process group is killed with the	specification  mentioned
     in --termseq.

   SQL interface
     GNU  parallel  uses the DBURL from GNU sql to give database software, user-
     name, password, host, port, database, and table in a single string.

     The DBURL must point to a table name. The table will be  dropped  and  cre-
     ated.  The  reason  for  not reusing an existing table is that the user may
     have added more input sources which would require more columns in	the  ta-
     ble. By prepending '+' to the DBURL the table will not be dropped.

     The table columns are similar to joblog with the addition of V1 .. Vn which
     are values from the input sources, and Stdout and Stderr which are the out-
     put from standard output and standard error, respectively.

     The  Signal  column  has  been renamed to _Signal due to Signal being a re-
     served word in MySQL.

   Logo
     The logo is inspired by the Cafe Wall illusion. The font is DejaVu Sans.

   Citation notice
     For       details:       See	https://git.savannah.gnu.org/cgit/paral-
     lel.git/tree/doc/citation-notice-faq.txt

     Funding  a  free software project is hard. GNU parallel is no exception. On
     top of that it seems the less visible a project is, the harder it is to get
     funding. And the nature of GNU parallel is that it will never  be	seen  by
     "the guy with the checkbook", but only by the people doing the actual work.

     This  problem  has  been  covered	by  others - though no solution has been
     found:	  https://www.slideshare.net/NadiaEghbal/consider-the-maintainer
     https://www.numfocus.org/blog/why-is-numpy-only-now-getting-funded/

     Before  implementing  the	citation notice it was discussed with the users:
     https://lists.gnu.org/archive/html/parallel/2013-11/msg00006.html

     Having to spend 10 seconds on running parallel --citation once is no  doubt
     not an ideal solution, but no one has so far come up with an ideal solution
     - neither for funding GNU parallel nor other free software.

     If you believe you have the perfect solution, you should try it out, and if
     it  works,  you should post it on the email list. Ideas that will cost work
     and which have not been tested are, however, unlikely to be prioritized.

     Running parallel --citation one single time takes less than 10 seconds, and
     will silence the citation notice for future runs.	This  is  comparable  to
     graphical tools where you have to click a checkbox saying "Do not show this
     again". But if that is too much trouble for you, why not use one of the al-
     ternatives instead?  See a list in: man parallel_alternatives.

     As  the  request for citation is not a legal requirement this is acceptable
     under GPLv3 and cleared with Richard M. Stallman himself. Thus it does  not
     fall  under this: https://www.gnu.org/licenses/gpl-faq.en.html#RequireCita-
     tion

Ideas for new design
   Multiple processes working together
     Open3 is slow. Printing is slow. It would be good if they did  not  tie  up
     resources, but were run in separate threads.

   --rrs on remote using a perl wrapper
     ... | perl -pe '$/=$recend$recstart;BEGIN{ if(substr($_) eq $recstart) sub-
     str($_)="" } eof and substr($_) eq $recend) substr($_)=""

     It  ought	to be possible to write a filter that removed rec sep on the fly
     instead of inside GNU parallel. This could then use more cpus.

     Will that require 2x record size memory?

     Will that require 2x block size memory?

Historical decisions
     These decisions were relevant for earlier versions of GNU parallel, but not
     the current version. They are kept here as historical record.

   --tollef
     You can read about the history of GNU parallel on https://www.gnu.org/soft-
     ware/parallel/history.html

     --tollef was included to make GNU parallel switch compatible with the  par-
     allel  from  moreutils (which is made by Tollef Fog Heen). This was done so
     that users of that parallel easily could port their use  to  GNU  parallel:
     Simply set PARALLEL="--tollef" and that would be it.

     But several distributions chose to make --tollef global (by putting it into
     /etc/parallel/config)  without  making  the  users  aware of this, and that
     caused much confusion when people tried out the examples  from  GNU  paral-
     lel's man page and these did not work.  The users became frustrated because
     the  distribution	did  not make it clear to them that it has made --tollef
     global.

     So to lessen the frustration and the resulting support, --tollef was  obso-
     leted 20130222 and removed one year later.

20260122			   2026-01-31		      PARALLEL_DESIGN(7)

Want to link to this manual page? Use this URL:
<https://man.freebsd.org/cgi/man.cgi?query=parallel_design&sektion=7&manpath=FreeBSD+Ports+15.1.quarterly>

home | help