In my last post, I demonstrated how Psyco can be used to speed up Python code using the example of a brute-force password cracker. To see how the speed of the Python-Psyco combination compared to the speed of other languages I ported to code to Java, C++ and C. In each language I wrote the code as if it were a regular program written in that language (i.e. I didn’t try to optimize the code) - in C++ I used C++ STL Strings, in Java I used the StringBuilder class and in C I used char arrays.

The timings for the Python+Psyco code are as follows (see last post for full details):

Password | Time taken
zzzz     | 0m0.134s
zzzzz    | 0m3.486s
zzzzzz   | 2m8.056s

Here is the Java code (I’m not a Java programmer at all - I just know the basics so I apologise if the code is poorly written):

class BrutePass {
 
    static int count = 0;
 
    public static void main( String args[] ) {
 
        if( args.length < 1 ) {
            System.out.println( "Usage: java BrutePass <password>" );
            return;
        }
 
        String pwd = args[0];
        int maxlen = 10;
 
        StringBuilder p = new StringBuilder();
 
        for( int w=0; w<maxlen; w++ )
            if( crack( w+1, 0, p, pwd ) > 0 )
                break;
 
        System.out.println( "Found password " + p.toString()
             + " with " + count + " attempts." );
 
    }
 
    public static int crack( int w, int pos, StringBuilder p, 
        String pwd ) {
 
        String chars = "abcdefghijklmnopqrstuvwxyz";
 
        for( int i=0; i<chars.length(); i++ ) {
            p.append( chars.charAt( i ) );
 
            if( pos == w-1 ) {
                count++;
                if( pwd.compareTo( p.toString() ) == 0 )
                    return 1;
            }
 
            int ret = 0;
            if( pos < w-1 )
                ret = crack( w, pos+1, p, pwd );
 
            if( ret > 0 )
                return 1;
 
            p.delete( p.length() - 1, p.length() );
        }
        return 0;
    }
}

Here are the timings for the java program:

Password | Time taken
zzzz     | 0m0.176s
zzzzz    | 0m1.334s
zzzzzz   | 0m31.369s

Much faster than the Psyco+Python version. The reason that the four-character password appears to take longer to crack in Java than in Python is that the JVM takes a while to get going. Now for the C++ code:

#include <iostream>
#include <string>
 
using namespace std;
 
int crack( int w, int pos, string& p, string& pass );
 
int main( int argc, char **argv )
{
    if( argc < 2 )
    {
        cout << "Usage: " << argv[0] << " <password>" << endl;
        return( 0 );
    }
 
    int maxlen = 10;
    if( argc > 2 )
        maxlen = atoi( argv[2] );
 
    string pwd( argv[1] );
    string p;
 
    int count;
    for( int i=0; i<maxlen; i++)
        if( count = crack( i+1, 0, p, pwd ) )
            break;
 
    cout << "Found password " << p << " with " << count << " attempts." << endl;
 
    return 0;
}
 
int crack( int w, int pos, string& p, string& pass )
{
    static int count = 0;
    string chars( "abcdefghijklmnopqrstuvwxyz" );
 
    for( int i=0; i<chars.length(); i++)
    {
        p.push_back( chars[i] );
 
        if( pos == w-1 )
        {
            if( p == pass )
                return( count );
            count++;
        }
 
        int ret = 0;
        if( pos < w-1 )
            ret = crack( w, pos+1, p, pass );
 
        if( ret )
            return( count );
 
        p.erase( p.end()-1 );
    }
    return( 0 );
}

And the timings:

Password | Time taken
zzzz     | 0m0.036s
zzzzz    | 0m0.730s
zzzzzz   | 0m18.999s

I’d heard that Java is in many cases faster than C++; clearly this is not one of those cases. Now the C version:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
unsigned long crack( int w, int pos, char *p, char *pass );
 
int main( int argc, char **argv )
{
    int maxlen = 10, i;
    unsigned long count;
    char *pwd, *p;
    if( argc < 2 )
    {
        printf( "Usage: %s <password>\n", argv[0] );
        return( 1 );
    }
 
    if( argc > 2 )
        maxlen = atoi( argv[2] );
 
    pwd = argv[1];
    p = (char *)calloc( maxlen + 1, sizeof(char) );
 
    for( i=0; i<maxlen; i++)
        if( count = crack( i+1, 0, p, pwd ) )
            break;
 
    printf( "Found password %s with %lu attempts.\n", p, count );
 
    return 0;
}
 
unsigned long crack( int w, int pos, char *p, char *pass )
{
    static unsigned long count = 0;
    int i, ret;
    char *chars = "abcdefghijklmnopqrstuvwxyz";
 
    for( i=0; i<strlen(chars); i++)
    {
        p[strlen(p)] = chars[i];
 
        if( pos == w-1 )
        {
            count++;
            if( !strcmp( p, pass ) )
                return( count );
        }
 
        ret = 0;
        if( pos < w-1 )
            ret = crack( w, pos+1, p, pass );
 
        if( ret )
            return( count );
 
        p[strlen(p)-1] = 0;
    }    
    return( 0 );
}

And the timings:

Password | Time taken
zzzz     | 0m0.022s
zzzzz    | 0m0.381s
zzzzzz   | 0m9.663s

That’s pretty much twice the speed of the C++ version! This shows how much overhead there is for using STL Strings rather than C strings.

Although Psyco provided a fantastic speed boost, it has not eliminated the need to know C as some people have claimed - the native C version of the program performed 13.25x faster than the Psyco version.