Forum: PC-Programmierung foeach schleife performance


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
von tai (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Hallo zusammen,

Ich programmiere in c# und habe eine Verständnisfrage. Ich habe unten 
zwei Codevarianten. Was ist bezüglich Geschwindigkeit schneller? Oder 
sind beide gleich?

1. Varianten

foreach(var item in items.where(x => x.abc == typeof(..) && !x.def )){ 
...}

2. Variante

var filteredItems = items.where(x => x.abc == typeof(..) && !x.def )
foreach(var item in filteredItems ){ ...}

Wird die Liste bei jedem Durchlauf neu gefiltert ?

Gruß

von Dunno.. (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Schöne Frage.
Hier: 
https://stackoverflow.com/questions/19867012/does-foreach-evaluate-the-array-at-every-iteration

Steht in einem ähnlichen Fall dass der enumerator nur einmal erstellt 
wird. Das where gibt eine Auflistung zurück, die dann von der Schleife 
zur Abarbeitung benutzt wird.

Zur Variante 2: wenn deine items in einer Liste sind, könntest du auch 
linqs .ForEach auf deine gefilterte  elemente verwenden.
Das mischt die Syntax nicht so auf. :)

von Jemand (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Stoppuhr kaputt?

von npn (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Jemand schrieb:
> Stoppuhr kaputt?

Langeweile?

von Jemand (Gast)


Bewertung
0 lesenswert
nicht lesenswert
npn schrieb:
> Jemand schrieb:
>> Stoppuhr kaputt?
>
> Langeweile?

Nein. Das ist die einzige wirklich funktionierende Methode, 
Geschwindigkeit zu beurteilen.

von Dunno.. (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Jemand schrieb:
>  Das ist die einzige wirklich funktionierende Methode,
> Geschwindigkeit zu beurteilen.

Naja, du könntest auch den profiler bemühen, oder application insights.

Bei Kleinkram wie hier finde ich den Blick auf die spec aber sinnvoller, 
weil das die grundsätzliche Frage klärt, und sich nicht nur auf 
empirische Beobachtung stützt.


Davon ab spielen solche Kleinigkeiten imho bei den meisten Applikationen 
eh keine Rolle, und man sollte die Variante schreiben die les-und 
wartbarer ist. Bei Performanceproblemen kann man immer noch Profilen.

von Sascha R. (srt2018)


Bewertung
0 lesenswert
nicht lesenswert
Beide Varianten sind gleich. Der IL-Code dürfte sich nicht 
unterscheiden. Die Auswertung geschieht nicht sofort sondern immer bevor 
die Schleife erneut ausgeführt bzw. beendet wird.

von sid (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Ausm Bauch raus würd ich sagen
kommt auf die Länge der Liste an!

bei kurzen Listen scheint mir der unterschied eher marginal falls 
überhaupt einer besteht, und ich bevorzugte variante 1

bei mittleren Listen kommt mir die zweite variante schneller vor,
sofern der Filter nicht millionenfach erneut aufgerufen wird.

bei sehr langen Listen allerdings könnte der Filter zusammen mit der 
angelegten Listenkopie soviel verbrauchen, dass der Vorteil wieder 
verschwindet.

wie gesagt, weder empirisch gemessen, noch in Datenblättern oder 
Kompilaten nachgesehen, nur so'n Bauchgefühl.
(wie wenig auch immer das wert sein mag)

'sid

von sparfux (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Wenn ich mir die heutige "Drecks"-Software so ansehe, macht

> Bei Performanceproblemen kann man immer noch Profilen.

das scheinbar keiner.

von Sascha R. (srt2018)


Bewertung
0 lesenswert
nicht lesenswert
sid schrieb:
> bei sehr langen Listen allerdings könnte der Filter zusammen mit der
> angelegten Listenkopie soviel verbrauchen, dass der Vorteil wieder
> verschwindet.

Es gibt aber keine Kopie der Liste. Mit "Where(...)" erzeugt man keine 
Listenkopie, sondern eine Aufzählung.

Erst während man per Schleife die Aufzählung durchiteriert, wird die 
Filter-Bedingung ausgewertet, einmal für jedes Element.

Und zwar in beiden Fällen.

Aber auch das ist im Grunde egal, die beiden Schreibweisen sind 
äquivalent. In der einen wird ein Zwischenergebnis einer Variablen 
zugewiesen die im nächsten Statement gleich wieder verwendet wird, in 
der anderen entfällt lediglich diese Variable, nicht aber das 
Zwischenergebnis.

Ein dem Where folgendes ToList z.B. würde eine Listenkopie erzeugen, und 
dazu die Bedingung für jedes Element von items auswerten. Die Schleife 
selbst würde nur noch diese neue Liste durchgehen. Das wäre aber auch in 
beiden Schreibweisen der Fall.

Ich würde empfehlen, sich das Kompilat mit ilSpy, dnSpy, dotPeek oder 
wie sie alle heißen anzuschauen. Spoiler: Dann sähe man, dass für 
foreach zunächst einmal der Ausdruck nach dem "in" ausgewertet wird, 
dann vom Ergebnis die Methode GetEnumerator() aufgerufen wird. Mit dem 
enumerator wird dann die Schleife "bewältigt" mit wiederholten aufrufen 
von MoveNext() und Current. Im Aufruf von MoveNext() geschieht die 
eigentliche Auswertung der Filterbedingungen, bis EIN weiteres passendes 
Element oder das Ende gefunden wird.

von Imonbln (Gast)


Bewertung
0 lesenswert
nicht lesenswert
sparfux schrieb:
> Wenn ich mir die heutige "Drecks"-Software so ansehe, macht
>
>> Bei Performanceproblemen kann man immer noch Profilen.
>
> das scheinbar keiner.

Macht, ja auch wenig Spaß, Marketing will Features sehen und außerdem 
gibt es immer einen im Team der in den frühen 90 mal ein Buch gelesen 
hat und daher fest überzeugt ist dass er das bottleneck durch pure 
Erfahrung und Codelesen findet und mit einem kruden Konstrukt vermeiden 
kann.

von sid (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Sascha R. schrieb:
> Es gibt aber keine Kopie der Liste. Mit "Where(...)" erzeugt man keine
> Listenkopie, sondern eine Aufzählung.

ich sollte vor'm schlafengehen keine Codezeilen mehr lesen.. ich hätte 
schwören können da .ToList() gelesen gehabt zu haben (uuups)

Du hast aber insofern Recht, alsdass ein Enumerator hier deutlich 
kleinere items trägt als die ursprüngliche Liste..
dennoch bleibt er dem Grunde nach eine Liste* und kostet Speicher, und 
Zeit zum erstellen ;)

Macht aber meinen letzten Gedanken in der Tat obsolet.. kann mir nicht 
vorstellen, dass man mit IEnumerable<T> ausreichend Speicher verbrauchen 
kann um in ein Performanceproblem zu rennen.

'sid

* sollte in etwa vergleichbar viel Platz benötigen wie List<int> meine 
ich #kopfkratz

von Udo S. (urschmitt)


Bewertung
0 lesenswert
nicht lesenswert
sparfux schrieb:
> Wenn ich mir die heutige "Drecks"-Software so ansehe

Ich gehe mal davon aus du programmierst noch direkt in Maschinencode, 
und benutzt maximal edlin oder vi auf einem 24x80 Terminalfenster.

Denn die modernen Entwicklungsumgebungen sind ja alles "Dreckssoftware" 
:-)

SCNR

von Yalu X. (yalu) (Moderator)


Bewertung
0 lesenswert
nicht lesenswert
sid schrieb:
> Du hast aber insofern Recht, alsdass ein Enumerator hier deutlich
> kleinere items trägt als die ursprüngliche Liste..
> dennoch bleibt er dem Grunde nach eine Liste* und kostet Speicher, und
> Zeit zum erstellen ;)

Ich kenne mich in C# nicht aus, vermute aber, dass ein Enumerator in C#
prinzipiell dasselbe ist wie ein Iterator in Python und damit unabhängig
von der Länge der Eingabesequenz (die sogar endlos sein kann) nur
konstanten Speicherplatz belegt. Die eigentliche Filterung wird nicht
beum Aufruf von where, sondern erst bei der Abarbeitung des Enumerators
bzw. Iterators (bspw. in einer foreach-Schleife) durchgeführt.

Auch die Filter-Funktion in Haskell arbeitet nach diesem Prinzip, nur
ist das dort dort nichts Besonderes, da in Haskell auch alle anderen
Funktionen defaultmäßig lazy, d.h erst bei Bedarf ausgewertet werden.

von Sascha R. (srt2018)


Bewertung
0 lesenswert
nicht lesenswert
Yalu X. schrieb:
> vermute aber, dass ein Enumerator in C#
> prinzipiell dasselbe ist wie ein Iterator in Python und damit unabhängig
> von der Länge der Eingabesequenz (die sogar endlos sein kann) nur
> konstanten Speicherplatz belegt. Die eigentliche Filterung wird nicht
> beum Aufruf von where, sondern erst bei der Abarbeitung des Enumerators
> bzw. Iterators (bspw. in einer foreach-Schleife) durchgeführt.

Ja so ist das im Prinzip.

Wobei hier noch unterschieden wird zwischen IEnumerable (kann 
Enumerator(en) bereitstellen, hat selbst keine Position) und IEnumerator 
(kann einmal durch die Aufzählung führen, beinhaltet implizit eine 
aktuelle Position). Where liefert IEnumerable, foreach holt sich von 
diesem dann den Enumerator.

Allerdings geschieht in beiden Varianten der Ausgangsfrage genau das. 
Einmal in getrennten Statements (Zuweisung + foreach), einmal in einem 
kombinierten Statement (foreach).

: Bearbeitet durch User

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.