Deklarieren einer Variablen innerhalb oder außerhalb einer foreach-Schleife: Was ist schneller / besser?

91

Welches davon ist das schnellere / bessere?

Dieses:

List<User> list = new List<User>();
User u;

foreach (string s in l)
{
    u = new User();
    u.Name = s;
    list.Add(u);
}

Oder dieses:

List<User> list = new List<User>();

foreach (string s in l)
{
    User u = new User();
    u.Name = s;
    list.Add(u);
}

Meine Fähigkeiten zur Entwicklung von Neulingen sagen mir, dass die erste besser ist, aber ein Freund von mir sagt mir, dass ich falsch liege, konnte mir aber keinen guten Grund geben, warum die zweite besser ist.

Gibt es überhaupt einen Leistungsunterschied?

Marcus
quelle

Antworten:

110

In Bezug auf die Leistung werden beide Beispiele zu derselben IL kompiliert, sodass es keinen Unterschied gibt.

Das zweite ist besser, weil es Ihre Absicht klarer ausdrückt, wenn ues nur innerhalb der Schleife verwendet wird.

dtb
quelle
10
Man beachte , dass es ist , einen Unterschied , wenn die Variable durch einen Lambda - Ausdruck oder anonym Delegierten erfaßt wird; siehe Äußere variable Falle .
dtb
Können Sie erklären, warum beide zu derselben IL kompiliert wurden? Ich bin mir ziemlich sicher, dass C # Variablendeklarationen nicht wie Javascript an die Spitze der Funktion hebt.
Styfle
4
@styfle hier ist die Antwort auf deine Frage.
David Sherret
Die folgenden Stapelüberlauf-Links bieten detailliertere Antworten: 1) Jon Hanna und 2) StriplingWarrior
user3613932
14

In jedem Fall wäre der beste Weg, einen Konstruktor zu verwenden, der einen Namen annimmt ... oder auf andere Weise die Notation in geschweiften Klammern auszunutzen:

foreach (string s in l)
{
    list.Add(new User(s));
}

oder

foreach (string s in l)
{
    list.Add(new User() { Name = s });
}

oder noch besser, LINQ:

var list = l.Select( s => new User { Name = s});

Während Ihr erstes Beispiel in einigen Fällen unmerklich schneller sein könnte, ist das zweite besser, weil es besser lesbar ist und der Compiler die Variable möglicherweise verwirft (und sie ganz weglässt), da sie nicht außerhalb des Gültigkeitsbereichs des foreachBereichs verwendet wird.

Tordek
quelle
6
Nekrophiler Kommentar des Tages: "oder noch besser, LINQ". Sicher, es ist eine Codezeile, und das gibt uns Entwicklern ein gutes Gefühl. Die vierzeilige Version ist jedoch weitaus verständlicher und damit wartbar.
Oskar Austegard
5
Kaum. Mit der LINQ-Version weiß ich, dass das, was ich tue, unveränderlich ist und über jedes Element funktioniert.
Tordek
6

Eine Deklaration bewirkt nicht, dass Code ausgeführt wird, daher handelt es sich nicht um ein Leistungsproblem.

Das zweite ist, was Sie meinen, und es ist weniger wahrscheinlich, dass Sie einen dummen Fehler machen, wenn Sie es auf die zweite Weise tun. Verwenden Sie das also. Versuchen Sie immer, Variablen im kleinsten erforderlichen Bereich zu deklarieren.

Außerdem ist es besser, Linq zu verwenden:

List<User> users = l.Select(name => new User{ Name = name }).ToList();
Mark Byers
quelle
2
Ich liebe die Zeile "Versuchen Sie immer, Variablen im kleinsten erforderlichen Bereich zu deklarieren." Ich denke, eine einzelne Zeile kann die Frage sehr gut beantworten.
Manjoor
5

Wenn Sie eine Frage zur Leistung haben, müssen Sie nur messen - führen Sie eine Schleife um Ihren Test und legen Sie die Zeit fest.

Um Ihre Frage zu beantworten - ohne zu messen :-) oder das generierte Ilasm zu betrachten -, wäre ein Unterschied in einer bedeutenden Anzahl von Iterationen und der teuersten Operation in Ihrem Code nicht erkennbar. Es ist wahrscheinlich, dass die Benutzerzuordnung durch einige Bestellungen erfolgt Konzentrieren Sie sich also auf die Klarheit des Codes (wie Sie es im Allgemeinen sollten) und fahren Sie mit 2 fort.

Oh, es ist spät und ich denke, ich versuche nur zu sagen, mach dir keine Sorgen um solche Dinge und lass dich nicht auf solche Details ein.

K.

Kevin Shea
quelle
Danke für den Tipp, ich denke, ich werde ein paar andere Sachen zeitlich festlegen, über die ich mich auch gewundert habe, hehe: D
Marcus
Wenn Sie sich eingehender mit den Auswirkungen auf die Leistung befassen möchten, sollten Sie einen Code-Profiler verwenden. Wenn nichts anderes, wird es Ihnen eine Vorstellung davon geben, welche Art von Code und Operationen die meiste Zeit in Anspruch nehmen. ProfileSharp und EqatecProfilers sind kostenlos und ausreichend, um Ihnen den Einstieg zu erleichtern.
Kevin Shea
1

