getfoldersize zu langsam?

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Benutzeravatar
darktrym
User
Beiträge: 784
Registriert: Freitag 24. April 2009, 09:26

Hallo,
ich habe mir ein Skript gebastelt, welches die Verzeichnisgröße ermittelt, leider dauert das bissl arg lang.
Gibt's schnellere pythonische Methoden um das Problem zu lösen?

Code: Alles auswählen

#! /usr/bin/env python

import os
#import cProfile

def getFolderSize(start_dir):
	size = 0
	for curr_dir, dirs, files in os.walk(start_dir):
		curr_size = 0
		os.chdir(curr_dir)
		for filename in files:
			curr_size += os.path.getsize(filename)
		size += curr_size
		#print "%s = %i" % (curr_dir, curr_size)	
	return size	
		
#cProfile.run('getFolderSize(os.getcwd())')
print "directory size: %i" % getFolderSize(os.getcwd())
„gcc finds bugs in Linux, NetBSD finds bugs in gcc.“[Michael Dexter, Systems 2008]
Bitbucket, Github
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Ich kapiere nicht, inwiefern os.chdir() da notwendig ist. Wozu die beiden Bezeichner size und curr_size? Einer sollte doch reichen.

Naja, Du musst ja jede Datei einmal anfassen, um die Größe zu ermitteln. Wenn Du halt zig tausend davon hast, dann kann das schon ein wenig dauern. Ich nehme mal an, dass Du in der Standard-Lib geguckt hast, ob es da nichts fertiges gibt?
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
darktrym
User
Beiträge: 784
Registriert: Freitag 24. April 2009, 09:26

Die Variablen werden später noch gebraucht.

Die Suchmaschine hat mir keine Hinweise geliefert, ob sowas bereits integriert ist. Was mich ein biss. wundert ist, dass os.path.getsize auch mit Verzeichnissen funktioniert nur kann ich mit der Ausgabe nichts anfangen.
„gcc finds bugs in Linux, NetBSD finds bugs in gcc.“[Michael Dexter, Systems 2008]
Bitbucket, Github
BlackJack

@darktrym: Nur dass das nicht untergeht: das `os.chdir()` sollte da weg! Das Arbeitsverzeichnis ist etwas was für den ganzen Prozess gilt. Wenn man daran herum spielt, schreibt man Code der nur schlecht mit anderem zusammen arbeitet. Früher oder hat man an anderer Stelle im Programm Code, der so überhaupt nicht damit rechnet dass das Arbeitsverzeichnis verändert wurde.

Die Grösse von Verzeichnissen ist die Grösse des Verzeichnisses selber. Die Dateinamen und Attribute die in einem Verzeichnis stecken, benötigen selbst ja auch Speicherplatz.
Benutzeravatar
darktrym
User
Beiträge: 784
Registriert: Freitag 24. April 2009, 09:26

Das mit os.chdir sehe ich ein, war nur eine Optimierung meinerseits um nicht den absoluten Pfad der Datei angeben zu müssen.

Die Sache mit dem Verzeichnis verstehe ich aber nicht. Wenn ich die Anweisung so formuliere erwarte ich ja nicht irgendwelche esoterischen Werte. Angenommen im Verzeichnis liegt eine Datei(447Byte), belegt werden auf dem Datenträger ein Block a 4KB, wieso wird dann 64KB! von der Funktion zurückgegeben(unter Windows 7)?
„gcc finds bugs in Linux, NetBSD finds bugs in gcc.“[Michael Dexter, Systems 2008]
Bitbucket, Github
BlackJack

@darktrym: Weil das Verzeichnis dort anscheinend 64 KiB gross ist. Die Grösse der Datei und wie viel die auf der Platte belegt ist dabei übrigens egal. Es geht da nur um die Metadaten, also Dateinamen, Rechte, Zeitstempel, ACLs, und so weiter, die in dem Verzeichnis gespeichert sind. Ausserdem solltest Du nicht damit rechnen, dass die Grösse sich bei jeder enthaltenen Datei ändert -- wenn Du da zwei statt einer Datei im Verzeichnis hast, dann werden da wahrscheinlich keine 128 KiB draus, weil sicher mehr als ein Dateieintrag in die 64 KiB passt.
Benutzeravatar
darktrym
User
Beiträge: 784
Registriert: Freitag 24. April 2009, 09:26

Ok, das Problem ist wohl os.walk. Diese Variante läuft ohne os.walk und braucht dafür nur die halbe Zeit!

Code: Alles auswählen

import os
#import cProfile

def getFolderSize(directory):
	size = 0
	dirs = [directory]
	while dirs:
		curr_size = 0
		directory = dirs.pop()
		os.chdir(directory)
		for item in os.listdir(directory):
			if os.path.isdir(item):
				dirs.append(os.path.join(directory, item))
			else:
				curr_size += os.path.getsize(item)
		size += curr_size
		#print "%s = %i" % (directory, curr_size)	
	return size
		
#cProfile.run('getFolderSize(os.getcwd())')
print "directory size: %i" % getFolderSize(os.getcwd())
„gcc finds bugs in Linux, NetBSD finds bugs in gcc.“[Michael Dexter, Systems 2008]
Bitbucket, Github
BlackJack

