Python vs bc in Evaluating 6^6^6

bcperformanceprocess-managementpython

I am evaluating the expression 6^6^6 using python and bc separately.

The content of the python file is print 6**6**6. When I execute time python test.py, I get the output as

real        0m0.067s
user        0m0.050s
sys         0m0.011s

And then, I ran the command time echo 6^6^6 | bc which gave me the following output

real        0m0.205s
user        0m0.197s
sys         0m0.005s

From these results it is clear that the sys time taken by python and bc was 11ms and 5ms respectively. The bc command outperformed python at sys time level but when it comes to user and real time python was almost 4 times faster than bc. What might have gone there. I haven't given any priority to the processes as such. I am trying to understand this situation.

Best Answer

Python imports a large number of files at startup:

% python -c 'import sys; print len(sys.modules)'
39

Each of these requires an even greater number of attempts at opening a Python file, because there are many ways to define a module:

% python -vv -c 'pass'
# installing zipimport hook
import zipimport # builtin
# installed zipimport hook
# trying site.so
# trying sitemodule.so
# trying site.py
# trying site.pyc
# trying /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site.so
# trying /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/sitemodule.so
# trying /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site.py
# /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site.pyc matches /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site.py
import site # precompiled from /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site.pyc
# trying os.so
# trying osmodule.so
# trying os.py
# trying os.pyc
# trying /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/os.so
# trying /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/osmodule.so
# trying /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/os.py
# /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/os.pyc matches /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/os.py
import os # precompiled from /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/os.pyc
    ...

Each "trying", except for those which are builtin, requires an os-level/system calls, and each "import" seems to trigger about 8 "trying" messages. (There was ways to reduce this using zipimport, and each path in your PYTHONPATH may require another call.)

This means there are almost 200 stat system calls before Python starts on my machine, and "time" assigns that to "sys" rather than "user", because the user program is waiting on the system to do things.

By comparison, and like terdon said, "bc" doesn't have that high of a startup cost. Looking at the dtruss output (I have a Mac; "strace" for a Linux-based OS), I see that bc doesn't make any open() or stat() system calls of its own, except for loading a few shared libraries are the start, which of course Python does as well. In addition, Python has more files to read, before it's ready to process anything.

Waiting for disk is slow.

You can get a sense for Python's startup cost by doing:

time python -c pass

It's 0.032s on my machine, while 'print 6**6**6' is 0.072s, so startup cost is 1/2rd of the overall time and the calculation + conversion to decimal is the other half. While:

time echo 1 | bc

takes 0.005s, and "6^6^6" takes 0.184s so bc's exponentiation is over 4x slower than Python's even though it's 7x faster to get started.