Welcome to AspAdvice Sign in | Join | Help

manik.net

alles über c# / microsoft asp.net und viel, viel mehr
C# und ASP.Net Performance Optimierungen

Hier mal in Stichworten ein paar Grundsätze die man beim Entwicklen beachten sollte, wenn Laufzeit-Performance eine Rolle spielt:

C# Performance Optimierungen:

  • Lieber wenig große wie viel kleine Assemblys
  • „sealed“-Klassen wann immer möglich bei geerbten Typen mit vielen virtuellen Funktionen. Die virtuellen aufrufe werden dann „inline“ teil der Klasse.
  • „Equals“ überschreiben bei Value-Types  ( struct valuetype ) , vermeidet Reflektionskosten
  • Propertys sind teurer wie öffentliche Attribute. Bei einfach switches eher öffentliche Attribute verwenden (z.B: public bool Debug).
  • Boxing vermeiden! Fixe Typen wann es geht.
  • for anstatt foreach
  • so wenig funktionsaufruf in einem loop wie möglich, d.h. auch rekursive Aufrufe vermeiden - auch Property zugriffe sollte so gut wie möglich vermieden werden 
  •  „jagged arrays“ anstelle von multidimensionalen arrays.
    string[][] jaggedArray = new string[2][];
    jaggedArray[0] = new string[4];
    jaggedArray[1] = new string[1];
    jaggedArray[0][1] = “performance!”;

    MSIL kann eindimensionale Arrays besser optimieren wie mehrdimensionale.
    Auf MSIL ebene sieht man den unterschied:

    int [,] secondarr = new int[1, 2];
    secondarr[0, 0] = 40;

    MSIL:

    IL_0029: ldc.i4.s   40
    IL_002b: call instance void int32[0...,0...]::Set(int32, int32,in32)

    Mit einem Jaggedarray sieht das ganze dann auf MSIL so aus:

    IL_001c:  ldc.i4.s   40
    IL_001e:  stelem.i4

    stelem = „store an element“

    Bei mehrdimensionalen Arrays wird der ganze „Generic Type“-Kram also betrieben was einiges an Overhead erzeugt.
  • Bei string vergleichen wo Groß-/Kleinschreibung ignoriert werden soll CompareTo anstatt a.ToLower()==b.ToLower() da die ToLower operation zusätzliche strings erzeugt
  • (try/catch)-blöcke vermeiden, lieber den Code sicher schreiben (null checks etc.) und gut prüfen


