Zur Navigation

Dauerschleifen finden

1 Ranma (Gast)

Hallo, ich bin es schon wieder!

Ich bin immernoch dabei, Listen für Wörterbücher in eine Datenbank zu übertragen. Nun haben sich gleich zwei meiner Skripte in eine Dauerschleife gehängt und ich kann den Grund nicht finden.

Gibt es so etwas wie technische Hilfsmittel, um Dauerschleifen aufzuspüren? Vielleicht PHP-Anweisungen, die ich in Schleifen einfügen kann, um sie zu überwachen?

Falls ich das richtig sehe, dann kann eine Dauerschleife nur aus einer ungezählten Schleife entstehen? Also weder aus Zählschleifen, noch aus Konstrukten, die keine Schleifen sind?

Meine Skripte durchlaufen die Dateien mit den Wörterlisten inzwischen mittels WHILE-Schleifen, weil so größere Dateien verarbeitet werden können als mit file(); aber der Zeiger für Dateioperationen wird sich kaum selbst zurücksetzen?

Bleiben also eigentlich nur noch WHILE-Schleifen innerhalb der WHILE-Schleife. Da habe ich eine im Skript für die Chinesisch-Liste. In dieser WHILE-Schleife werden Informationen herausgezogen, die sich innerhalb von runden Klammern befinden. Erst machte das Skript Probleme, weil einige Klammern nicht geschlossen waren. Solche lassen sich leicht mit der Bedingung substr_count($Eintrag,'(')!=substr_count($Eintrag,')') aufspüren. Darunter waren einige mit dem fiesen Fehler, ASCII-Klammern mit Nicht-ASCII-Klammern zu paaren. Die würde man glatt übersehen, wenn man sie nicht mittels eines Skripts sucht. Aber inzwischen habe ich alle solchen Einträge korrigiert. Trotzdem lieferte mein Skript danach nur wenige Einträge an die Datenbank. Dann hängte es sich wieder in die Dauerschleife. Es sollten noch ungefähr dreimal mehr Einträge an die Datenbank geliefert werden...

Die andere Dauerschleife hindert mich an der Normalisierung der Einträge in den Tabellen für Japanisch. Ich habe Kana-Einträge gemacht, Kanji-Einträge gemacht und anschließend beide Tabellen automatisch durchnummerieren lassen. Für die Tabelle mit den Einträgen der Restriktionen muß mein Skript die id-Nummern passender Kana- und Kanji-Einträge aus den entsprechenden Tabellen holen. Sollte eine einfache SELECT-Anfrage mit einem Vergleich sein. Ich bin mal davon ausgegangen, daß mysqli_fetch_result() immer in eine WHILE-Schleife will...

Damit war es aber nicht getan, das Skript muß natürlich nochmal die Quelldatei durchlaufen, um herauszufinden, wo die Restriktionen gegeben sind. Statt von <entry> bis </entry> wird ein Array von <r_ele> bis </r_ele> gefüllt. Aber von bis ist eine übertriebene Behauptung, tatsächlich habe ich alle Elemente eingefüllt, die zwischen <r_ele> und </r_ele> auftauchen können. Außer <re_restr> können sie auch außerhalb auftauchen. Mit diesen Abschnitten durchläuft mein Skript FOREACH-Schleifen, bevor das Array wieder geleert wird. Innerhalb solcher werden dann die zusätzlichen Informationen aus den bereits befüllten zwei Tabellen geholt (also die id-Nummern) und schließlich die Kombination an die dritte Tabelle gesendet... Also theoretisch. Praktisch wird nichts eingetragen, nur das Skript läuft...
Ranma

12.11.2015 06:44

2 Jörg Kruse

Gibt es so etwas wie technische Hilfsmittel, um Dauerschleifen aufzuspüren? Vielleicht PHP-Anweisungen, die ich in Schleifen einfügen kann, um sie zu überwachen?

Du könntest einen Zähler einbauen und ab einer bestimmten Wiederholungszahl die Schleife mit break abbrechen:

$i = 0;
while (true) {
    if ($i >= 1000) {
        echo 'Die Schleife wird abgebrochen';
        break;
    }
    $i++;
    /* ... */
}

