Script beschleunigen Topic is solved

Stelle Fragen zur Programmierung mit Autohotkey

Moderator: jNizM

KHA
Posts: 403
Joined: 21 Aug 2018, 11:11

Script beschleunigen

Post by KHA » 24 Jan 2021, 12:06

Hallo,
ich habe die Aufgabe aus zwei CSV Dateien eine zu machen. Aufgabe ist simpel, hole aus Stocks.csv den ProductId, schau nach ob es in Produkte.csv vorkommt, wenn ja, dann gehe Produkte.csv Zeile für Zeile durch, bis die richtige Zeile gefunden worden ist, dann Teile die Zeile, füge die mit Informationen aus Stocks.csv zusammen.
Stocks.csv = ca. 6.500 Zeilen groß (ca. 300 KB)
Produkte.csv = ca. 40.000 Zeilen groß (ca. 14MB)
Dafür braucht mein PC ca. 8 Minuten.

Mir ist klar, es geht Zeile für Zeile durch, je tiefer sich die gesuchte Zeile unten befindet desto länger braucht es.


1. Frage: Kann man das irgendwie beschleunigen, sprich habe ich im Script ein Logik / Denk Fehler?
2. Frage: Ich habe sowas ähnliches mit XML-Dateien gemacht, die Dateien waren 30x größer, trotzdem hat AHK innerhalb von wenigen Sekunden die Aufgabe erledigt. Arbeitet XML parse von @just me anders? (siehe: https://www.autohotkey.com/boards/viewtopic.php?f=20&t=85026)

LG

Code: Select all

#NoEnv
#SingleInstance, force
SetBatchlines, -1

StartZeit := A_TickCount


FileRead, Bestaende, Data\Stocks.csv
FileRead, Produkte, Data\Produkte.csv

DateiName := "Data\BestandPlusEKpreis.csv"
File := FileOpen(DateiName, "w")
	if !IsObject(File)
	{
	    MsgBox Kann "%DateiName%" nicht zum Schreiben öffnen.
	    return
	}


Loop, parse, Bestaende, `n, `r
{
	if (A_LoopField = "")
	continue

BestaendeTeilen := StrSplit(A_LoopField, ";")
ProductId := BestaendeTeilen[1]
EAN := BestaendeTeilen[2]
Count := BestaendeTeilen[3]
	
	if InStr(Produkte, ProductId)
	{
		Loop, parse, Produkte, `n, `r
		{
			if InStr(A_LoopField, ProductId)
			{
			ProdukteTeilen := StrSplit(A_LoopField, ";")
			Preis := ProdukteTeilen[3]
			Bezeichnung := ProdukteTeilen[4]
			Marke := ProdukteTeilen[6]
			File.Write(ProductId ";" EAN ";" Marke " " Bezeichnung ";" Count ";" Preis "`n")
			break
			}
		}
	}Else
	File.Write(ProductId ";" EAN ";" Count "; Nicht in ProduktListe drinnen `n")

}

File.Close()

VerstricheneZeit := A_TickCount - StartZeit
MsgBox,  % VerstricheneZeit / 1000 " sekunden sind verstrichen."
User avatar
divanebaba
Posts: 805
Joined: 20 Dec 2016, 03:53
Location: Diaspora

Re: Script beschleunigen

Post by divanebaba » 24 Jan 2021, 16:02

Hi.

Ich vermeide, innerhalb von (großen und schnell zu durchlaufenden) Schleifen, die Zugriffe auf die Festplatte und konnte so erhebliche Leistungssteigerungen und extrem festplattenschonenden Code kreieren.
Desweiteren habe ich, auf Verdacht, die InStr()-Abfrage durch SubStr() und einer (=)-Abfrage ersetzt. Du müsstest nur noch die exakte ProductId-Länge eingeben und hoffentlich ist es immer dieselbe Ziffernlänge. Aber ich denke, das könnte auch Spielerei sein. Probier es einfach mal aus und stoppe die Zeit.

Weitere Tips, eine Schleife mit wenigstens 6.500 Zeilen zu parsen, wobei eventuell jede Zeile erneut 40.000 Zeilen zum Parsen auslösen kann, sind mir nicht bekannt, denke ich.
Spoiler
Einfach nur ein toller Typ. :mrgreen:
User avatar
haichen
Posts: 631
Joined: 09 Feb 2014, 08:24

Re: Script beschleunigen

Post by haichen » 25 Jan 2021, 01:27

Ich habe mal mit der Bibliothek von JnLlnd gearbeitet. Hier findest du die Doc. Ich meine, ich hatte da auch recht große CSV-Dateien verwendet.
Da gibt es die Funktion ObjCSV_CSV2Collection() die ein Objekt aus der CSV-Datei erzeugt. Die Objekte kann man dann direkt ansprechen.
Du könntest sogar mit ObjCSV_Collection2XML() XML-Dateien erzeugen. Keine Ahnung ob das bei deinem Problem hilft, aber du schreibst, dass du damit Erfahrung hast.
User avatar
haichen
Posts: 631
Joined: 09 Feb 2014, 08:24

Re: Script beschleunigen

Post by haichen » 25 Jan 2021, 05:17

Ich habe das mal mit ein paar artifiziellen Daten ausprobiert.
Bestaende.csv wurde dabei 133 KB groß und Produkte.csv 1122 KB.
Die Erzeugung von BestandPlusEKpreis.csv (266 KB) hat ca. 1 sek gedauert.
Vielleicht kannst du damit ja was anfangen.

Code: Select all

#include ObjCSV.ahk

/* "Bestaende.csv"
	ID;EAN;Count
	1;748871116528;1
	2;748871118027;4
	3;748871090026;55
	4;748871116525;3
	5;748871118024;6
	6;748871090023;2
*/

Bestaende := "Bestaende.csv"
Produkte  := "Produkte.csv"

/*
Fileappend, %  "ID;EAN;Count", Bestaende.csv
Loop,  6500
	Fileappend, % "`n" A_Index ";748871116528;3", Bestaende.csv

Fileappend, % "ID;Preis;Bezeichnung;Marke", Produkte.csv
Loop, 40000
	Fileappend, % "`n" A_Index ";Gummiente;1,99;Flummi", Produkte.csv
*/

/* "Produkte.csv" Bsp.
	ID;Preis;Bezeichnung;Marke
	1;1,99;Gummiente;M1
	2;1,98;Teddybär;M1
	3;5,99;Ken;M2
	4;24,5;Barbie;M2
	5;33,29;Lego Star Wars;M3
*/
SetBatchlines, -1


strFields := ""
Bestaende := "Bestaende.csv"
Produkte  := "Produkte.csv"
DateiName := "BestandPlusEKpreis.csv"

FileDelete, DateiName
sleep, 200


File := FileOpen(DateiName, "w")
if !IsObject(File)
{
	MsgBox Kann "%DateiName%" nicht zum Schreiben öffnen.
	return
}

StartZeit := A_TickCount

obj0 := ObjCSV_CSV2Collection(Bestaende, strFields,1,,,";","""","","")
obj1 := ObjCSV_CSV2Collection(Produkte,  strFields,1,,,";","""","","")



File.Write("ID;EAN;Marke;Bezeichnung;Count;Preis")

/* Als for-Schleife
for index, element in obj0
{
	BIndex:= obj0[a_index]["ID"]
	PIndex:= obj1[a_index]["ID"]
	if (PIndex)
	{
		row:= "`n" BIndex ";" obj0[BIndex]["EAN"] ";" obj1[BIndex]["Marke"] ";" obj1[BIndex]["Bezeichnung"] ";" obj0[BIndex]["Count"] ";" obj1[BIndex]["Preis"]
		File.Write(row)
	}
}
file.close
ExitApp
*/

loop, % obj0.MaxIndex(){
	BIndex:= obj0[a_index]["ID"]
	PIndex:= obj1[a_index]["ID"]
	if (PIndex)
	{
		row:= "`n" BIndex ";" obj0[BIndex]["EAN"] ";" obj1[BIndex]["Marke"] ";" obj1[BIndex]["Bezeichnung"] ";" obj0[BIndex]["Count"] ";" obj1[BIndex]["Preis"]
		File.Write(row)
	}
}
file.close

VerstricheneZeit := A_TickCount - StartZeit
MsgBox,  % round(VerstricheneZeit / 1000 ) " sekunden sind verstrichen."
; 1sek
ExitApp
just me
Posts: 9458
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: Script beschleunigen  Topic is solved

Post by just me » 25 Jan 2021, 06:30

Moin,

divanebabas Tipp hinsichtlich des Schreibens auf Platte könnte/sollte schon etwas bringen. Die von haichen genannte Bibliothek mag auch hilfreich sein, ich schrecke aber immer davor zurück, relativ einfach selbst zu erledigende Probleme mit dem Knüppel einer 'eierlegenden Wollmilchsau' zu erschlagen, deren Code man im Zweifel vielleicht nicht nachvollziehen kann.

In diesem Fall gehe ich davon aus, dass jede ProduktID in Stocks.csv nur einmal vorkommt, und dass es auch in der Produkte.csv in jedem Satz ein Feld mit der ProduktID gibt. Das schreit geradezu danach, als Erstes aus der Stocks.csv ein assoziatives Array mit der ProduktID zu machen:

Code: Select all

BestandsArray := {}
Loop, Parse, Bestaende, `n, `r
{
   If (A_LoopField = "")
      Continue
   Felder := StrSplit(A_LoopField, ";")
   BestandsArray[Felder[1]] := {EAN: Felder[2], Count: Felder[3], Found: 0}
}
Der Schlüssel Found dient nur dazu, die im Bestand aber nicht in der Produktdatei vorhandenen ProduktIDs ausgeben zu können. Wenn Du das nicht brauchst, kann er weg.

Wir haben jetzt eine Schleife über 6500 Zeilen durchlaufen.

Jetzt folgt die Verarbeitung der Produktdatei mit einer Schleife über 40.000 Zeilen:

Code: Select all

Ausgabe := ""
Loop, Parse, Produkte, `n, `r
{
   If (A_LoopField = "")
      Continue
   Felder := StrSplit(A_LoopField, ";")
   ; IndexderProduktID bitte durch zutreffenden Wert ersetzen
   If (Bestand := BestandsArray[Felder[IndexderProduktID])
   {
      Ausgabe .= Felder[IndexderProduktID] . ";" . Bestand.EAN . ";" . Felder[6] . ";" . Felder[4] . ";"
               . Bestand.Count . ";" . Felder[3] . "`n"
      Bestand.Found := True
   }
   Else
      Ausgabe .= Felder[IndexderProduktID] . ";" . Felder[6] . ";" . Felder[4] . ";" Felder[3]
               . ";;Nicht im Bestand!`n"
}

Wenn jetzt noch die ProduktIDs aus dem Bestand, die nicht in der Produktliste vorhanden sind, ausgegeben werden sollen, folgt noch eine Schleife über 6500 Zeilen:

Code: Select all

For ProduktID, Produkt In BestandsArray
   If !Produkt.Found
      Ausgabe .= ProduktID . ";" . Produkt.EAN . ";" . Produkt.Count . ";;;Nicht in der Produktliste!`n"
Die Ausgabe ist jetzt fertig und kann in eine Datei geschrieben werden. Insgesamt sollte das keine 8 Minuten dauern.

Bitte beachten: Der obenstehende Code ist nur beispielhaft und praktisch nicht getestet! Ich hoffe, es hilft Dir trotzdem weiter.

@haichen:

Code: Select all

loop, % obj0.MaxIndex(){
	BIndex:= obj0[a_index]["ID"]
	PIndex:= obj1[a_index]["ID"]
	...
}
Ich habe mir ObjCSV_CSV2Collection() nicht angeschaut, aber: Der Code ist zwar schnell, ich glaube aber nicht, dass die Schleife vollständig ist, wenn die Arrays unterschiedlich viele Elemente enthalten.
User avatar
Frosti
Posts: 426
Joined: 27 Oct 2017, 14:30
Contact:

Re: Script beschleunigen

Post by Frosti » 25 Jan 2021, 08:40

Mir waren die 29s für 1,3Millionen Datensätze schon zu lang in meiner DBase Datei. 8 min sind kaum vorstellbar. Er meinte 8s?
just me
Posts: 9458
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: Script beschleunigen

Post by just me » 25 Jan 2021, 09:34

Moin @Frosti,

wenn man in einer 6.500er Schleife eine weitere Schleife mit bis zu 40.000 Durchläufen ausführt, mag das schon Dauern. Und bei Plattenzugriffen ist es ein Riesenunterschied, ob die auf eine SSD oder eine althergebrachte HD zielen.

;)
KHA
Posts: 403
Joined: 21 Aug 2018, 11:11

Re: Script beschleunigen

Post by KHA » 25 Jan 2021, 15:29

@divanebaba
Danke für deine Lösungsvorschlag!
Habe aber kein Unterschied zwischen dein Script und mein Script feststellen können, also ob ich alles in einer Variable packe und dann mir FileAppend auf die Festplatte schreibe oder ob ich mit FileOpen arbeite, unterschied ist plus minus 10sekunden.

@haichen
Danke!, ObjCSV Library kannte ich gar nicht, habe es mir jetzt heruntergeladen, schau mir die Funktionen am Wochenende an, vlt. kann ich es für später benötigen. Soweit ich es verstanden habe, Arbeit es auch associative arrays.

@just me
Danke! einfach Genial!
Vorher 460 sec. jetzt 0.29 sec.
User avatar
Frosti
Posts: 426
Joined: 27 Oct 2017, 14:30
Contact:

Re: Script beschleunigen

Post by Frosti » 25 Jan 2021, 15:31

Über LAN auf Server der SSD hat. Lokale Kopie der Datei verhält sich doppelt so schnell. Hab aber auch ein ne Zeit gefummelt dran um von Minuten auf Sekunden zu kommen. Da die DBASE Dateien statisch sind, hilft jetzt ein eigener Index nach die Suche auf unter 3s zu bringen. Dennoch 8min sind eine harte Geduldsprobe, insbesondere wenn man seinen Code noch nicht fehlerfrei hat. Ich hab da erstmal nur 1000 Datensätze gemacht und langsam aufgemacht. Auf Computer zu warten ist nicht so meins.
Post Reply

Return to “Ich brauche Hilfe”