No. Process/context switches aren't free.
How much other processes running will slow yours down is very system-dependent, but it consists of things like:
Every time a processor switches to a different address space (including process), then the MMU cache must be flushed. And probably the processor L1 caches. And maybe the L2 and L3 caches. This will slow down memory access right after your process is resumed (and this counts against your "user" time).
On a SMP (or multi-core) box, if two processes are trying to access the same parts of (physical) RAM, the processors must cooperate. This takes time. It is often done at a architecture level, below even the OS. This will count against your user or sys time, depending on when it hit.
Quick kernel locking (that doesn't actually schedule another process) will count against your sys time. This is similar to the point above.
on NUMA boxes, you may get moved to a different node. Memory access may now be cross-domain, and thus slower
other processes may influence power management decisions. For example, pegging more cores will reduce Intel Turbo Boost speeds, to keep the processor package inside its electrical & thermal specs. Even without turbo boost, the processor may be slowed due to excess heat. It can work the other way too—more load may cause the CPU speed governor to increase the CPU speed.
There are other shared resources on a system; if waiting for them doesn't actually involve your process sleeping, then it'll get counted against your user or sys time.
By using strace
, I saw that the line
number=$(expr $number + 1)
causes a fork, path search, and exec of expr
. (I'm using bash 4.2.45 on Ubuntu). That filesystem, disk, and process overhead led to bash only getting around 28% of the CPU.
When I changed that line to use only shell builtin operations
((number = number + 1))
bash used around 98% of the CPU and the script ran in a half hour. This was on a single-CPU 1.5GHz Celeron.
The script as is doesn't do anything that runs in parallel, so having 32 free CPUs won't help much. However, you can certainly parallelize it by, for instance, splitting it into 10 1-million-iteration loops that run in parallel, writing to 10 different files, and then using cat
to combine them.
The following sample program was added by @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="fname.lname.$n@domain.com"
password="1234567890"
altemail="lname.fname.$n@domain.com"
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
Best Answer
In
time time
, both are the built-in from bash, none is the external/usr/bin/time
command.This is possible because the
time
built-in takes a pipeline as its arguments, buttime
itself is a pipeline, so multiple calls are possible.If you look at bash source code, you'll even find comments referring to this special case, looking for a call to
time
following anothertime
ortime -p
.You only see the output once because
time
is implemented by setting a bit flag, so calling it more than once has no effect since it's just setting the same bit on that pipeline...This, however, calls it in two separate pipelines, so you see the output twice:
You can see results using the external
/usr/bin/time
by calling it explicitly with the path... Or using\time
(the leading\
prevents the shell from using a built-in) or using thecommand
built-in (as intime command time
), for example:As you see, the external
/usr/bin/time
complains when it's called with no arguments... So that's what you're seeing there. (Also, if you use it on an actual command, you'll notice the output format is different from that of the bash built-in.)