In dem if Block kannst du dir zur Analyse, warum die Schleife so lange läuft, die entsprechenden Variablen ausgeben lassen. Manchmal kommt man dadurch schon dem Problem auf die Spur. Ich würde anfangs erstmal auch nur mit einem kleineren Test-Ausschnitt der XML-Datei testen.

Die andere Dauerschleife hindert mich an der Normalisierung der Einträge in den Tabellen für Japanisch. Ich habe Kana-Einträge gemacht, Kanji-Einträge gemacht und anschließend beide Tabellen automatisch durchnummerieren lassen.

Das Durchnummerieren übernimmt aber schon MySQL, wenn du dem Primary Index die Eigenschaft AUTO_INCREMENT zugewiesen hast.

Ich würde die XML-Datei auch eher Entry-Node für Entry-Node einlesen, und bei jedem Durchgang alle drei Tabellen befüllen. Mit mysqli_insert_id erhältst du die IDs der ersten beiden Tabellen, und kannst diese dann für die dritte Tabelle nutzen. Möglicherweise ersparst du dir dadurch eine der Schleifen? Wenn du die entsprechenden Code-Abschitte mal postest, lassen sich vielleicht noch andere Optimierungen finden.

12.11.2015 14:21 | geändert: 12.11.2015 14:31

3 Ranma (Gast)

Wenn von Anfang an eine Spalte auf Auto-Increment steht, dann ist jeder Eintrag von allen anderen verschieden, egal was in den Spalten mit den relevanten Informationen steht. Man könnte hundert völlig leere Einträge haben und sie wären trotzdem alle voneinander verschieden. Schon bei der ersten Wörterliste hatte ich unerklärliche Doppelungen und daher nach einer Methode gesucht, um die zu vermeiden. Den Index nachträglich einzufügen ist daher viel besser! MySQL zählt danach noch genauso problemlos hoch.

Die Variante mit mysqli_insert_id() spart also nur eine einfache SELECT-Abfrage. Falls es an der liegt und ich daher nichtmal eine einfache SELECT-Abfrage hinbekommen sollte, würde ich dann nicht besser ganz aufgeben?

Natürlich könnte ich entry-Node für entry-Node einlesen, aber danach müßte ich ja doch noch die Teilstücke finden, welche r_ele-Nodes sind. Dadurch erhält der Array eine Dimension mehr. Theoretisch mag ich mehrdimensionale Arrays, aber praktisch verleiten sie vor allem zu mehr Fehlern...

Wenn ich mir den Array innerhalb der Schleife ausgeben lasse, dann enthält der immer leere Elemente, obwohl meine IF-Bedingungen nur Zeilen mit relevanten Informationen in den Array lassen dürften...

Für die Chinesisch-Liste würde es mir sehr helfen, wenn ich wüßte, welcher Eintrag in die Tabelle als letzter gemacht wurde. Dann könnte ich mir den nächsten in der Liste ansehen...

Natürlich würde ein Index mit Auto-Increment dabei helfen, aber möglicherweise gibt es dann wieder mehrfache Einträge. Da fällt mir gerade ein, ich kann es vielleicht mal mit einer parallelen und sonst gleich aufgebauten Tabelle für einen Test versuchen...
Ranma

13.11.2015 01:59

4 Jörg Kruse

Schon bei der ersten Wörterliste hatte ich unerklärliche Doppelungen und daher nach einer Methode gesucht, um die zu vermeiden. Den Index nachträglich einzufügen ist daher viel besser! MySQL zählt danach noch genauso problemlos hoch.

Wie machst du diese Doppelungen denn ausfindig? (du hattest das wohl mal in einem anderen Thread beschrieben, aber ich habe das jetzt nicht mehr auf dem Schirm)

13.11.2015 16:07

5 Ranma (Gast)

Ich hatte die Doppelungen ursprünglich nur bemerkt, weil sie gleich auf der ersten Seite der Tabelleneinträge in phpMyAdmin zu sehen waren als ich nur schaute, ob das Skript überhaupt funktioniert.

Das Problem in meiner Chinesisch-Liste habe ich inzwischen gefunden. Es liegt nicht an meinem Skript. In der Liste sind die deutschen Übersetzungen mittels / abgetrennt als /Übersetzung/ aber einige Male kommt / auch in anderer Bedeutung vor. Das fällt nur dann auf, wenn es innerhalb von ( ) steht. Da kann mein Skript dann den Inhalt nicht mehr aus den Klammern ziehen. Fast alle zusätzlichen Angaben stehen in der Liste innerhalb von runden Klammern.