ASP .Net Optimierungen:

  • Roundtrips vermeiden: Server.Transfer anstelle von Response.Redirect  (es wird kein Responseheader zurück geschickt der einen Redirect auf dem Clienten verursacht, d.h. die Url im Browser ändert sich auch nicht, allerdings spart man dadurch einen Roundtrip)
  • Lange Control.ID zuweisungen vermeiden
  • Tiefe Control Schachtelungen vermeiden
  • Viewstate ausschalten wenn nicht benötigt, am besten nicht per Control sondern per page
    wann?
    -> die seite macht kein postback mit informationen verschiedener controls (5 textboxen auf einer seite)
    -> controls haben keinen dynamischen inhalt oder werden mit jedem postback neu befüllt
  • Page.IsPostBack property verwenden um eine mehrfach initialisierung zu vermeiden
  • Page.DataBind() vermeiden -> jedes control das DataBinding unterstützt wird dann gebundend. -> notwendige controls einzeln binden
  • Eval(##) vermeiden lieber explizit Casten und Daten von hand auswerten, spart Reflektionskosten

     

Sponsor
(Child) Controls finden auf der gesamten Page

Leider bietet ja FindControl nur die möglichkeit das/die aktuelle Control/Page zu durchsuchen. Was ist aber wenn man ein Childcontrol eines nachbar Controls sucht auf das die aktuelle Page/Control kein Zugriff hat? Man braucht eine Funktion mit der man rekursiv nach einer Control.ID suchen kann. Diese Funktion gibts nicht direkt im .net Framework aber auf meinem Blg und zwar hier:

   1: T FindControl<T>(string id, Control root) where T : class
   2: {
   3:  
   4:     if (root == null)
   5:         return null; 
   6:  
   7:     Control control = root.FindControl(id); 
   8:  
   9:     if (control != null)
  10:         return control as T; 
  11:  
  12:     foreach (Control childControl in root.Controls)
  13:     {    
  14:         if (childControl == null)
  15:             continue; 
  16:  
  17:         if (childControl.Controls.Count <= 0)
  18:             continue; 
  19:  
  20:         T match = FindControl<T>(id, childControl); 
  21:  
  22:         if (match != null)
  23:             return match;
  24:     }
  25:     return null;
  26: }
  27:  

Wenn wir ein Updatepanel von einem Customcontrol aus auf der gesamten Page suchen mit dem namen "MyAjaxPanel" dann würde die Funktion wie folgt gerufen:

UpdatePanel updatePanel = FindControl<UpdatePanel>("WatchlistAjaxPanel", this.Page)

Man übergibt also den Typen des Controls das man sucht, das erspart uns überflüssiges type-casting. Falls der Typ unbekannt ist kann man aber auch ganz einfach nach dem "Control" Typen suchen, also:

FindControl<Control>("WatchlistAjaxPanel", this.Page)
Ajax UpdatePanel.Refresh() ausl&#246;sen per Javascript

Vor ein paar Tagen musste ich ein Ajax UpdatePanel.Refresh() per JavaScript auslösen, der Haken daran war es mussten auch noch Zusatzinformationen an den Server übertragen werden.
Die Lösung ist man ruft die JavaScript Funktion "__doPostBack" aus und übergibt als EventTarget die ClientID des UpdatePanel das zu refreshen gilt. Die Zusatzinformationen werden dem EventArgument Parameter der "__doPostBack" Funktion übergeben.
Wenn man keine Parameter bzw. Zusatzinfos an den Server übertragen möchte - also nur das Updatepanel refreshen ist das ganze ziemlich einfach.

Javascript Code:

function refreshUpdatePanel( updatePanelClientID )
{ 
    __doPostBack( updatePanelClientID, '' );
}

Dazu der passende C# Code:

button.OnClientClick = 
    "BLOCKED SCRIPTrefreshUpdatePanel( '" + updatePanel.ClientID + "' );"+
    "return false;";
Zu beachten ist hier lediglich das "return false" am ende des OnClientClick JavaScripts.
Dieses verhindert einen asp.net postback zurück an den server.

Wenn man Parameter/Zusatzinformationen an den Server zurück schicken möchte wird das ganze allerdings etwas komplizierter.

Javascript Code:

function refreshUpdatePanelParameterized( updatePanelClientID, eventArgumentName )
{
    // do javascript stuff to populate event arguments here, like get all checked checkboxes
    // or other clientside control content and send it back to the server
    // i'll use "nothing" as argument placeholder here
    var eventArguments = 'nothing';
    
    __doPostBack( updatePanelClientID, eventArgumentName + '_#_' + eventArguments);
}

C# Code:

protected void Page_Init(object sender, EventArgs e)
{
    if ( Request.Params["__EVENTTARGET"] == updatePanel.ClientID )
    {
        string[] eventsArgs = Request.Params["__EVENTARGUMENT"].Split( new string[]{"_#_"}, StringSplitOptions.RemoveEmptyEntries );
        string argumentName = eventsArgs[0];
        string argumentValue = eventsArgs[1];
 
        System.Diagnostics.Debug.WriteLine(string.Format("Recieved command {0} with values {1}.", argumentName, argumentValue ) );
    }             
}
 
protected void Page_Load(object sender, EventArgs e)
{
    button.OnClientClick = "BLOCKED SCRIPTrefreshUpdatePanelParameterized( '" + updatePanel.ClientID + "', 'arg1' ); return false;";
}

Wie das geschulte Auge erkennt werden hier zwei Parameter übertragen, ArgumentName und ArgumentValue. Der "refreshUpdatePanelParameterized" wird der ArgumentName (hier: "arg1") übergeben und die Funktion selbst ermittelt dann noch im JavaScript die ArgumentValue (hier: "nothing" ) und schickt die Informationen als Postback an den Server.
Der Serverseitige C# code "entschlüsselt" diese Informationen wieder in argumentName und argumentValue und verwertet diese anschließend.

Performance Timing mit ‚using’ und delegates mit „Conditional“ Methoden

Letztens musste ich die (zeitliche) Performance einer sehr CPU/IO lästigen Operation verbessern.
Da die Operation in diverse kleinere Vorgänge unterteilt war musste ich die Vorgänge ermitteln, welche am meisten Zeit in Anspruch nahmen und das meiste Optimierungspotential hatten. Dafür hab ich eine Klasse entworfen die mit dem „using“-Mechanismus die Dauer einer Operation ermittelt. Dabei bin ich auch darauf gestoßen, dass der C# Compiler Methoden die das „Conditional“ Attribut haben nicht delegates zuweisen kann.

Also, nun zu der Klasse:

public class PerformanceTiming : IDisposable
{
 public delegate void LogPerformance(string value);
 
 private DateTime _startTime;
 private string _name;
 private LogPerformance _logPerformanceMethod;
 
 private const string _startMessage = "PerformanceTiming={0}: Start={1}";
 private const string _endMessage = "PerformanceTiming={0}: End={1} Duration={2}msec";
 
 public PerformanceTiming(string name)
 {
  // cannot directly use Debug.WriteLine(string) as it has the "Conditional" attribute.
  //  _logPerformanceMethod = System.Diagnostics.Debug.WriteLine(value);
  // have to use wrapper function
  _logPerformanceMethod = DebugWriteLineWrapper;
  _name = name;
  _startTime = DateTime.Now;
 
  _logPerformanceMethod(string.Format(_startMessage, _name, _startTime.ToLongTimeString()));
 }
 
 public PerformanceTiming(string name, LogPerformance logPerformanceMethod)
 {
  _logPerformanceMethod = logPerformanceMethod;
  _name = name;
  _startTime = DateTime.Now;
 
  _logPerformanceMethod(string.Format(_startMessage, _name, _startTime.ToLongTimeString()));
 }
 public void DebugWriteLineWrapper(string value)
 {
  System.Diagnostics.Debug.WriteLine(value);
 }
 
 #region IDisposable Members
 public void Dispose()
 {
  _logPerformanceMethod(string.Format(_endMessage, _name, DateTime.Now.ToLongTimeString(), (DateTime.Now - _startTime).TotalMilliseconds));
 }
#endregion
}


Mehr oder weniger simple, ja, aber es ist eigentlich auch die Idee bzw. die Anwendung der Klasse was die Sache so sinnvoll macht.

using (PerformanceTiming performanceTiming = new PerformanceTiming("HeavyWork"))

      System.Threading.Thread.Sleep(2000);

}

