Performance von C und Java bei Genetischen Algorithmen
: Die Messungen wurden damals mit Java 1.4 und der Client-VM durchgeführt. Mit Version 1.5 und der Server-VM sollte der Unterschied deutlich geringer Ausfallen. Ich bin allerdings noch nicht dazu gekommen erneut zu messen.
Ein genetischer Algorithmus ist eine Metaheuristik die zur Lösung von NP-schweren Optimierungsproblemen eingesetzt werden kann.
Bekannte Probleme zu deren Lösung genetische Algorithmen eingesetzt
wurden sind das Resource Constrained Project Scheduling Problem (RCPSP)
,
das Travelling Salesman Problem (TSP)
und das Vehicle Routing
Problem (VRP)
.
Da die Laufzeit des Algorithmus bis zum Finden einer guten Lösung mitunter sehr lang sein kann spielt die Performance einer Implementierung eine große Rolle. Im folgenden wird daher der Performanceunterschied zwischen einer Implementierung in C und einer äquivalenten Implementierung in Java untersucht.
Der hier vorgestellte genetische Algorithmus arbeitet auf einer Permutation einer Menge von n Zahlen zwischen 0 und n. Diese Kodierung bezeichnet im Falle des RCPSP die Reihenfolge in der Aktivitäten nach der seriellen oder parallelen Methode eingeplant werden sollen.
Bei der Mutation werden zwei aufeinanderfolgende Elemente mit einer Wahrscheinlichkeit von 0,1 miteinander vertauscht. Die Rekombination wählt zunächst einen Zufallswert zwischen 0 und n. Alle Elemente bis zu diesem Index werden aus dem Vater-Individuum übernommen. Danach werden alle Elemente des Mutter-Individuums durchlaufen, wenn diese noch nicht im Nachfolger vorhanden sind werden sie an diesen angehängt. Der Nachfolger hat dann die gleiche Länge wie seine Eltern-Individuen.
Der verwendete Quellkode stammt aus einem Projekt an der
Berufsakademie Stuttgart und wurde im Rahmen der Vorlesung
SE-Projekt
ursprünglich in Java eintwickelt. Um umfangreichere
Untersuchungen über den Einfluß von Parametern zu machen war das
Programm zu langsam und wurde daher in C neu geschrieben.
Da während des Neuschreibens weitere Teile des Programms geändert und optimiert wurden konnte nicht genau nachvollzogen werden welchen Anteil der C-Compiler an der Geschwindigkeitssteigerung hatte. Dieser Vergleich soll nun an einer reduzierten Version des Quellkodes nachgeholt werden.
Der hier verwendete Java-Quellkode orientiert sich stark an der
C-Version, das Anlegen neuer Objekte in der
cross
-Methode wird im Gegensatz zur ursprünglichen
Version vermieden.
Ergebnisse
Die Ergebnisse wurden auf einem Compaq Evo N610c mit 1,8 GHz Pentium IV erzielt. Die Startzeiten der Java VM wurden dabei mit gemessen, diese haben bei längeren Individuen aber kaum Einfluß auf die Laufzeit
Größe der Individuen | Zeit C (ms) | Zeit Java (ms) | Verhältnis |
---|---|---|---|
50 | 1207 | 8019 | 6,64 |
100 | 2464 | 15168 | 6,16 |
150 | 3540 | 22527 | 6,36 |
200 | 4708 | 29712 | 6,31 |
250 | 5974 | 37928 | 6,20 |
300 | 6918 | 44026 | 6,36 |
test.c
Compilieren mit gcc test.c -o test.exe -std=c99 -O3 -march=pentium4
.
#include <stdio.h> #include <stdlib.h> #include <string.h> /*#include <assert.h>*/ #define ITERATIONS 100000 #define MAX_SIZE 200 #define BITMAP_SIZE ((MAX_SIZE / 32)+1) #define RANDOM_DOUBLE() ((double)rand()/(RAND_MAX+1)) #define RANDOM_INT(x) ((int)x*(double)rand()/(RAND_MAX+1)) typedef unsigned int uint; typedef unsigned char uchar; typedef struct List { uint size; uint data[MAX_SIZE]; uint bitmap[BITMAP_SIZE]; } List; void List_init(List *this) { memset(this,0,sizeof(List)); } int List_contains(List *this,uint i) { return 0 != (this->bitmap[i >> 5] & ((uint)1 << (i & 31))); } void List_set(List *this,uint i) { /*assert(!List_contains(this,i));*/ this->data[this->size++] = i; this->bitmap[i >> 5] |= ((uint)1 << (i & 31)); } void List_remove(List *this,uint i) { /*assert(List_contains(this,i));*/ int j = 0; while (j < this->size && this->data[j] != i) { j++; } if (j < this->size) { for (int k=j;k<this->size;k++) this->data[k] = this->data[k+1]; this->size--; this->bitmap[i >> 5] &= ~((uint)1 << (i & 31)); } } int List_empty(List *this) { return this->size == 0; } void List_shuffle(List *this) { List_init(this); for (int i=0;i<MAX_SIZE;i++) { List_set(this,i); } for (int i=0;i<MAX_SIZE;i++) { int j = RANDOM_INT(MAX_SIZE); int tmp = this->data[i]; this->data[i] = this->data[j]; this->data[j] = tmp; } } void List_mutate(List *this) { for (int i=0;i<MAX_SIZE-1;i++) { if (RANDOM_DOUBLE() < 0.1) { int tmp = this->data[i]; this->data[i] = this->data[i+1]; this->data[i+1] = tmp; } } } void cross(List *l1, List *l2, List *out) { List_init(out); int idx = RANDOM_INT(MAX_SIZE); for (int i=0;i<idx;i++) { List_set(out,l1->data[i]); } for (int i=0;i<MAX_SIZE;i++) { if (!List_contains(out,l2->data[i])) List_set(out,l2->data[i]); } List_mutate(out); } int main(int argc, char *argv[]) { List list1,list2,list3; for (int i=0;i<ITERATIONS;i++) { /*printf("%d\n",i);*/ /*List_init(&list1);*/ List_shuffle(&list1); /*List_init(&list2);*/ List_shuffle(&list2); cross(&list1,&list2,&list3); } return 0; }
test.java
Compilieren mit javac test.java
.
import java.io.*; import java.util.*; class MyList { static final int MAX_SIZE = 200; static final int BITMAP_SIZE = ((MAX_SIZE / 32)+1); private int size; private int[] data; private int[] bitmap; public MyList() { this.size = 0; this.data = new int[MAX_SIZE]; this.bitmap = new int[BITMAP_SIZE]; this.init(); } void init() { this.size = 0; java.util.Arrays.fill(this.data ,0); java.util.Arrays.fill(this.bitmap,0); } boolean contains(int i) { return 0 != (this.bitmap[i >> 5] & ((int)1 << (i & 31))); } void set(int i) { this.data[this.size++] = i; this.bitmap[i >> 5] |= (1 << (i & 31)); } int get(int i) { return this.data[i]; } void remove(int i) { int j = 0; while (j < this.size && this.data[j] != i) { j++; } if (j < this.size) { for (int k=j;k<this.size;k++) this.data[k] = this.data[k+1]; this.size--; this.bitmap[i >> 5] &= ~(1 << (i & 31)); } } boolean empty() { return this.size == 0; } void shuffle() { this.init(); for (int i=0;i<MAX_SIZE;i++) { set(i); } for (int i=0;i<MAX_SIZE;i++) { int j = (int)(MAX_SIZE*Math.random()); int tmp = this.data[i]; this.data[i] = this.data[j]; this.data[j] = tmp; } } void mutate() { for (int i=0;i<MAX_SIZE-1;i++) { if (Math.random() < 0.1) { int tmp = this.data[i]; this.data[i] = this.data[i+1]; this.data[i+1] = tmp; } } } void cross(MyList l1,MyList l2) { this.init(); int idx = (int)(MAX_SIZE*Math.random()); for (int i=0;i<idx;i++) { set(l1.get(i)); } for (int i=0;i<MAX_SIZE;i++) { int x = l2.get(i); if (!contains(x)) set(x); } mutate(); } } public class test { final static int ITERATIONS = 100000; public static void main(String[] args) { MyList list1 = new MyList(); MyList list2 = new MyList(); MyList list3 = new MyList(); for (int i=0;i<ITERATIONS;i++) { list1.shuffle(); list2.shuffle(); list3.cross(list1,list2); } } }
bench.c
Compilieren mit gcc bench.c -o bench.exe
.
#include <stdio.h> #include <stdlib.h> #include <windows.h> int main(int argc, char *argv[]) { LARGE_INTEGER Timestamp,Frequency; unsigned int t1,t2; /*double delta;*/ double c_time,java_time,ratio; // fits into LowPart on my Pentium IV 1.8 GHz QueryPerformanceFrequency(&Frequency); /*printf("%ld, %ld\n",Timestamp.HighPart,Timestamp.LowPart);*/ /*printf("%ld, %ld\n",Frequency.HighPart,Frequency.LowPart);*/ printf("c\n"); QueryPerformanceCounter(&Timestamp); t1 = Timestamp.LowPart; system("test.exe"); QueryPerformanceCounter(&Timestamp); t2 = Timestamp.LowPart; c_time = (double)1000.0/Frequency.LowPart*(t2 - t1); printf("%12.2f\n",c_time); printf("java\n"); QueryPerformanceCounter(&Timestamp); t1 = Timestamp.LowPart; system("java test"); QueryPerformanceCounter(&Timestamp); t2 = Timestamp.LowPart; java_time = (double)1000.0/Frequency.LowPart*(t2 - t1); printf("%12.2f\n",java_time); printf("c / java: %.2f\n",c_time/java_time); printf("java / c: %.2f\n",java_time/c_time); return 0; }