Autor Beitrag
Oppi35
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 95
Erhaltene Danke: 3



BeitragVerfasst: Fr 03.02.12 21:06 
Hallo Zusammen,

ich habe ein Performance-Problem mit Reflection.

Folg. Ausgangslage:
Der Codeausschnitt kommt aus einer Klasse zum einlesen von CSV-Dateien direkt in Objekte. Über ein Attribut in der Model-Klasse wird festgelegt, welche Csv-Spalte in welche Property geschrieben wird.
Die Liste infos enthält die PropertyInfos der Properties, die mit dem CSV-Attribut behaftet sind.

Einzulesende Daten:
Zeilen >10.000
Spalten ca. 100

Dauer >3 Minuten dann abgebrochen.

Nach diversen Tests habe ich rausgefunden, dass folg. Codezeile für die langsame Ausführung des Programms verantwortlich ist:
item.SetValue(obj, row[ColName], null);

Die Variable infos enthält ca. 100 Werte (=100 Spalten in der CSV-Datei).
_ImportData.Rows.Count()=10.000

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
List<PropertyInfo> infos = CsvAttributedPropertyInfos();
            List<TResult> _result = new List<TResult>();


            foreach (DataRow row in _ImportData.Rows)
            {
                TResult obj = new TResult();

                foreach (var item in infos)
                {
                    foreach (string ColName in _relevanteCsvColumns)
                    {
                        item.SetValue(obj, row[ColName], null);
                    }
                }
                _result.Add(obj);
            }


Kann mir hier jemand weiterhelfen, bzw. sagen, wie ich das Performance-Problem lösen kann?


Gruß

Frank
Kha
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 3803
Erhaltene Danke: 176

Arch Linux
Python, C, C++ (vim)
BeitragVerfasst: Fr 03.02.12 21:54 
Sicher, dass es das allein ist? Ich erreiche bei einem schnellen Versuch unter Mono 10 Millionen SetValue-Aufrufe in 7 Sekunden :!: .
Womit du aber quasi den gesamten Reflection-Aufwand verhindern könntest: Hole dir für jede Spalte einmalig den Setter über GetSetMethod und verwandle ihn über Delegate.CreateDelegate in einen stark typisierten Delegate.

_________________
>λ=

Für diesen Beitrag haben gedankt: Oppi35
Oppi35 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 95
Erhaltene Danke: 3



BeitragVerfasst: Sa 04.02.12 01:05 
Hallo Sebastian,

Du hattest Recht. Eine foreach-Schleife war zu viel. Vielen Dank.
Trotzdem würde mich noch interessieren, wie Du das genau meintest mit dem Delegate. Die Sache ist ja die, dass ich verschiedene Tabellen mit dem Programm einlesen möchte, also keine fixen Spaltentypen zur Laufzeit kenne.

Über Reflection muss ich doch eh gehen, da ich die Attribute abfragen muss. Ich kann mir die Set-Methode zwar holen; muss sie dann aber mit Invoke aufrufen, m.E. kein großer Unterschied zu dem was ich bisher mache.

Mit den 7 Sekunden über 10 Mio. SetValue Aufrufe finde ich übrigens auch interessant. Ich habe mit Excel gerade mal eine Csv-Datei mit 11.000 * 100 Feldern erstellt. Das reine einlesen der ca. 1 Mio. Felder (ohne Ladezeit der Datei) dauert bei jedem Durchlauf 19 Sekunden.

Ich werde die Sache am Wochenende auch nochmal genauer unter die Lupe nehmen.

Also vielen Dank nochmal.

Gruß
Frank
Kha
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 3803
Erhaltene Danke: 176

Arch Linux
Python, C, C++ (vim)
BeitragVerfasst: Sa 04.02.12 01:59 
user profile iconOppi35 hat folgendes geschrieben Zum zitierten Posting springen:
m.E. kein großer Unterschied zu dem was ich bisher mache.
M.E. schon ;) .
ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
class MainClass
{
  public string Foo { get; set; }
  
  public static void Main(string[] args)
  {
    var m = new MainClass();
    var p = m.GetType().GetProperty("Foo");
    var f = (Action<MainClass, string>) Delegate.CreateDelegate(typeof(Action<MainClass, string>), p.GetSetMethod());
    
    var sw = Stopwatch.StartNew();
    for (int i = 0; i < 10000000; i++)
      p.SetValue(m, "sdf"null);
    Console.WriteLine(sw.ElapsedMilliseconds);
    
    sw = Stopwatch.StartNew();
    for (int i = 0; i < 10000000; i++)
      f(m, "sdf");
    Console.WriteLine(sw.ElapsedMilliseconds);
    
    sw = Stopwatch.StartNew();
    for (int i = 0; i < 10000000; i++)
      m.Foo = "sdf";
    Console.WriteLine(sw.ElapsedMilliseconds);
  }
}

ausblenden Quelltext
1:
2:
3:
9951
71
9

_________________
>λ=

Für diesen Beitrag haben gedankt: Oppi35
Ralf Jansen
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 4708
Erhaltene Danke: 991


VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
BeitragVerfasst: Sa 04.02.12 02:00 
Zitat:
Ich kann mir die Set-Methode zwar holen; muss sie dann aber mit Invoke aufrufen,


Nicht wenn du wie erwähnt daraus einen Delegaten machst. Denn kannst du natürlich ganz normal aufrufen. Wobei ich denke das du das häufiger machen musst als Kha andeutet.
Je Row und nicht je Spalte.

Zitat:
Das reine einlesen der ca. 1 Mio. Felder (ohne Ladezeit der Datei) dauert bei jedem Durchlauf 19 Sekunden.


Eine Datatable ist jetzt nicht gerade bekannt für atemberaubende Geschwindigkeit. Sowas z.B.

Zitat:
row[ColName]


kann sich bei einer gewissen Aufrufhäufigkeit zu einiger Zeit summieren. Die Felder in einer DataRow sind eine simple indexbasierte Liste.
Das ständige ~umrechnen~ Name nach Index kostest einmalig nicht viel. Millionenfach .... hmmm. Wenn einfach möglich besser gleich die Spaltenindizes verwenden.

Für diesen Beitrag haben gedankt: Oppi35
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4796
Erhaltene Danke: 1059

Win10
C#, C++ (VS 2017/19/22)
BeitragVerfasst: Sa 04.02.12 11:17 
Hallo Frank,

ich sehe es auch so wie Ralf, daß du einmalig die Indizes für die Spalten dir in einem Array merken solltest (mittels DataTable.Columns.IndexOf(...) und dann über
ausblenden C#-Quelltext
1:
row[ column_indices[i] ]					

auf die einzelnen Werte schneller zugreifen kannst.

Generell solltest du dir aber für solche Sachen einen Profiler zulegen, z.B. einen von csharp-source.net/open-source/profilers

Für diesen Beitrag haben gedankt: Oppi35
Oppi35 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 95
Erhaltene Danke: 3



BeitragVerfasst: Fr 10.02.12 10:33 
Hallo Zusammen,

vielen Dank an alle Helfer.

Kha, Dein Beitrag mit den drei Vergleichen war sehr Hilfreich. Mein Programm funktioniert jetzt.

Gruß

Frank