Die Liste von Handedict ist zwar nicht die neueste Version, weil die verloren ist, aber das Projekt wurde mit GB5-Kodierung begonnen also vor Unicode-Zeiten. Bis 2011 dürften einige voneinander unabhängige Benutzer dazu beigetragen haben. Alle lieferten ihre eigenen Einteilungen und Fehler mit. In der Datei stehen sogar Einträge, die ich für dümmlich störende Bemerkungen halte. Aber da kann ich mir nicht sicher sein und sie schon garnicht mit einem Suchmuster aufspüren. Jedenfalls komme ich mir selbst jetzt garnicht mehr so unfähig vor, wenn ich sehe wieviele Fehler in der Datei gemacht wurden.

Leider führte der Versuch, das Muster (x/y) in den Einträgen zu suchen, nur dazu, daß der Speicher vollief. Statt einer Fehlermeldung gab es gleich einen Absturz, nur der Systemmonitor zeigte den vollen Speicher. Jetzt lasse ich das Skript immer wieder laufen und immer wenn es nicht weitermacht, kann ich über den letzten Eintrag in die Tabelle einen störenden Eintrag in der Datei mit der Liste finden. Die korrigiere ich dann von Hand, ersetze / durch \, was mein Skript wieder rückgängig macht. Natürlich erst nach der Zerlegung entlang der /. Die Methode ist ziemlich langwierig und umständlich. Das lohnt sich überhaupt nur, weil ich auch Stunden brauchen würde, um ein weiteres Skript zum Aufspüren der Fehler zu schreiben.

In der XML-Datei für Japanisch habe ich bei der Fehlersuche festgestellt, daß es noch xref-Nodes gibt, die ich bisher übersehen hatte. Darin werden besondere Verwendungen japanischer Ausdrücke angegeben. Jedenfalls wird zusammen mit dem Elter-Node immer eine bestimmte Übersetzung einem xref-Eintrag zugeordnet. Von der Struktur her also nicht anders als die Restriktion. Deshalb habe ich noch eine Tabelle für die xref-Zuordnungen gemacht. Dazu das Skript modifiziert. Es soll jetzt immer von <entry> bis </entry> einen Array befüllen, aber den nur weiterverarbeiten, falls xref- oder re_restr-Nodes darin vorkommen. Festgestellt mit substr_count()>0. Aber dieser Zweig wird nie durchlaufen und das Skript ist sehr schnell fertig. Also muß ich hier erstmal das gegenteilige Problem lösen, bevor es wieder auf die Suche nach der Dauerschleife geht.
Ranma

14.11.2015 03:50

6 Ranma (Gast)

Natürlich habe ich eine Variable als Weiche verwendet und die am Anfang der WHILE-Schleife immer auf FALSE gesetzt. Auf TRUE stand sie nur, wenn sie sowieso nicht abgefragt wurde. Ein Fehler, den ich nicht zum ersten Mal gemacht habe... Ist einer meiner häufigsten, genau darum ziehe ich FOREACH-Schleifen vor.

Jetzt hängt mein Skript für die Japanisch-Liste natürlich wieder in einer Dauerschleife. Dabei kommt es nicht zu Einträgen in die Datenbank. Eigentlich sollte sich bei der zugehörigen SELECT-Abfrage immer nur ein Wert in der Datenbank befinden. Vielleicht geht das besser als mit while(mysqli_stmt_fetch){}. Ich sollte eine Subquery ausprobieren und davor noch den einzutragenden Array auf den relevanten Teilbereich reduzieren...

Die Chinesisch-Liste ist endlich in einer Datenbanktabelle gelandet.
Ranma

15.11.2015 03:58

7 Ranma (Gast)

Hilft alles nichts. Da befüllt immernoch eine Dauerschleife den Arbeitsspeicher, aber nicht die Datenbank. Obwohl außer der WHILE(!FEOF($Liste)) jetzt alle WHILE-Schleifen entfernt sind und sämtliche Arrays nach jedem Durchlauf geleert werden. Welche anderen Strukturen können noch Dauerschleifen erzeugen?

Außerdem liefert die SQL-Query FALSE zurück, wieder der Fehlermeldung zu entnehmen, die meine Query als Boolean bezeichnet. Das seit ich aus den vorherigen beiden Queries eine gemacht habe:

