Tôi đang xem xét sử dụng một cái gì đó như StackFrame stackFrame = new StackFrame(1)
để đăng nhập phương thức thực thi, nhưng tôi không biết về ý nghĩa hiệu suất của nó. Là ngăn xếp dấu vết một cái gì đó được xây dựng anyway với mỗi cuộc gọi phương pháp để hiệu suất không phải là một mối quan tâm hoặc là nó cái gì đó chỉ xây dựng khi được yêu cầu? Bạn có khuyên bạn nên chống lại nó trong một ứng dụng mà hiệu suất là rất quan trọng? Nếu vậy, điều đó có nghĩa là tôi nên vô hiệu hóa nó cho bản phát hành?Hiệu suất của StackFrame như thế nào?
Trả lời
chỉnh sửa: Một số nền
Chúng tôi có một tính năng tương tự mà bị vô hiệu hóa 99% thời gian; chúng tôi đã sử dụng một cách tiếp cận như:
public void DoSomething()
{
TraceCall(MethodBase.GetCurrentMethod().Name);
// Do Something
}
public void TraceCall(string methodName)
{
if (!loggingEnabled) { return; }
// Log...
}
TraceCall(MethodBase.GetCurrentMethod().Name)
Đó là đơn giản, nhưng bất kể có hay không truy tìm được kích hoạt, chúng tôi đã phải gánh chịu hiệu suất trúng của việc sử dụng Reflection để tra cứu tên phương pháp.
Tùy chọn của chúng tôi là yêu cầu mã nhiều hơn trong mọi phương pháp (và rủi ro đơn giản hoặc từ chối) hoặc chuyển sang sử dụng StackFrame
để xác định phương thức gọi chỉ khi ghi nhật ký được bật.
Lựa chọn A:
public void DoSomething()
{
if (loggingEnabled)
{
TraceCall(MethodBase.GetCurrentMethod().Name);
}
// Do Something
}
public void TraceCall(string methodName)
{
if (!loggingEnabled) { return; }
// Log...
}
Lựa chọn B:
public void DoSomething()
{
TraceCall();
// Do Something
}
public void TraceCall()
{
if (!loggingEnabled) { return; }
StackFrame stackFrame = new StackFrame(1);
// Log...
}
Chúng tôi đã lựa chọn Lựa chọn B. Nó cung cấp cải tiến hiệu suất đáng kể so với lựa chọn A khi đăng nhập bị vô hiệu hóa, 99% thời gian và rất đơn giản để triển khai.
Dưới đây là một thay đổi mã của Michael, để hiển thị các chi phí/lợi ích của phương pháp này
using 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
}
}
}
Kết quả:
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
Điểm tuyệt vời: thông thường, bạn có thể lùi lại và tìm cách khác để giải quyết vấn đề. StackFrame() mới không đắt nếu bạn không bao giờ cần phải gọi nó. –
@Michael: chính xác! nó rất sạch sẽ để thực hiện, dễ dàng để đơn vị kiểm tra, và thời gian duy nhất chúng tôi thực hiện hit hiệu suất là khi chúng tôi về cơ bản gỡ lỗi và hiệu suất không phải là một mối quan tâm anyways. – STW
Tôi đoán nó phụ thuộc vào tần suất bạn truy tìm sẽ được bật sau đó vì tùy chọn B vẫn chậm hơn 10 lần so với tùy chọn A nếu truy tìm được bật. Nhưng bây giờ mọi người có thể tự quyết định dựa trên sự thật. Rất sâu sắc. Cảm ơn! –
Từ các tài liệu MSDN, nó có vẻ như StackFrames đang được tạo ra tất cả các thời gian:
Một StackFrame được tạo ra và đẩy vào các cuộc gọi stack cho mỗi cuộc gọi chức năng thực hiện trong việc thực hiện một chủ đề. Khung ngăn xếp luôn bao gồm Thông tin về MethodBase và tùy chọn bao gồm tên tệp, số dòng và thông tin về số cột.
Các constructor new StackFrame(1)
bạn muốn gọi sẽ làm điều này:
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
là một phương pháp bên ngoài. CalculateFramesToSkip
có một vòng lặp hoạt động chính xác một lần, vì bạn đã chỉ định 1 khung hình. Mọi thứ khác trông khá nhanh.
Bạn đã thử đo phải mất bao lâu để tạo ra, ví dụ, 1 triệu trong số đó?
Cảm ơn Paul. Tôi đọc cùng một đoạn trong MSDN và cố gắng theo dõi cách nó hoạt động trong Reflector, nhưng không thực sự hiểu nó. Tôi đã nghĩ về việc định thời gian, nhưng nó có thể gây hiểu lầm nếu nó được lưu trữ bằng cách nào đó sau cuộc gọi đầu tiên đó là lý do tại sao tôi hy vọng ai đó biết nó hoạt động như thế nào trong nội bộ. –
Kiểm tra của Michael cho thấy rằng các khung ngăn xếp không được "tạo và đẩy vào ngăn xếp cuộc gọi cho mọi cuộc gọi hàm được thực hiện trong quá trình thực thi một chuỗi" bởi vì nó mất nhiều thời gian hơn để yêu cầu. –
Hoặc chúng có thể tạo chúng khác nhau hoặc có thể là cấu trúc gốc cho đến khi được yêu cầu hoặc tài liệu có thể sai. Tôi cho rằng CLR cần phải theo dõi ngăn xếp được quản lý, vì vậy phải có * cái gì đó * trong thiết lập bộ nhớ riêng khi chồng phát triển. –
Một kiểm tra nhanh chóng và ngây thơ chỉ ra rằng đối với mã hiệu suất nhạy cảm, vâng, bạn muốn chú ý đến điều này:
Đừng tạo 100K khung: 3ms
Tạo 100K khung: 1805ms
Khoảng 20 micro giây trên mỗi khung được tạo, trên máy của tôi. Không nhiều, nhưng một sự khác biệt có thể đo lường được qua một số lượng lớn các lần lặp lại.
Phát biểu với câu hỏi sau ("Tôi có nên tắt tính năng StackFrame trong ứng dụng của mình không?"), Tôi khuyên bạn nên phân tích ứng dụng của mình, thực hiện các kiểm tra hiệu suất như tôi đã thực hiện ở đây và xem hiệu suất khác biệt cho bất kỳ thứ gì với khối lượng công việc của bạn.
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);
}
}
}
}
Cảm ơn Michael! Tôi thích cách bạn thử nghiệm này kể từ khi tôi đã quan tâm đến.NET khuôn khổ bằng cách nào đó bộ nhớ đệm phương pháp, nhưng thử nghiệm của bạn dường như cho thấy rằng đây không phải là trường hợp. –
Vâng, tôi không chắc chắn làm thế nào để vuông bài Paul tham chiếu với kinh nghiệm và bài kiểm tra của tôi. Tôi cũng đã đi qua các tài liệu log4net, mà các cuộc gọi một cách rõ ràng gọi ra cách chậm tạo stackframes (để đưa vào các bản ghi) là. –
Tôi lặp lại thử nghiệm này áp dụng [MethodImpl (MethodImplOptions.NoInlining)] cho mỗi phương pháp chỉ trong trường hợp và tôi cũng nhận được khoảng 20µs cho mỗi StackFrame với 100k lặp và khoảng 15µs cho mỗi StackFrame với 1 triệu lần lặp. 15-20 micro giây dường như không quá tệ. 50 Tác phẩm StackFrame chỉ mất 1 mili giây sau khi tất cả. –
Tôi đang xem xét sử dụng một cái gì đó giống như StackFrame StackFrame = new StackFrame (1) để đăng nhập phương thức thực hiện
Không quan tâm: Tại sao? Nếu bạn chỉ muốn phương pháp hiện tại, thì
string methodName = System.Reflection.MethodBase.GetCurrentMethod().Name;
có vẻ tốt hơn. Có lẽ không thực hiện nhiều hơn (tôi không so sánh, nhưng Reflection cho thấy GetCurrentMethod() không đơn giản tạo ra một StackFrame nhưng lại có một số "ma thuật"), nhưng rõ ràng hơn trong ý định của nó.
Tôi đã thử nghiệm của Michael với MethodBase.GetCurrentMethod() và bạn nói đúng, nó thực sự nhanh hơn. Mất khoảng 1 micro giây cho mỗi cuộc gọi trên máy của tôi. –
Tôi chỉ nhìn thấy câu trả lời của yoooder, anh ấy làm cho một điểm tốt. –
Tôi nghĩ Paul Williams đánh vào đầu bằng công việc đang được thực hiện. Nếu bạn đào sâu hơn vào StackFrameHelper
bạn sẽ thấy rằng fNeedFileInfo
thực sự là kẻ giết người hiệu suất - đặc biệt là ở chế độ gỡ lỗi. Thử đặt nó false nếu hiệu suất là quan trọng. Bạn sẽ không nhận được nhiều thông tin hữu ích trong chế độ phát hành.
Nếu bạn vượt qua false tại đây bạn sẽ vẫn nhận được tên phương thức khi thực hiện ToString()
và không xuất ra bất kỳ thông tin nào, chỉ cần di chuyển con trỏ ngăn xếp xung quanh, nó rất nhanh.
Tôi biết ý bạn là gì, nhưng kết quả ví dụ này vượt quá. Thực hiện GetCurrentMethod ngay cả khi đăng nhập được tắt là một sự lãng phí. Nó phải là một cái gì đó như:
if (loggingEnabled) TraceCall(MethodBase.GetCurrentMethod());
Hoặc nếu bạn muốn TraceCall luôn thực hiện:
TraceCall(loggingEnabled ? MethodBase.GetCurrentMethod() : null);
Tôi biết đây là một bài cũ, nhưng chỉ trong trường hợp bất cứ ai đi qua nó ở đó là thay thế khác nếu bạn đang nhắm mục tiêu .Net 4.5
Bạn có thể sử dụng thuộc tính CallerMemberName để xác định tên phương thức gọi. Nó nhanh hơn nhiều so với phản xạ hoặc StackFrame. Đây là kết quả từ một bài kiểm tra nhanh lặp lại hàng triệu lần. StackFrame là cực kỳ chậm so với sự phản ánh, và thuộc tính mới làm cho cả hai trông giống như họ đang đứng yên. Điều này đã được chạy trong IDE.
Reflection Kết quả: 00: 00: 01,4098808
StackFrame quả 00:00:06.thuộc tính 2002501
CallerMemberName quả: 00: 00: 00,0042708
Xong
Sau đây là từ exe biên soạn: Reflection Kết quả: 00: 00: 01,2136738 StackFrame quả 00: 00: 03.6343924 CallerMemberName thuộc tính Kết quả: 00: 00: 00.0000947 Xong
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 = "")
{
}
Lý do thuộc tính này nhanh hơn là do trình biên dịch thực sự ghi dữ liệu này vào hình ảnh kết quả trực tiếp. Trong tất cả những nơi mà một phương thức sử dụng các thuộc tính dịch vụ trình biên dịch được gọi, các giá trị được thay thế bằng bất kỳ trình biên dịch nào được yêu cầu cung cấp (CallerMemberName, CallerFileName, CallerLineNumber). Về cơ bản nó là viết lại nhị phân. Điều này có thể được xác nhận bằng cách sử dụng công cụ ILDASM. – PSGuy
Điều này không nhận được loại của người gọi hoặc, mà rõ ràng là chỉ có sẵn thông qua một khung stack, và một phần thiết yếu của việc tìm ra nơi mà một cái gì đó đến từ. – StingyJack
- 1. Hiệu suất trình giả lập Android so với hiệu suất thiết bị thực như thế nào?
- 2. Hiệu suất của Bộ đếm hiệu suất
- 3. Hiệu suất bắt ngoại lệ StopIteration như thế nào?
- 4. z-index, ảnh hưởng đến hiệu suất như thế nào?
- 5. Nâng cao hiệu suất của webView (nên có cùng hiệu suất như Trình duyệt web gốc)
- 6. mysql như hiệu suất tăng
- 7. Dateadd ảnh hưởng như thế nào đến hiệu suất của truy vấn SQL?
- 8. Hiệu suất của khung thực thể 4 so với khung thực thể 3.5 như thế nào?
- 9. Hiệu suất của Spark View Engine so với ASP.NET như thế nào?
- 10. Thay thế cho Java Bitset với mảng như hiệu suất?
- 11. Lợi thế hiệu suất của MySQL Enum?
- 12. Làm thế nào để cải thiện hiệu suất của FtpWebRequest?
- 13. Hiệu suất của Fortran
- 14. Hiệu suất của OracleBulkCopy
- 15. Hiệu suất của ArrayList
- 16. hiệu suất của ActiveRecord SessionStore
- 17. Hiệu suất của Arrays.asList
- 18. Hiệu suất của Interlocked.Increment
- 19. Hiệu suất của dynamic_cast?
- 20. Hiệu suất của jQuery Parallax/Scroll Hiệu suất
- 21. Hiệu suất của Java Enums?
- 22. Tần suất xhr.upload.onProgress cháy như thế nào?
- 23. Hiệu suất của Boost Python
- 24. Hiệu suất của Android HttpClient
- 25. Hiệu suất JIT của LLVM
- 26. Hiệu suất của marshaller Java
- 27. Hiệu suất của DataInputStream \ DataOutputStream
- 28. Hiệu suất của ID dài
- 29. DBMS ảnh hưởng như thế nào đến hiệu suất của ứng dụng? Và các công cụ GUI của Informix?
- 30. Hiệu suất của Java ResourceBundle
Hãy coi chừng có một SO-bot trả lời tất cả các câu hỏi như sau: Hãy thử! – harpo
Liệu nhật ký này có luôn được bật không? Cách tiếp cận thay thế là gì? Kết quả của Michael rất thú vị, nhưng câu hỏi thực sự là "cách tiếp cận này xếp chồng lên X như thế nào?" – STW
@Yoooder: câu hỏi hay. Tôi nghĩ rằng "X" chúng tôi so sánh điều này là ghi nhật ký mà không có thông tin về phương thức hoặc với thông tin phương thức được tạo tĩnh. "Tôi có nên tắt tính năng này không?": Thử nghiệm trong ứng dụng của bạn và xem liệu sự khác biệt về hiệu suất có thể đo lường hay đáng chú ý với khối lượng công việc của bạn hay không. –