Frage zu konvertierung von ctype in Python Typ

Python in C/C++ embedden, C-Module, ctypes, Cython, SWIG, SIP etc sind hier richtig.
snow
User
Beiträge: 25
Registriert: Mittwoch 4. Juli 2012, 08:52

Hallo,
ich habe ein Funktion in C geschrieben, die aus einem string bestimmte Wörter, welche mit einem $ oder @ beginnen, in einem array ausgibt. Das habe ich dann in eine Shared Library gebuildet. Die Signatur der Funktion ist die Folgende:

Code: Alles auswählen

char **tokenize(char* text);
Nun will ich diese Funktion in meinem Python Programm benutzen und habe das wie folgt versucht:

Code: Alles auswählen

from ctypes import CDLL, c_char_p, POINTER

lib = CDLL("./lib.so")

lib.tokenize.argtypes = [c_char_p]
lib.tokenize.restype = POINTER(c_char_p)

print "1"
print "2"
result = set([])
ret = lib.tokenize(c_char_p("$hallo wer @ist da"))
print ret
for word in ret:
    print(word)
    result.update([word])
Die Ausgabe ist die Folgende:

Code: Alles auswählen

1
2
<__main__.LP_c_char_p object at 0x7f479d6615f0>
$hallo
@ist
None
[Finished in 0.4s with exit code -11]
Zwar gibt er anfangs aus, was ich möchte, jedoch folgt anschließend ein None, was nicht dazugehört und zudem beendet das Programm noch mit einem negativen exit code. Weiß jemand was ich falsch mache? Muss ich vorher noch eine Typkonvertierung durchführen? Weil anscheinend weiß er nicht wie weit er bei einem Pointer iterieren muss....

Vielen Dank schon einmal im voraus :)
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@snow: für Stringverarbeitung kennt die Standardlibrary doch schon genug Werkzeuge (z.B. hier: re). Warum also nochmal etwas selbst erfinden?
Zum Problem: woher soll ctypes die Länge des zurückgegebenen Arrays wissen? Das wird in c normalerweise mit einem NULL-Eintrag (Python -> None) gemacht. Alles darüber hinaus ist Schrott und führt im besten Fall zu einem Programmabbruch.
BlackJack

@snow: Woher wüsste denn ein C-Programm welches Deine Funktion verwendet wie viele Elemente das zurück gegebene Array hat?

Klammern bei ``print`` machen nur Sinn wenn das auch eine Funktion wäre. Wenn es, wie in Python 2 eine Anweisung ist, dann verwirren die Klammern im besten Fall nur.

Wenn man bei einem `set.update()` grundsätzlich eine Liste mit *einem* Element übergibt, die man extra für diesen Zweck auch noch erstellt, dann verwendet man eindeutig die falsche Methode.
snow
User
Beiträge: 25
Registriert: Mittwoch 4. Juli 2012, 08:52

Sirius3 hat geschrieben:@snow: für Stringverarbeitung kennt die Standardlibrary doch schon genug Werkzeuge (z.B. hier: re). Warum also nochmal etwas selbst erfinden?
Zum Problem: woher soll ctypes die Länge des zurückgegebenen Arrays wissen? Das wird in c normalerweise mit einem NULL-Eintrag (Python -> None) gemacht. Alles darüber hinaus ist Schrott und führt im besten Fall zu einem Programmabbruch.
Das Problem ist, dass mir re zu langsam ist und ich deswegen was eigenes in c programmiert habe um eine performantere Lösung zu haben.
Der None Eintrag ist bei mir ja an letzter Stelle, bevor der exit Fehler kommt. Muss ich dann drüber iterieren bis ich None erreiche und solange Elemente in meine Python Datenstruktur kopieren oder gibt es da eine elegantere Lösung? :)
BlackJack hat geschrieben:@snow: Woher wüsste denn ein C-Programm welches Deine Funktion verwendet wie viele Elemente das zurück gegebene Array hat?

Klammern bei ``print`` machen nur Sinn wenn das auch eine Funktion wäre. Wenn es, wie in Python 2 eine Anweisung ist, dann verwirren die Klammern im besten Fall nur.