@darktrym: Da ist immer noch das `os.chdir()` drin…

Wie ist die Messung eigentlich zustande gekommen? Ich will die nicht anzweifeln -- nur sicherstellen das die Ausgangsbedingungen auch wirklich vergleichbar waren. Der Cache wurde vor beiden Testläufen geleert? Also nicht das der zweite Testlauf die ganzen Daten schon im RAM hatte.
lunar

@BlackJack: Die gezeigte Variante ist im Gegensatz zu "os.walk()" nicht rekursiv, was angesichts der Kosten für einen Funktionsaufruf bei großen Hierarchien schon spürbare Unterschiede mit sich bringen kann, auch wenn ich eine Halbierung der Ausführungszeit nicht wirklich glaube.
deets

@lunar

Mit Verlaub, aber im Kontext von Festplattenoperationen geht ein Funktionsaufruf mehr schlicht im Rauschen unter.

Wenn der OP solche massiven Effekte beobachtet hat, dann ist das IMHO auf dein Plattencache zurueckzufuehren, da hat BlackJack schon den richtgen Riecher, die Methodik zu hinterfragen.
Benutzeravatar
b.esser-wisser
User
Beiträge: 272
Registriert: Freitag 20. Februar 2009, 14:21
Wohnort: Bundeshauptstadt B.

Ich hab das mal minimal optimiert, aber wenn ich beide Funktionen auf den selben Pfad loslasse - egal in welcher Reihenfolge - ist die zweite 10x schneller (logisch, jedes OS hat 'nen Cache für die Festplatte).

Wie misst man das denn 'richtig' (Cache, os.path.join vs. os.chdir, rekursion )? :K

edit.:
ps.: bei dem script im pastebin: Linux-nutzer sollten wahrscheinlich besser time.time() statt time.clock() benutzen
deets

Kann ich nicht nachvollziehen:

Code: Alles auswählen

(master)dir@client8131:~/projects/ableton/master$ time python /tmp/a.py
directory size: 723902605

real    0m2.360s
user    0m0.188s
sys     0m0.160s
(master)dir@client8131:~/projects/ableton/master$ time python /tmp/a.py
directory size: 723902605

real    0m0.217s
user    0m0.120s
sys     0m0.096s
(master)dir@client8131:~/projects/ableton/master$ time python /tmp/a.py
directory size: 723902605

real    0m0.217s
user    0m0.120s
sys     0m0.096s
(master)dir@client8131:~/projects/ableton/master$ time python /tmp/b.py
directory size: 723902605

real    0m0.333s
user    0m0.184s
sys     0m0.144s
(master)dir@client8131:~/projects/ableton/master$ time python /tmp/b.py
directory size: 723902605

real    0m0.332s
user    0m0.232s
sys     0m0.096s
(master)dir@client8131:~/projects/ableton/master$ 
Variante a ist dabei etwas ungluecklich benannt deine zweite, und b die von os.walk. Es gibt zwar minimale Vorteile bezueglich a, aber nicht Faktor 10. Der erste Lauf zeigt ganz gut, wie der Plattencache anspringt.

Ich habe mir mal die Implementation von os.walk angeschaut. Die ist ein kleeeeines bisschen komplexer als deine home-brew-geschichte, und unter anderem ruft sie islink auf, was ein zusaetzlicher syscall ist. Das koennte die zeitlichen Nachteile erklaeren.
BlackJack

Neben dem mehrfachen laufen lassen um den Cache zu füllen bevor man das Programm testet, könnte man auch vor jedem Lauf die Caches leeren. Unter Linux zum Beispiel mit ``echo 3 > /proc/sys/vm/drop_caches`` mit "root"-Rechten.

Dann misst man zwar nicht nur das Programm, bekommt aber vielleicht praxisnähere Zahlen. Und wahrscheinlich einen deutlich kleineren Unterschied zwischen den Varianten.
Benutzeravatar
darktrym
User
Beiträge: 784
Registriert: Freitag 24. April 2009, 09:26

os.chdir() kommt noch raus, "du hast mein Ehrenwort"; es diente alleinig zum Vergleich beider Varianten.
Hat mich ehrlich gesagt etwas verwundert, dass die spezialisierte Variante langsamer ist als die allgemeine, selbstgebastelte.
Mein Test bezog sich auf ein Verzeichnis mit 2643 Unterverzeichnissen 37331 Dateien, knapp 1.14 GB, mehrere Repos.
„gcc finds bugs in Linux, NetBSD finds bugs in gcc.“[Michael Dexter, Systems 2008]
Bitbucket, Github
BlackJack

@darktrym: Ich glaube jetzt hast Du gerade "allgemein" und "spezialisiert" verwechselt. Deine ist die Spezialisiertere. `os.walk()` ist allgemeiner -- damit kann man nicht nur Dateigrössen zusammenrechnen. Man kann dort auch während des Iterierens Verzeichnisse filtern, in die dann nicht weiter rein gegangen wird. Und man kann die Ergebnisse "bottom up" liefern lassen, also die Blätter des Verzeichnisbaums zuerst.
Antworten