Wie ich hier kurz erwähnte, will ich mich heute ins Ruby-Ghetto wagen. Es geht darum C in Ruby einzubinden.
Ich würde Euch gerne sagen, dass Ruby der Ursprung war. Dass es nix davor gab und dass es nix danach geben wird. Aber leider ist dies weit von der Wahrheit entfernt. Denn so wie jede Ballkönigin eine kleine dicke Freundin hat, so hat auch Ruby ein kleines hässliches Anhängsel. Es ist wirklich so klein und hässlich, dass man schon bei der Namenssuche nach dem ersten Buchstaben verzweifelte, aufgab und es heute nur noch 'C' nennt. Man weiß nicht genau wozu dieses 'C' da sein soll. Einige behaupten es wäre eine Programmiersprache - aber eine Programmiersprache sollte unser Leben einfacher gestalten. Andere behaupten es wäre die erste Waffe im Informationskrieg gewesen um die Russen zu verwirren - dies wiederum würde einiges erklären.
Trotzdem ist es möglich C in Ruby zu nutzen. Der normale Workflow sieht hierbei wie folgt aus:
makeEine kaum beachtete Ruby-Bibliothek namens CodeRay hat sich in den letzten Wochen zu einer meiner lieblings Codequellen entwickelt. Bei CodeRay handelt es sich um einen Highlighter der u.a. Ruby und C highlighten kann. Es soll nicht unerwähnt bleiben, dass es sich hierbei um eine Schöpfung des Minenarbeiters murphy handelt.
Zu Beginn möchte ich mir eine geeignete Stelle für meine C-Erweiterung raussuchen. Hierfür lass ich den Profiler ein wenig schuften:
require 'coderayOptimized' require 'profile' CodeRay.scan_file("test_data/rb_source_coderay.rb").html.div
Nach einem kurzen oder auch langen Augenblick bekommen wir eine nette Tabelle die uns zeigt, dass sich CodeRay hauptsächlich in den Methoden Ruby#scan_tokens und HTML#token aufhält. Ein Blick in die Methode HTML#token zeigt mir einen interessanten gekapselten Part:
if text =~ /#{HTML_ESCAPE_PATTERN}/o text = text.gsub(/#{HTML_ESCAPE_PATTERN}/o) { |m| @HTML_ESCAPE[m] } end
In dem Hash @HTML_ESCAPE befinden sich Paare wie '&' => '&'. Oder um es kurz zu sagen, dieser Abschnitt sorgt dafür HTML zu entwerten. Ich denke es sollte ein leichtes sein, eine naive Implementierung dieser Funktionalität auch in C nachzubauen.
Gesagt getan. Ich erschaffe in meiner C-Datei 'new_escape.c' eine Funktion void Init_new_escape() und static VALUE escape_html_from_c(VALUE self, VALUE string). Dabei handelt es sich einmal um die Init Methode der Erweiterung (Init_new_escape). Die zweite Methode (escape_html_from_c) soll das HTML entwerten übernehmen. Das definieren der Methode ist hierbei der leichteste Vorgang. Ich erzeuge eine Variable vom Typ VALUE mit dem Namen modExtension. Diese Variable stellt das Ruby-Modul da, welches ich später einbinden werde. Hierzu muss ich die C-Funktion rb_define_module aufrufen.
modExtension = rb_define_module("BovisExtension");
Hiermit erzeuge ich ein Modul mit dem Namen "BovisExtension". Dieses Modul wird in der Variablen modExtension gehalten. Um nun meine Methode escape_html_from_c diesem Modul zuzuordnen, rufe ich die C-Funktion rb_define_module_function auf.
rb_define_module_function(modExtension, "escape_html_from_c", escape_html_from_c, 1);
Damit inkludiere ich die Methode escape_html_from_c in das Modul modExtension. Der Ruby-Methodenname wird hierbei als zweiter Parameter übergeben. Dieser kann sich logischerweise auch von dem C-Funktionsnamen unterscheiden. Zum Schluß folgt die Anzahl der Parameter die dieser Methode übergeben werden.
Um das ganze nun zu übersetzen, erstellen wir ein Ruby-Skript namens extconf.rb:
require 'mkmf' create_makefile('new_escape')
Dieses Skript erstellt ein Makefile für unser System. Nun können wir mithilfe von make die Übersetzung starten. Schlußendlich haben wir eine C-Bibliothek die unter Mac OS X beispielsweise die Endung '.bundle' besitzt. Dies ist jedoch abhängig von Eurer Umgebung. Zu beachten ist hierbei, dass Ihr eine auf dem Mac erzeugte Lib nicht unter Windows nutzen könnte. Wenn Ihr in einer Windowsumgebung arbeiten müsst, solltet Ihr einen dort vorhandenen C-Compiler verwenden.
require 'new_escape' include BovisExtension escape_html_from_c(TEST_STRING)
Das ist alles. Ihr könnt natürlich auch das ganze in einer eigenen Klasse inkludieren. Das Modul 'BovisExtension' arbeitet und reagiert wie ein ganz gewöhnliches in Ruby geschriebenes Modul, folglich könnt Ihr es auch erweitern.
Also neben Kopfschmerzen und Angstzuständen, bringt die Erweiterung über 'C' auch einen theoretischen Geschwindigkeitsvorteil. Ich sage theoretisch, weil man es ohne Mühe schaffen kann ineffizenten Code zu schreiben der deutlich langsamer ist. Meine tolle naive Erweiterung für CodeRay brachte z.B. eine Performancesteigerung von rechnerisch 3%. Bei kurzen Tokens ist der Geschwindigkeitsvorteil höher, bei längeren wiederum passiert es, dass Ruby deutlich performanter arbeitet. Hierbei sollte man jedoch beachten das ich kein C-Programmierer bin und die Funktionen nur auf "Gut Glück" geschrieben habe. Jemand der Ahnung von der Materie hat, wird sicherlich deutliche Performancesteigerungen erzielen. Ich würde mich freuen wenn jemand einen effizenteren Code hier posten würde. Murphy würde sich bestimmt freuen, wenn er am Sonntag nach dem Campen einen CodeRay Booster von uns präsentiert bekommt.
Resultat: C-Extension (new_escape)
Kommentar schreiben
Kommentare
Zumindest hast Du gezeigt, dass reguläre Ausdrücke in Ruby sehr effizient verarbeitet werden :), sonst hättest Du einen deutlicheren Laufzeitvorteil erzielt.
ich habs jetzt nochmal probiert, und ganze 10% für CodeRay Ruby nach HTML rausgehauen. die funktion selber ist ca. 4 mal so schnell, also über 75% gewinn. C-erweiterungen rocken wirklich.
Eine optimierte Version gibt es nun von Murphy. Siehe Beitrag.