Wenn man bei einem `set.update()` grundsätzlich eine Liste mit *einem* Element übergibt, die man extra für diesen Zweck auch noch erstellt, dann verwendet man eindeutig die falsche Methode.
Das weiß es nicht. Da die Funktion ständig ein Qt Textfeld prüft um Vorschläge für eine Autocompletion bereit zu stellen ist die Länge der Rückgabe auch oft sehr unterschiedlich.

Danke für den Tipp wegen print, werde versuchen in Zukunft drauf zu achten ;)

Das mit dem Set ist noch ein Überbleibsel aus einer älteren Implementierung, als ich die Funktion in Python geschrieben habe und die Funktion die diese aufgerufen hat ein set erwartet, da sie anschließend ihre eigene Liste mit built-in Wörtern mit der Liste die sie bekommt vereinigt.
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@snow: so etwa:

Code: Alles auswählen

result = set(itertools.takewhile(bool, ret))
Jetzt verrat aber noch, was so zeitkritisch an Deinem Problem ist, dass sich dieser Aufwand lohnt? Und wieviel Du dadurch gewinnst:

Code: Alles auswählen

result = set(re.finditer('[$@]\w+', string)
BlackJack

@snow: Wer immer diese Funktion aufruft *muss* wissen und irgendwie feststellen können wie viele Elemente in dem Array vorhanden sind. Auch ein C-Programm muss doch wissen wann es aufhören muss Zeichenketten aus dem Array zu lesen damit es nicht auf Speicher zugreift der dann zu einem Absturz führt. Das Problem in einem C-Programm hätte man genau so wie in Deiner Pythonanbindung mit `ctypes`.

Falls das Ende mit einem Nullpointer gekennzeichnet ist, kann man einen Iterator mit ``iter(result, None)`` über die Einträge erstellen.
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@snow: mein Timing-Test mit einer fehlerhaften c-Funktion (weil statisches Array für die Rückgabe) ergab, dass das re-Modul dreimal schneller ist als meine C-Funktion . Natürlich ist das reine durchparsen des Strings ungefähr dreimal schneller in reinem C, das Erzeugen der Python-Strings ist dann aber der Flaschenhals.
Bevor Du also anfängst an irgendwelchen Stellen zu optimieren, schau erst einmal nach, wie viel der Zeitgewinn insgesamt ist. Ob ich jetzt 0.004 oder 0.007 Sekunden warten muss ist nicht spürbar, die um 2 Stunden längere Entwicklungszeit und die etlichen Stunden wegen schwerer Wartbarkeit dagegen schon.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@snow:
Wie oder wann gibst Du eigentlich den Speicher hinter den char*'s frei? :twisted:
snow
User
Beiträge: 25
Registriert: Mittwoch 4. Juli 2012, 08:52

Danke Sirius und Blackjack.

Die Problemstellung ist ein Teil meiner Bachelorarbeit, worin ich eine IDE programmiere.
Die Autocompletion soll diese Liste von Wörtern bekommen und das möglichst oft. Da ich einige Ideen habe, wie z.B. Scope basierte Autocompletion. Das heißt soviel wie: wo sich der Cursor derzeit befindet, in dessen Scope sollen auch nur Vorschläge gemacht werden. Zum Beispiel wenn sich der Cursor in einer for Schleife befindet, soll ich auch nur dort die Zählvariable als Vorschlag bekommen und nicht außerhalb.
Dafür muss natürlich in jeder Änderung des Cursors oder wenn ein Wort fertig geschrieben wurde oder gelöscht wurde etc. diese Funktion ausgeführt werden um immer die aktuellsten Vorschläge in der Autocompletion zu haben. Das kann natürlich teilweise dazu führen, dass mehre male pro Sekunde eine Datei durchstöbert werden muss, welche weit über 100 Zeilen hat.

Ich habe jetzt einmal deinen Python Vorschlag genommen:

Code: Alles auswählen

import re

string = open('test2.rs', 'r').read()
result = list(re.finditer('[$@]\w+', string))
und ihn mit einer Datei, welche aus 160 Zeilen Code besteht 500 mal (mithilfe von avgtime: https://github.com/jmcabo/avgtime) durchlaufen lassen.

Das ist das Ergebnis, was die Python RegEx Variante erzielt hat:

Code: Alles auswählen

Total time (ms): 9665.63
Repetitions    : 500
Sample mode    : 19 (207 ocurrences)
Median time    : 19.53
Avg time       : 19.3313
Std dev.       : 1.21324
Minimum        : 14.054
Maximum        : 23.495
95% conf.int.  : [16.9533, 21.7092]  e = 2.37792
99% conf.int.  : [16.2061, 22.4564]  e = 3.12511
EstimatedAvg95%: [19.2249, 19.4376]  e = 0.106344
EstimatedAvg99%: [19.1915, 19.471]  e = 0.139759
und mein C Code, welcher noch nicht sehr optimiert ist, da z.B. noch bei jedem neuen Wort neue Speicherplatz mit malloc frei geschoben wird, statt der Nutzung eines Speicherpools und ähnlicher Sachen:

Code: Alles auswählen

Total time (ms): 1481.85
Repetitions    : 500
Sample mode    : 2.6 (86 ocurrences)
Median time    : 2.7595
Avg time       : 2.9637
Std dev.       : 0.826546
Minimum        : 1.743
Maximum        : 7.716
95% conf.int.  : [1.3437, 4.5837]  e = 1.62
99% conf.int.  : [0.834658, 5.09274]  e = 2.12904
EstimatedAvg95%: [2.89125, 3.03615]  e = 0.0724486
EstimatedAvg99%: [2.86848, 3.05891]  e = 0.0952136
Meiner Meinung nach ist der Unterschied schon jetzt sehr deutlich.

Des Weiteren hab ich die volle Kontrolle über die Erweiterung und Anpassung dieser Funktion und muss mich nicht mit einem immer komplexer werdenden regex auseinandersetzen, welchen ich wahrscheinlich selber nicht mehr nach 1-2 Monaten verstehe :D
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

Autocompleter arbeiten normalerweise auf einem AST der entweder inkrementell oder in regelmäßigen Abständen neu erstellt wird. Dass das für jede Cursorbewegung gemacht wird, kommt mir seltsam und sehr ineffizient vor.
Das bestätigt mich in meiner Aussage, dass Du erstmal Dein Problem lösen mußt, um dann, wenn es zu langsam arbeitet nach Stellen zu suchen, die sich eignen optimiert zu werden (also das Ergebnis der Suche irgendwie zu speichern um nicht immer wieder neu suchen zu müssen).
snow
User
Beiträge: 25
Registriert: Mittwoch 4. Juli 2012, 08:52

Sirius3 hat geschrieben:Autocompleter arbeiten normalerweise auf einem AST der entweder inkrementell oder in regelmäßigen Abständen neu erstellt wird. Dass das für jede Cursorbewegung gemacht wird, kommt mir seltsam und sehr ineffizient vor.
Das bestätigt mich in meiner Aussage, dass Du erstmal Dein Problem lösen mußt, um dann, wenn es zu langsam arbeitet nach Stellen zu suchen, die sich eignen optimiert zu werden (also das Ergebnis der Suche irgendwie zu speichern um nicht immer wieder neu suchen zu müssen).
Das mit dem AST ist auf jeden Fall interessant, von dem habe ich bisher leider noch nichts gehört. Werd mir das auf jeden Fall mal anschauen.

Es geht ja nicht direkt um jede "Cursorbewegung" sondern darum, dass wenn der Textcursor in meinem QTextEdit Bereich an eine neue Stelle gesetzt wird, weil dann unter anderem ja die Wahrscheinlichkeit besteht, dass ich in einem neuen Scope bin. Ich kann da ja nicht einfach auf gut Glück sagen, dass der Benutzer nur bei jeder dritten Änderung der Cursorposition außerhalb eines bestimmten scopes ist. Natürlich werde ich da nochmal drüber nachdenken, was ich zwischenspeichern kann und was nicht. Zum Beispiel werden globale Variablen ja immer als Vorschläge da sein, bevor die Zeile nicht entfernt wird. Das ist vielleicht overkill wenn man diese Zeilen jedes mal auf neue durchläuft. Aber da muss ich mir noch was überlegen. Ich wollte es halt erstmal überhaupt zum laufen bekommen.

@jerch: ich versteh deine frage nicht ganz. Speicher für Char* muss nicht frei gegeben werden, solange man ihn nicht mit malloc anfordert.
Ich weiß zwar nicht ab welchem Standard VLA's gekommen sind. Diese sind aber eine gute Alternative zur malloc Variante, wenn die Länge des Char-Arrays erst zur Laufzeit fest steht.
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@snow: über Speicherverwaltung muß man in C immer nachdenken. Woher soll Deine Library denn wissen, daß der Speicher, der als Rückgabewert an Python übergeben wurde, nicht mehr gebraucht wird?
Welche Sprache soll die IDE unterstützen? Entweder hat man einen Editor, der nur nach Wörtern sucht und diese als Autocomplete-Vorschläge anbietet, da nimmt man am besten gleich noch die Anzahl der Vorkommen mit, damit man, wenn man Zeilen löscht auch Wörter aus der Liste löschen kann, die nur in dieser Zeile vorgekommen sind.
Wenn man aber die kontextsensitive Hilfe anbieten will, muß man die Syntax der Sprache kennen und diese läßt sich effektiv in einem Abstract-Syntax-Tree darstellen. Wird dann noch zusätzlich gespeichert, an welcher Position welcher Zweig sich befindet, läßt sich ganz einfach über den Cursor abfragen, in welcher for-Schleife man sich gerade befindet.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

snow hat geschrieben:@jerch: ich versteh deine frage nicht ganz. Speicher für Char* muss nicht frei gegeben werden, solange man ihn nicht mit malloc anfordert.
Ich weiß zwar nicht ab welchem Standard VLA's gekommen sind. Diese sind aber eine gute Alternative zur malloc Variante, wenn die Länge des Char-Arrays erst zur Laufzeit fest steht.
Punkt 1 ist korrekt. Die Frage ist - wie erstellst Du das String-Array? Da Du VLAs ansprichst - diese leben auf dem Stack und unterliegen daher dem Funktionskontext. Falls Du das VLA nicht vorher nochmal auf den Heap schiebst, gibst Du einen ungültigen Pointer zurück. Schiebst Du auf den Heap, musst Du den Speicher irgendwie wieder freigeben. Daher die Frage.
VLAs sind offiziell mit C99 spezifiziert, der gcc konnte die schon früher über die GNU-Erweiterungen. Als Alternative zu malloc/free taugen sie meiner Meinung nach nur bedingt (vorallem wegen des beschränkten Stacks und der Funktionsgrenze).

Edit: Da Du anscheinend auf Qt setzt - warum entwickelst Du die IDE nicht mit C++?
snow
User
Beiträge: 25
Registriert: Mittwoch 4. Juli 2012, 08:52

@Sirius Ja darüber hab ich auch nach gedacht. Aber dort muss es doch was geben, was sich darum kümmert oder nicht? Da ich ja auf C Seite nach dem return keine Anweisungen mehr geben kann, kann es auch nicht gefreed werden. Dachte eigentlich, dass sich danach python per gc darum kümmert das los zu werden, wenn keine Referenzen darauf mehr existieren. Sonst wäre der gesamte ctypes Kram doch ziemlicher Murks oder?

Die Sprache ist eine noch in der Entwicklungsphase befindliche Transcompilersprache, welche von einem Kommillitonen parallel entwickelt wird. Also Klassen und Polymorphie Konzepte geschweige denn Aufrufe aus Bibliotheken werden mit ziemlicher Wahrscheinlichkeit nicht möglich sein, weswegen ich mir da bisher auch noch keine Gedanken gemacht habe, wie ich diese in der Autocompletion realisiere.

@jerch Meinst du mit String Array die Struktur in der ich die char* speichere? Dafür habe ich eine eigene Set Implementierung gemacht(weil ich ja jedes Wort nur einmal brauche). Die Strings selber habe ich wie folgt aus dem Text geholt und abgespeichert:

Code: Alles auswählen

char cur[end - start + 1];
strncpy(cur, text+start, end - start);
cur[end-start] = '\0';
setInsert(s, cur);
In der Struktur werden sie mithilfe von malloc auf dem Heap gespeichert und das char** was am Ende an den Python Code geschickt wird, wüßte ich nicht wie das noch vom c code gefreed werden könnte. Ich dachte, wie oben gesagt, dass sich ab dem Punkt Python darum kümmert.

Ich hab mich für Python und PySide entschieden, weil ich damit wesentlich schneller die Oberfläche auf die Beine gestellt bekomme, als wenn ich das ganze in C++ getan hätte. Die rechenintensiven Sachen, wie die Autocompletion, wollte ich dann nach C auslagern.
BlackJack

@snow: Python kann sich nicht um den Speicher kümmern weil Python nicht weiss ob es den Speicher freigeben darf oder nicht. Woher soll denn der Python-Interpreter wissen ob es irgendwo auf der Seite der C-Bibliothek noch Referenzen auf die Speicherbereiche mit den Zeichenketten gibt oder nicht? `ctypes` ist deswegen kein Murks. Das wäre es eher wenn es einfach davon ausgehen würde der Speicher könnte freigegeben werden. Denn ein Speicherleck ist nicht so schlimm wie ein Zugriff auf Speicher der nicht mehr gültig ist, und das Speicherleck kann der Programmierer ja leicht selber verhindern in dem er den Speicher frei gibt. `ctypes` bietet eine Schnittstelle zu C-Bibliotheken und wenn man Pointer auf Datenstrukturen als Rückgabewerte bekommt, muss man sich die selben Gedanken um die Speicherverwaltung machen wie man das als C-Programmierer auch müsste.
snow
User
Beiträge: 25
Registriert: Mittwoch 4. Juli 2012, 08:52

Also muss ich dann mithilfe von ctypes den Speicher vom char array, welchen ich von der C Bibliothek bekomme, wieder frei machen, wenn ich diesen nicht mehr brauche?
BlackJack

@snow: Ja. Wobei man da üblicherweise in der Bibliothek eine Funktion für schreibt. Mit einem einfachen `free()` ist es ja nicht getan, man muss ja alle Zeichenketten in dem Array auch freigeben.
snow
User
Beiträge: 25
Registriert: Mittwoch 4. Juli 2012, 08:52

Ach ich Idiot. Ja die habe ich auch geschrieben. Keine Ahnung wieso ich da jetzt nicht dran gedacht habe die einfach aus Python heraus aufzurufen :D Danke dir :)
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Zwei Hinweise hab ich noch bezüglich der VLAs und Deinem kurzen Codebsp.:

Wenn Du das plattformübergreifend halten willst und später mit dem MS-C Compiler übersetzen musst, fliegen Dir die VLAs um die Ohren. Der MS-Compiler ist auf C89 hängengeblieben und Du musst dann alle VLAs mit _alloca umschreiben. Das ist sehr nervig beim Refactoring.

Da Dir die Geschwindigkeit so wichtig ist, warum übergibst Du nicht `text` mit Offset und Länge dem `setInsert`? Das char cur[] ist imho ein überflüssige Kopieraktion.
snow
User
Beiträge: 25
Registriert: Mittwoch 4. Juli 2012, 08:52

Ich arbeite gerade nur auf Linux, aber ich wollte es auf jeden Fall auch unter Windows weiterhin lauffähig halten. Hatte eigentlich vor mit gcc für windows die Bibliothek zu erzeugen. Wird das ebenfalls zu Problemen führen/ geht das garnicht/ oder gibt das keine Probleme?

Das andere werd ich mal ändern, danke.
Antworten