Wednesday, November 6, 2013

Local testing for executable overhead

The other day a friend of mine and I were discussing different types of overhead involved with different programming languages, and I used some simple comparisons to explain that compiled languages have lower overhead than interpreted languages. While it does not directly correlate to ram or processor usage (this can vary on a developer's code), it can give you a general idea of the overall efficiency of any specific language's implementation. We'll be comparing the disk usage and running time of a simple program, exit(0), written in a variety of languages.

Assembly

This is a very basic implementation of exit() using a linux system call.

.section .data
.section .text
.globl _start
_start:
    xor %rdi, %rdi
    push $0x3c
    popq %rax
    syscall

I saved the file as exit.s and assembled/linked it with the following commands:

$ as exit.s -o exit.o
$ ld exit.o -o exit

C

This is a very quick version of exit.c:

#include <stdio.h>
#include <stdlib.h>

int main (int *argc, char** argv) {
    return(0);
} 

I compiled this using the following:

$ gcc exit.c -o exit-c

Perl

Exit.pl is only 2 lines in length:

#!/usr/bin/perl
exit(0);

I compiled this using par packer (pp):

$ pp exit.pl -o exit-pl

Simple comparisons

Disk usage reveals:

$ du -sh exit exit-c exit-pl
4.0K exit
12K exit-c
2.4M exit-pl

That test includes slack space in its results. Lets find out what the actual byte counts of these files are, shall we?

$ wc -c exit exit-c exit-pl
    664 exit
   8326 exit-c
2474525 exit-pl
2483515 total

A timing test will show us:

$ time ./exit

real 0m0.001s
user 0m0.000s
sys  0m0.000s

$ time ./exit-c

real 0m0.002s
user 0m0.000s
sys  0m0.000s

$ time ./exit-pl

real 0m0.187s
user 0m0.100s
sys  0m0.020s

Interpreters

While the perl example is packed using par packer it might not be a fair comparison for a script. We can time that, along with ruby, python, and php while being interpreted by their interpreters:

$ time perl -e 'exit(0);'

real 0m0.005s
user 0m0.000s
sys 0m0.004s

$ time ruby -e 'exit'

real 0m0.008s
user 0m0.004s
sys 0m0.004s

$ time python -c 'exit'

real 0m0.024s
user 0m0.016s
sys 0m0.008s

$ time php -r 'exit(0);'

real 0m0.017s
user 0m0.008s
sys 0m0.008s

These timing tests can be used as a base indicator for the general performance of a given language, with assembly in the lead and C not far behind, its trivial to see that truly compiled or assembled languages are in fact faster than interpreters. These aren't perfectly fair comparisons because of several reasons:

  • Unused compiler/interpreter functionality overhead is included regardless of whether or not we use it in our code
  • Other processes running on the test system may cause things like timing to be unreliable (real cycle counting is much more reliable)
  • Actual CPU/Ram usage was never measured

Regardless of the fact that it isn't perfect, it should give you some idea of the difference in overhead/performance between the given interpreters on the test system, and certainly shows that in general, compiled/assembled languages run more quickly than interpreted languages. Of course, the performance of any application is partly to its programming; so while this may give you an idea of the language performance, it won't tell you how well any particular application written in a given programming language is going to run.

No comments:

Post a Comment