Boy, oh boy, we have a fun one today. It's my first time benchmarking languages against eachother. I was inspired to do this because, recently, I had to make a choice of using python or golang for a backend API. Obviously, performance was one of the main factors we were judging in our designs.
In all, I want to see how long some simple operation takes across a few languages.
Today, we will be comparing a few languages:
Please note that all of these languages have vastly different pros and cons. Obviously, some languages handle different scenarios more elegantly than others! So, with that being said, take this silly performance test with a grain of salt!
What are the tests today? Oh, it's a very simple one. Each language is going to just count to 1,000,000,000 and do nothing else. To keep the tests comparable across the languages, we will not print anything to STDOUT and we will try to avoid any optimizations in the code itself. In pseudocode, all we are going to do is the below:
while counter < some_max_counter; do
counter++
done
We will run the tests 10 times and then chart the averages of each language against eachother.
I don't want to bore the reader by going through extremely simple code in 4 languages, but let's just take the C++ one as an example:
int main(int argc, char* argv[])
{
int counter = 0;
int max_counter = 1000000000;
while (counter < max_counter) {
counter++;
}
return 0;
}
We can see we are literally just counting up to a billion and doing nothing else. Each language will do the exaclty the same thing so we can try to get the most control over our scenarios.
Let's build a simple helper script that will run all of the languages for us. We will first need a function to run the commands and time.
time_language() {
iters=5
lang="$1"
file="$2"
cmd=""
case $lang in
cpp)
cmd="$file"
;;
golang)
cmd="$file"
;;
python)
cmd="python $file"
;;
rust)
cmd="$file"
;;
*)
cmd=""
;;
esac
echo $lang >> $RESULTS_FILE
i=1
sp="/-\|"
echo -n ' '
for i in $(seq 1 $iters); do
printf "\b${sp:i++%${#sp}:1}"
/usr/bin/time -o $RESULTS_FILE -a $cmd
done
}
It's pretty simple, but we're just going run the unix time
command
and send it over to a file using both the -o
and -a
flags. To get somewhat
of an average, we will run it 5 times each. Then we just need
to actually compile the code (if needed) and run the binaries:
printf "${CYAN}Running CPP Tests${NC}\n"
cd $_PWD/cpp
g++ -o counter *.cpp
time_language "cpp" "./counter"
cd $_PWD
printf "\n${CYAN}Running GoLang Tests${NC}\n"
cd $_PWD/golang
go build
time_language "golang" "./pointless-performance-tests"
cd $_PWD
printf "\n${CYAN}Running Python Tests${NC}\n"
cd $_PWD/python
time_language "python" "./main.py"
cd $_PWD
printf "\n${CYAN}Running Rust Tests${NC}\n"
cd $_PWD/rust
cargo build
time_language "rust" "./target/debug/counting"
cd $_PWD
And that's all there is to it. We will compile/build C++, then golang, then python, then rust. Our resulting file (on my computer) looks something like this:
cpp
0.73 real 0.63 user 0.00 sys
0.63 real 0.62 user 0.00 sys
0.63 real 0.62 user 0.00 sys
0.63 real 0.62 user 0.00 sys
0.62 real 0.62 user 0.00 sys
golang
0.36 real 0.31 user 0.00 sys
0.31 real 0.31 user 0.00 sys
0.31 real 0.31 user 0.00 sys
0.31 real 0.31 user 0.00 sys
0.31 real 0.31 user 0.00 sys
python
48.28 real 47.94 user 0.15 sys
46.79 real 46.53 user 0.07 sys
58.53 real 58.27 user 0.09 sys
54.66 real 54.34 user 0.14 sys
47.64 real 47.38 user 0.07 sys
rust
1.08 real 0.95 user 0.00 sys
0.94 real 0.93 user 0.00 sys
0.94 real 0.93 user 0.00 sys
0.95 real 0.94 user 0.00 sys
0.94 real 0.93 user 0.00 sys
I was pretty blown away by the results. Now, I want to be clear that I did not do any resource profiling and I was strictly looking at the real time to see how long each iteration took. I was extremely surprised by the time in which it took python to count all the way up to a billion, while the other languages took less than a second in most cases.
Thanks for following along! The full source code can be found