INSERT IGNORE INTO Restriktion (Kana_id, Kanji_id) VALUES (SELECT Kana_id FROM Kana WHERE Wort = ?, SELECT Kanji_id FROM Kanji WHERE Japanisch = ?)

Ist das so falsch? Die Subqueries verlangen Strings und liefern Integers, was davon muß in mysqli_stmt_bind_param() angegeben werden? Die Bezeichnungen stimmen dieses Mal alle, darum wüßte ich nicht, was sonst noch die Query in ein Boolean verwandeln könnte.
Ranma

18.11.2015 02:10

8 Jörg Kruse

Welche anderen Strukturen können noch Dauerschleifen erzeugen?

Neben der while Schleife gibt es noch die foreach und for Schleife. Darüber hinaus laufen auch rekursive Funktionen in einer Art Schleife ab.

Außerdem liefert die SQL-Query FALSE zurück, wieder der Fehlermeldung zu entnehmen, die meine Query als Boolean bezeichnet.

Ist das die Fehlermeldung, die durch mysql_error() wiedergegeben wird, und wie lautet der genaue Wortlaut?

Ich würde die Subqueries noch in Klammern setzen:

INSERT IGNORE INTO Restriktion
(Kana_id, Kanji_id)
VALUES (
    (SELECT Kana_id FROM Kana WHERE Wort = ?),
    (SELECT Kanji_id FROM Kanji WHERE Japanisch = ?)
)

Die Subqueries verlangen Strings und liefern Integers, was davon muß in mysqli_stmt_bind_param() angegeben werden?

Der jeweilige String

18.11.2015 16:07

9 Ranma (Gast)


Neben der while Schleife gibt es noch die foreach und for Schleife. Darüber hinaus laufen auch rekursive Funktionen in einer Art Schleife ab.

FOR und FOREACH sind Zählschleifen und die dienen doch eigentlich genau dazu, Dauerschleifen von vorneherein auszuschließen? In manchen Programmiersprachen, inklusive PHP, kann man es hinbekommen, die Bedingungen für FOR so zu formulieren, daß doch eine Dauerschleife herauskommt. Aber wie bekäme man eine Dauerschleife per FOREACH hin?

Rekursion sollte immer zu einem Volllaufen des Arbeitsspeichers führen. Das gehört in der fraglichen Dauerschleife zu den Symptomen laut Systemmonitor. Rekursive Programmierung gilt als sehr anfällig für so etwas und, so elegant das Konzept auch ist, als etwas, das man besser vermeidet. Ich wüßte auch garnicht, wo ich etwas rekursiv programmiert hätte. Das bekommt man sicherlich nicht aus Versehen hin?

Die Fehlermeldung ist diejenige, die zur Laufzeit anstatt der von mir erwarteten Aktivität ausgegeben wird. Wo müßte mysql_error() hin, um mehr Informationen darüber zu bekommen? Ginge das ungefähr so? try{mysqli_stmt_execute()}catch{mysql_error()}


Der jeweilige String

Ich meinte den Teil vor den Variablen, wo "ss" oder "ii" oder "si" oder so etwas stehen muß.
Ranma

19.11.2015 04:50

10 Jörg Kruse

Ich wüßte auch garnicht, wo ich etwas rekursiv programmiert hätte. Das bekommt man sicherlich nicht aus Versehen hin?

Man muss hierzu schon innerhalb einer Funktionsdefinition die Funktion selbst aufrufen.

Wo müßte mysql_error() hin, um mehr Informationen darüber zu bekommen? Ginge das ungefähr so? try{mysqli_stmt_execute()}catch{mysql_error()}

In dem Fall wäre wohl die Verwendung von mysqli_stmt_error() in einem if-Konstrukt passender, welches den Rückgabewert von mysqli_stmt_execute() auswertet, da mit einem try-catch-Konstrukt eher Exceptions abgefangen werden:

if (! mysqli_stmt_execute($stmt)) {
    echo mysqli_stmt_error($stmt) . "\n";
}

Ich meinte den Teil vor den Variablen, wo "ss" oder "ii" oder "si" oder so etwas stehen muß.

Da muss in dem Fall ein "ss" stehen, da beide ? durch einen String ersetzt werden.

19.11.2015 15:34