Der zweite ist besser. Sie möchten in jeder Iteration einen neuen Benutzer haben.

Jarrett Widman
quelle
1

Technisch gesehen spart das erste Beispiel einige Nanosekunden, da der Stapelrahmen nicht verschoben werden muss, um eine neue Variable zuzuweisen. Dies ist jedoch so wenig CPU-Zeit, dass Sie es nicht bemerken, wenn der Compiler dies nicht tut Optimieren Sie jeden Unterschied.

Erik Funkenbusch
quelle
Ich bin mir ziemlich sicher, dass die CLR nicht bei jeder Iteration einer Schleife "eine neue Variable" zuweist.
dtb
Nun, der Compiler kann das zwar optimieren, aber für alle Variablen in einer Schleife muss Stapelspeicher zugewiesen werden. Dies wäre implementierungsabhängig und eine Implementierung könnte einfach den Stapelrahmen gleich halten, während eine andere (z. B. Mono) den Stapel freigeben und ihn dann in jeder Schleife neu erstellen könnte.
Erik Funkenbusch
16
Alle lokalen Variablen in einer Methode (oberste Ebene oder in einer Schleife verschachtelt) werden in IL zu Variablen auf Methodenebene kompiliert. Der Speicherplatz für die Variablen wird zugewiesen, bevor die Methode ausgeführt wird, nicht wenn ein Zweig mit der Deklaration in C # erreicht wird.
dtb
1
@dtb Haben Sie eine Quelle für diesen Anspruch?
Styfle
1

In diesem Szenario ist die zweite Version besser.

Wenn Sie nur auf den Wert im Hauptteil der Iteration zugreifen müssen, wählen Sie im Allgemeinen die zweite Version. Wenn es andererseits einen Endzustand gibt, den die Variable über den Rumpf der Schleife hinaus hält, deklarieren Sie und verwenden Sie dann die erste Version.

csj
quelle
0

Es sollte keinen wahrnehmbaren Leistungsunterschied geben.

Jacob Adams
quelle
0

Ich habe dieses Problem überprüft. Überraschenderweise stellte ich bei meinen schmutzigen Tests fest, dass die 2. Option immer etwas schneller ist.

namespace Test
{
  class Foreach
  {
    string[] names = new[] { "ABC", "MNL", "XYZ" };

    void Method1()
    {
      List<User> list = new List<User>();
      User u;

      foreach (string s in names)
      {
        u = new User();
        u.Name = s;
        list.Add(u);
      }
    }

    void Method2()
    {

      List<User> list = new List<User>();

      foreach (string s in names)
      {
        User u = new User();
        u.Name = s;
        list.Add(u);
      }
    }
  }

  public class User { public string Name; }
}

Ich habe die CIL überprüft, aber sie ist nicht identisch.

Geben Sie hier die Bildbeschreibung ein

Also habe ich etwas vorbereitet, was ich viel besser testen wollte.

namespace Test
{
  class Loop
  { 

    public TimeSpan method1 = new TimeSpan();
    public TimeSpan method2 = new TimeSpan();

    Stopwatch sw = new Stopwatch();

    public void Method1()
    {
      sw.Restart();

      C c;
      C c1;
      C c2;
      C c3;
      C c4;

      int i = 1000;
      while (i-- > 0)
      {
        c = new C();
        c1 = new C();
        c2 = new C();
        c3 = new C();
        c4 = new C();        
      }

      sw.Stop();
      method1 = method1.Add(sw.Elapsed);
    }

    public void Method2()
    {
      sw.Restart();

      int i = 1000;
      while (i-- > 0)
      {
        var c = new C();
        var c1 = new C();
        var c2 = new C();
        var c3 = new C();
        var c4 = new C();
      }

      sw.Stop();
      method2 = method2.Add(sw.Elapsed);
    }
  }

  class C { }
}

Auch in diesem Fall hat die 2. Methode immer gewonnen, aber dann habe ich überprüft, dass die CIL keinen Unterschied festgestellt hat.

Geben Sie hier die Bildbeschreibung ein

Ich bin kein CIL-lesender Guru, aber ich sehe kein Problem mit der Dekleration. Wie bereits erwähnt, handelt es sich bei der Erklärung nicht um eine Zuteilung, daher gibt es keine Leistungseinbußen.

Prüfung

namespace Test
{
  class Foreach
  {
    string[] names = new[] { "ABC", "MNL", "XYZ" };

    public TimeSpan method1 = new TimeSpan();
    public TimeSpan method2 = new TimeSpan();

    Stopwatch sw = new Stopwatch();

    void Method1()
    {
      sw.Restart();

      List<User> list = new List<User>();
      User u;

      foreach (string s in names)
      {
        u = new User();
        u.Name = s;
        list.Add(u);
      }

      sw.Stop();
      method1 = method1.Add(sw.Elapsed);
    }

    void Method2()
    {
      sw.Restart();

      List<User> list = new List<User>();

      foreach (string s in names)
      {
        User u = new User();
        u.Name = s;
        list.Add(u);
      }

      sw.Stop();
      method2 = method2.Add(sw.Elapsed);
    }
  }

  public class User { public string Name; }
Ucho
quelle