Ich erwäge, etwas wie StackFrame stackFrame = new StackFrame(1)
zu verwenden, um die ausführende Methode zu protokollieren, aber ich weiß nicht über seine Auswirkungen auf die Leistung. Ist der Stack-Trace etwas, das bei jedem Methodenaufruf ohnehin erstellt wird, sodass die Performance keine Rolle spielt oder nur auf Nachfrage erstellt wird? Empfehlen Sie dagegen in einer Anwendung, in der die Leistung sehr wichtig ist? Wenn ja, heißt das, ich sollte es für die Veröffentlichung deaktivieren?Wie leistungsfähig ist StackFrame?
Antwort
edit: Einige Hintergrund
Wir haben eine ähnliche Funktion haben, die 99% der Zeit deaktiviert ist; wir wurden mit einem Ansatz wie:
public void DoSomething()
{
TraceCall(MethodBase.GetCurrentMethod().Name);
// Do Something
}
public void TraceCall(string methodName)
{
if (!loggingEnabled) { return; }
// Log...
}
TraceCall(MethodBase.GetCurrentMethod().Name)
Es war einfach, aber unabhängig davon, ob oder nicht, wurde Tracing aktiviert wir die Performance-Einbußen Reflection wurden entstehen für die Verwendung der Methode Namen nachzuschlagen.
waren unsere Optionen entweder zu mehr Code in jeder Methode (und Risiko einfache Fehler oder Verweigerung) oder wechseln zu verwenden StackFrame
erfordern die Rufmethode nur wenn die Protokollierung aktiviert wurde, um zu bestimmen.
Option A:
public void DoSomething()
{
if (loggingEnabled)
{
TraceCall(MethodBase.GetCurrentMethod().Name);
}
// Do Something
}
public void TraceCall(string methodName)
{
if (!loggingEnabled) { return; }
// Log...
}
Option B:
public void DoSomething()
{
TraceCall();
// Do Something
}
public void TraceCall()
{
if (!loggingEnabled) { return; }
StackFrame stackFrame = new StackFrame(1);
// Log...
}
Wir für Option B entschieden es erhebliche Leistungsverbesserungen gegenüber Option bietet ein bei der Anmeldung deaktiviert ist, 99% der Zeit und ist sehr einfach zu implementieren.
Hier ist eine Änderung von Michaels Code,
die Kosten/Nutzen dieses Ansatzes anzuzeigenusing System;
using System.Diagnostics;
using System.Reflection;
namespace ConsoleApplication
{
class Program
{
static bool traceCalls;
static void Main(string[] args)
{
Stopwatch sw;
// warm up
for (int i = 0; i < 100000; i++)
{
TraceCall();
}
// call 100K times, tracing *disabled*, passing method name
sw = Stopwatch.StartNew();
traceCalls = false;
for (int i = 0; i < 100000; i++)
{
TraceCall(MethodBase.GetCurrentMethod());
}
sw.Stop();
Console.WriteLine("Tracing Disabled, passing Method Name: {0}ms"
, sw.ElapsedMilliseconds);
// call 100K times, tracing *enabled*, passing method name
sw = Stopwatch.StartNew();
traceCalls = true;
for (int i = 0; i < 100000; i++)
{
TraceCall(MethodBase.GetCurrentMethod());
}
sw.Stop();
Console.WriteLine("Tracing Enabled, passing Method Name: {0}ms"
, sw.ElapsedMilliseconds);
// call 100K times, tracing *disabled*, determining method name
sw = Stopwatch.StartNew();
traceCalls = false;
for (int i = 0; i < 100000; i++)
{
TraceCall();
}
Console.WriteLine("Tracing Disabled, looking up Method Name: {0}ms"
, sw.ElapsedMilliseconds);
// call 100K times, tracing *enabled*, determining method name
sw = Stopwatch.StartNew();
traceCalls = true;
for (int i = 0; i < 100000; i++)
{
TraceCall();
}
Console.WriteLine("Tracing Enabled, looking up Method Name: {0}ms"
, sw.ElapsedMilliseconds);
Console.ReadKey();
}
private static void TraceCall()
{
if (traceCalls)
{
StackFrame stackFrame = new StackFrame(1);
TraceCall(stackFrame.GetMethod().Name);
}
}
private static void TraceCall(MethodBase method)
{
if (traceCalls)
{
TraceCall(method.Name);
}
}
private static void TraceCall(string methodName)
{
// Write to log
}
}
}
Die Ergebnisse:
Tracing Disabled, passing Method Name: 294ms
Tracing Enabled, passing Method Name: 298ms
Tracing Disabled, looking up Method Name: 0ms
Tracing Enabled, looking up Method Name: 1230ms
Ausgezeichneter Punkt: Oft können Sie einen Schritt zurückgehen und einen anderen Weg finden, um das Problem zu lösen. new StackFrame() ist nicht teuer, wenn Sie es nie aufrufen müssen. –
@Michael: genau! Es ist sehr sauber zu implementieren, einfach zu testen, und das einzige Mal, wenn wir den Performance-Hit machen, ist, wenn wir im Wesentlichen debuggen und die Performance sowieso keine Rolle spielt. – STW
Ich denke, es hängt davon ab, wie oft Sie die Ablaufverfolgung aktiviert wird, da Option B immer noch 10x langsamer ist als Option A, wenn die Ablaufverfolgung aktiviert ist. Aber jetzt kann jeder aufgrund von Fakten selbst entscheiden. Sehr aufschlussreich. Vielen Dank! –
Aus der MSDN-Dokumentation, wie es scheint Stackframes ganze Zeit erstellt werden:
A StackFrame- erstellt und Aufschieben den Call-Stack für jeden Funktionsaufruf während der Ausführung eines Threads gemacht. Der Stapelrahmen enthält immer MethodBase-Informationen und optional enthält Dateiname, Zeilennummer und Spalte Nummer Informationen.
Der Konstruktor new StackFrame(1)
Sie dies tun würde, nennen würde:
private void BuildStackFrame(int skipFrames, bool fNeedFileInfo)
{
StackFrameHelper sfh = new StackFrameHelper(fNeedFileInfo, null);
StackTrace.GetStackFramesInternal(sfh, 0, null);
int numberOfFrames = sfh.GetNumberOfFrames();
skipFrames += StackTrace.CalculateFramesToSkip(sfh, numberOfFrames);
if ((numberOfFrames - skipFrames) > 0)
{
this.method = sfh.GetMethodBase(skipFrames);
this.offset = sfh.GetOffset(skipFrames);
this.ILOffset = sfh.GetILOffset(skipFrames);
if (fNeedFileInfo)
{
this.strFileName = sfh.GetFilename(skipFrames);
this.iLineNumber = sfh.GetLineNumber(skipFrames);
this.iColumnNumber = sfh.GetColumnNumber(skipFrames);
}
}
}
GetStackFramesInternal
ist ein externes Verfahren. CalculateFramesToSkip
hat eine Schleife, die genau einmal funktioniert, da Sie nur 1 Rahmen angegeben haben. Alles andere sieht ziemlich schnell aus.
Haben Sie versucht zu messen, wie lange es dauern würde, sagen wir, 1 Million von ihnen?
Danke Paul. Ich habe den gleichen Absatz in MSDN gelesen und versucht, herauszufinden, wie es in Reflector funktioniert, habe es aber nicht wirklich verstanden. Ich habe darüber nachgedacht, es zu times, aber es könnte irreführend sein, wenn es nach dem ersten Anruf irgendwie zwischengespeichert wird, deshalb hoffte ich, dass jemand wusste, wie es intern funktionierte. –
Michaels Test schlägt vor, dass die Stack-Frames nicht für jeden Funktionsaufruf, der während der Ausführung eines Threads ausgeführt wird, "erstellt und auf den Aufruf-Stack geschoben werden", da es länger dauert, danach zu fragen. –
Oder sie können sie anders erstellen, oder es kann ein natives Konstrukt sein, bis angefordert, oder die Dokumente können falsch sein. Ich nehme an, die CLR muss den verwalteten Stack im Auge behalten, daher muss im nativen Speicher-Setup * etwas * sein, wenn der Stack wächst. –
Ein schneller und naiver Test zeigt an, dass für die erfolgskritischen Code, ja, Sie darauf aufmerksam bezahlen:
nicht generiert 100K Rahmen: 3 ms
generieren 100K Rahmen: 1805ms
Etwa 20 Mikrosekunden pro generierten Rahmen auf meinem Computer. Nicht viel, aber ein messbarer Unterschied über eine große Anzahl von Iterationen.
Mit Ihren späteren Fragen ("Sollte ich StackFrame-Generierung in meiner Anwendung deaktivieren?"), Würde ich vorschlagen, analysieren Sie Ihre Anwendung, machen Sie Leistungstests wie die, die ich hier getan habe, und sehen Sie, ob der Leistungsunterschied ergibt sich alles mit Ihrer Arbeitsbelastung.
using System;
using System.Diagnostics;
namespace ConsoleApplication
{
class Program
{
static bool generateFrame;
static void Main(string[] args)
{
Stopwatch sw;
// warm up
for (int i = 0; i < 100000; i++)
{
CallA();
}
// call 100K times; no stackframes
sw = Stopwatch.StartNew();
for (int i = 0; i < 100000; i++)
{
CallA();
}
sw.Stop();
Console.WriteLine("Don't generate 100K frames: {0}ms"
, sw.ElapsedMilliseconds);
// call 100K times; generate stackframes
generateFrame = true;
sw = Stopwatch.StartNew();
for (int i = 0; i < 100000; i++)
{
CallA();
}
Console.WriteLine("Generate 100K frames: {0}ms"
, sw.ElapsedMilliseconds);
Console.ReadKey();
}
private static void CallA()
{
CallB();
}
private static void CallB()
{
CallC();
}
private static void CallC()
{
if (generateFrame)
{
StackFrame stackFrame = new StackFrame(1);
}
}
}
}
Danke Michael! Ich mag die Art und Weise, wie Sie das getestet haben, seit ich mich um die.NET Framework irgendwie die Methode zwischenspeichern, aber Ihr Test scheint zu zeigen, dass dies nicht der Fall ist. –
Ja, ich bin mir nicht sicher, wie man den Artikel, auf den Paul sich bezieht, mit meinen Erfahrungen und Tests vergleicht. Ich bin auch durch die log4net-Dokumentation gegangen, die explizit aufruft, wie langsam generierende Stackframes (für die Aufnahme in Logs) sind. –
Ich wiederholte diesen Test mit [MethodImpl (MethodImplOptions.NoInlining)] auf jede Methode nur für den Fall und ich bekomme auch etwa 20μs für jeden StackFrame mit 100k Iterationen und etwa 15μs für jeden StackFrame mit 1 Million Iterationen. 15-20 Mikrosekunden scheint nicht so schlecht. 50 StackFrame-Kreationen benötigen immerhin nur 1 Millisekunde. –
Ich erwäge mit so etwas wie StackFrame- StackFrame- = new StackFrame- (1) zum Protokollieren der Ausführungsmethode
Aus Interesse: Warum? Wenn Sie nur die aktuelle Methode verwenden möchten, dann scheint
besser zu sein. Vielleicht nicht leistungsfähiger (ich habe nicht verglichen, aber Reflection zeigt, dass GetCurrentMethod() nicht einfach einen StackFrame erstellt, sondern "Magie" ausführt), aber klarer in seiner Absicht.
Ich versuchte Michaels Test mit MethodBase.GetCurrentMethod() und du hast Recht, es ist tatsächlich schneller. Dauert etwa 1 Mikrosekunde pro Anruf auf meinem Rechner. –
Ich habe gerade yoooders Antwort gesehen, er macht einen guten Punkt. –
Ich denke Paul Williams traf den Nagel auf den Kopf mit der Arbeit, die getan wird. Wenn Sie sich tiefer in StackFrameHelper
vertiefen, werden Sie feststellen, dass fNeedFileInfo
eigentlich der Performance-Killer ist - besonders im Debug-Modus. Versuchen Sie es false einzustellen, wenn Leistung wichtig ist. Im Freigabemodus werden Sie sowieso nicht viele nützliche Informationen erhalten.
Wenn Sie falsch hier passieren werden Sie noch Methodennamen erhalten, wenn ein ToString()
tun und ohne Informationen ausgibt, nur Stapelzeiger bewegen, es ist sehr schnell.
Ich weiß, was Sie meinen, aber dieses Beispiel Ergebnis ist ein Überschwingen. Das Ausführen der GetCurrentMethod auch bei deaktivierter Protokollierung ist eine Verschwendung. Es sollte so etwas wie sein:
if (loggingEnabled) TraceCall(MethodBase.GetCurrentMethod());
Oder wenn Sie das TraceCall wollen immer ausgeführt:
TraceCall(loggingEnabled ? MethodBase.GetCurrentMethod() : null);
Ich weiß, dass dies eine alte Post, aber nur für den Fall jemand kommt über sie es eine andere Alternative ist, Wenn Sie auf .Net 4.5 zielen.
Sie können das CallerMemberName-Attribut verwenden, um den Namen der aufrufenden Methode zu identifizieren. Es ist viel schneller als Reflection oder StackFrame. Hier sind die Ergebnisse eines millionenfach iterierten Schnelltests. StackFrame ist im Vergleich zur Reflexion extrem langsam und das neue Attribut lässt beide aussehen, als stünden sie still. Dies wurde in der IDE ausgeführt.
Reflexion Ergebnis: 00: 00: 01,4098808
StackFrame- Ergebnis 00.00.06.Attribut 2002501
CallerMemberName Ergebnis: 00: 00: 00,0042708
GetanDas Folgende ist aus den kompilierten exe: Reflection Ergebnis: 00: 00: 01,2136738 Stackframe Ergebnis 00: 00: 03,6343924 CallerMemberName Attribut Ergebnis: 00: 00: 00.0000947 Geschehen
static void Main(string[] args)
{
Stopwatch sw = new Stopwatch();
sw.Stop();
Console.WriteLine("Reflection Result:");
sw.Start();
for (int i = 0; i < 1000000; i++)
{
//Using reflection to get the current method name.
PassedName(MethodBase.GetCurrentMethod().Name);
}
Console.WriteLine(sw.Elapsed);
Console.WriteLine("StackFrame Result");
sw.Restart();
for (int i = 0; i < 1000000; i++)
{
UsingStackFrame();
}
Console.WriteLine(sw.Elapsed);
Console.WriteLine("CallerMemberName attribute Result:");
sw.Restart();
for (int i = 0; i < 1000000; i++)
{
UsingCallerAttribute();
}
Console.WriteLine(sw.Elapsed);
sw.Stop();
Console.WriteLine("Done");
Console.Read();
}
static void PassedName(string name)
{
}
static void UsingStackFrame()
{
string name = new StackFrame(1).GetMethod().Name;
}
static void UsingCallerAttribute([CallerMemberName] string memberName = "")
{
}
Der Grund, warum dieses Attribut schneller ist, liegt darin, dass der Compiler das tatsächlich direkt in das resultierende Bild einbrennt. An allen Stellen, an denen eine Methode aufgerufen wird, die die Compiler-Services-Attribute verwendet, werden die Werte durch den vom Compiler bereitgestellten Code ersetzt (CallerMemberName, CallerFileName, CallerLineNumber). Es ist im Grunde binäre Umschreiben. Dies kann mit dem ILDASM-Tool bestätigt werden. – PSGuy
Dies erhält auch nicht den Typ des Aufrufers, der offensichtlich nur über einen Stapelrahmen verfügbar ist, und ein wesentlicher Teil davon, herauszufinden, woher etwas kam. – StingyJack
- 1. Welches ist am besten Leistungsfähig: $ Digest, $ Apply und $ Timeout
- 2. Wie bekomme ich das ausführende Objekt für einen Stackframe?
- 3. Sind moderne Computer nicht leistungsfähig genug, um Strings zu handhaben, ohne Symbole zu brauchen (in Ruby)
- 4. Aus welchen Gründen sind Protokolle und Multimethoden in Clojure weniger leistungsfähig für Polymorphie als Typklassen in Haskell?
- 5. Erhalten Sie Zeilennummer, wo Ausnahme in ASP.NET Core aufgetreten ist
- 6. Was ist der leistungsfähigste Weg, um den Namen von Klasse und Methode zu protokollieren?
- 7. Was ist der Unterschied zwischen Model.findOne() und Model.findById() in Mongoose?
- 8. Wie viel Bereinigung ist für Webdienste erforderlich, die Stored Procedures aufrufen?
- 9. Warum ist numpy.dot viel schneller als numpy.einsum?
- 10. ThreadPool ist Executor wie Polling ist?
- 11. Ist es sicher, die StackTrace-Klasse zu verwenden, um den Aufrufer der aktuellen Methode zu finden?
- 12. Wie teuer ist ST_GeomFromText
- 13. Wie sicher ist PHP?
- 14. Wie langsam ist Reflection
- 15. Wie genau ist DateTime.AddDays?
- 16. Wie einzigartig ist socket.id?
- 17. Wie sicher ist openID?
- 18. Wie effizient ist WebMatrix?
- 19. Wie portabel ist mmap
- 20. Wie skalierbar ist System.Threading.Timer?
- 21. Wie nützlich ist Response.IsClientConnected?
- 22. Wie allgegenwärtig ist hash_map?
- 23. Wie tragbar ist GLib?
- 24. Ist GMT wie UTC?
- 25. Wie zuverlässig ist DRb?
- 26. Wie zuverlässig ist current_kernel_time()?
- 27. Wie einzigartig ist ManagedThreadID?
- 28. Wie ist atomic_dec_if_positive atomar?
- 29. Wie schnell ist Python?
- 30. Wie teuer ist Typschluss?
Vorsicht, es gibt einen SO-Bot, der alle Fragen beantwortet: Probieren Sie es aus! – harpo
Wird diese Protokollierung immer aktiviert? Was ist der alternative Ansatz? Michaels Ergebnisse sind interessant, aber die eigentliche Frage ist: "Wie kommt dieser Ansatz zu X?" – STW
@Yoooder: gute Fragen. Ich denke, das "X", mit dem wir das vergleichen, ist die Protokollierung ohne Methodeninformationen oder mit statisch generierten Methodeninformationen. "Sollte ich es deaktivieren?": Testen Sie in Ihrer Anwendung, und sehen Sie, ob die Leistungsunterschiede messbar oder bemerkbar sind. –