Wednesday, August 22, 2012

bc threading

bash allows integer arithmetic, but no floating-point arithmetic. It is not uncommon to want to perform floating-point arithmetic on quick 'n dirty command-line scripts. I needed this a few days ago, and I found the handy bc command, which evaluates arbitrary-precision arithmetic and returns it as a neat shell string:

http://www.linuxquestions.org/questions/programming-9/decimal-numbers-in-bash-script-variables-380924/

Later on, I realized that the scripts that included the bc command were taking just as long or longer to run on a few hundred lines of text than a speech-recognition engine took to decode a half-hour audio file (yes, that's a lot. My script was sloooooooow).

I realized then that bc is a full-on interpreter, and that the overhead of starting it up and shutting it down multiple times per text line was awfully time-consuming.

So I googled around a little bit more, and I found a pretty neat way to deal with this problem: Open up bc on a separate process, and communicate with it through FIFOs! It's not straightforward, but it's certainly ingenious (and oh-just-ALMOST-so-very-clean). Most importantly, it speeds up my process a THOUSAND-FOLD!! Or by a lot, anyway.

It's not a one-line procedure - it requires preemptive initialization and some clean-up afterwards. Basically,
  1. Create two temporary FIFO files, one for input, one for output.
  2. Assign numbered streams to the FIFOs.
  3. Start up a background bc process that inputs from one stream, and outputs to the other. Perform the following two steps as many times as required:
  4. Send bc commands to the input stream.
  5. Read the bc result from the output stream.
  6. When you're done playing with bc, send it a "quit" command.
  7. Close both streams.
And you're done!! It'll be soo much faster! Smart guy who wrote this for all of us to usefully utilize in such situations:

Thanks dude!

As he mentions, this nifty little trick can be used with any other interpreters, like perl, php, or python.

For future reference and to avoid future link breakage, I'm copy-pasting his code here:

#!/bin/bash
##
# Make some fifos we can connect to.
##
mkfifo /tmp/bc.in
mkfifo /tmp/bc.out
##
# Attach file descriptors to those fifos.
##
exec 7<>/tmp/bc.in
exec 8<>/tmp/bc.out

##
# Start a single bc process in background and connect our fds to
# it's stdin, stdout, stderr we can use to do all of our math. We can
# do this because bc is an interpreter, with it's own shell.  Using 
# these same methods we can do inline perl/php/python, etc in 
# bash.
##
bc 0<&7 1>&8 2>&8 &

while read numerator denominator
do
  echo "scale=5; $numerator / $denominator" >&7
  read quotient <&8
  # bc errors have ':' in them somewhere.
  if [ "$quotient" != "${quotient//:/}" ; then
      echo "Error: $quotient"
  else
      echo "Answer of $numerator / $denominator is $quotient."
  fi
done

##
# Shutdown our bc process
##
echo "quit" >&7
##
# Close down our file descriptors.
##
exec 7>&-
exec 7<&-
exec 8>&-
exec 8<&-

No comments:

Post a Comment