Damit wird der folgende Output in den Debugoutput stream geschrieben:

PerformanceTiming=HeavyWork: Start=13:12:16
PerformanceTiming=HeavyWork: End=13:12:18 Duration=1999,6672msec

Es wird also die Start-/Endzeit der Operation ausgegeben, plus die Dauer in Millisekunden.
Die Ausgabe kann natürlich ohne Probleme angepasst werden.
Man kann auch eine Methode an das PerformanceTiming Objekt übergeben, welcher dann die Ausgabestrings übergeben werden, so z.B.
Console.Writeline.

using (PerformanceTiming performanceTiming = new PerformanceTiming("HeavyWork", Console.WriteLine))

      System.Threading.Thread.Sleep(2000);
}

Man könnte  natürlich auch jede andere Funktion die nur einen (string) Parameter hat übergeben, z.B. StreamWriter.WriteLine um Direkt in eine Datei zu loggen.

Conditional Methoden und delegates, wie macht man das? Man schreibt einfach eine Wrapper funktion die nicht das [Conditional("DEBUG")] Attribut besitzt.

So wird aus:

_logPerformanceMethod = System.Diagnostics.Debug.WriteLine;

Das:

_logPerformanceMethod = DebugWriteLineWrapper; 
public void DebugWriteLineWrapper(string value) 

            System.Diagnostics.Debug.WriteLine(value); 
}

So, das war nun mein erster mehr oder weniger Sinnvoller Blogeintrag :-)

EDIT: Wie postet man eigentlich code-snippets hier?

EDIT2: I now know the answer thanks to my friend Brendan from Orcs, Goblins and .NET, I will blog about it in a few days.

Hi

Es heisst der erste Blogpost ist der schwerste. So ist es auch tatsächlich, aus diesem Grund werde ich den Blogeintrag an dieser Stelle abbrechen.

Der beleuchtete Neon-Platin Gullideckel