سؤال

أقوم بإجراء بعض الاختبارات المرجعية السريعة والقذرة على سطر واحد من كود C# باستخدام DateTime:

long lStart = DateTime.Now.Ticks;
// do something
long lFinish = DateTime.Now.Ticks;

المشكلة في النتائج:

Start Time [633679466564559902]
Finish Time [633679466564559902]

Start Time [633679466564569917]
Finish Time [633679466564569917]

Start Time [633679466564579932]
Finish Time [633679466564579932]

...وما إلى ذلك وهلم جرا.

نظرًا لأن أوقات البداية والانتهاء متطابقة، فمن الواضح أن القراد ليس دقيقًا بدرجة كافية.

إذًا، كيف يمكنني قياس الأداء بشكل أفضل؟

هل كانت مفيدة؟

المحلول

ال Stopwatch الفئة المتوفرة منذ .NET 2.0 هي أفضل طريقة للقيام بذلك.إنه عداد عالي الأداء للغاية ودقيق لكسور من المللي ثانية.نلقي نظرة على وثائق MSDN, ، وهو أمر واضح جدًا.

يحرر:كما اقترحنا سابقًا، يُنصح أيضًا بتشغيل التعليمات البرمجية الخاصة بك عدة مرات للحصول على متوسط ​​وقت معقول.

نصائح أخرى

قم بتنفيذ التعليمات البرمجية الخاصة بك بشكل متكرر.يبدو أن المشكلة تكمن في أن التعليمات البرمجية الخاصة بك يتم تنفيذها بشكل أسرع بكثير من دقة أداة القياس الخاصة بك.الحل الأبسط لذلك هو تنفيذ التعليمات البرمجية الخاصة بك عدة مرات (الآلاف، وربما الملايين) ثم حساب متوسط ​​وقت التنفيذ.

يحرر:أيضًا، نظرًا لطبيعة المترجمين الحاليين الذين يقومون بالتحسين (والأجهزة الافتراضية مثل CLR وJVM) قد يكون من المضلل جدًا قياس سرعة تنفيذ الأسطر الفردية من التعليمات البرمجية، نظرًا لأن القياس يمكن أن يؤثر على السرعة كثيرًا.قد يكون النهج الأفضل هو تحديد ملف تعريف النظام بأكمله (أو على الأقل الكتل الأكبر حجمًا) والتحقق من مكان الاختناقات.

وأجد هذه مفيدة

http://accelero.codeplex.com/SourceControl/changeset/view/ 22633 # 290971 http://accelero.codeplex.com/SourceControl/changeset/view/22633#290973 http://accelero.codeplex.com/SourceControl/changeset/view/22633#290972

وTickTimer هو خفض نسخة من ساعة التوقيت الذي يبدأ عندما شيدت ولا يدعم استئناف. وسوف يخطر لك أيضا إذا لم الجهاز الحالي سيدعم قرار توقيت عالية (ساعة التوقيت يبتلع هذه المشكلة)

وحتى هذه

var tickTimer = new TickTimer();
//call a method that takes some time
DoStuff();
tickTimer.Stop();
Debug.WriteLine("Elapsed HighResElapsedTicks " + tickTimer.HighResElapsedTicks);
Debug.WriteLine("Elapsed DateTimeElapsedTicks " + tickTimer.DateTimeElapsedTicks);
Debug.WriteLine("Elapsed ElapsedMilliseconds " + tickTimer.ElapsedMilliseconds);
Debug.WriteLine("Start Time " + new DateTime(tickTimer.DateTimeUtcStartTicks).ToLocalTime().ToLongTimeString());

وسوف يظهر هذا

Elapsed HighResElapsedTicks 10022886
Elapsed DateTimeElapsedTicks 41896
Elapsed ElapsedMilliseconds 4.18966178849554
Start Time 11:44:58

وDebugTimer هو مجمع لTickTimer التي من شأنها إرسال النتيجة إلى تصحيح. (لاحظ: أنها تدعم نمط المتاح)

وحتى هذه

using (new DebugTimer("DoStuff"))
{
    //call a method that takes some time
    DoStuff();
}

وسوف يظهر هذا في إطار التصحيح

DoStuff: Total 3.6299 ms

وIterationDebugTimer هو لتوقيت الزمن الذي يستغرقه لتشغيل العملية عدة مرات وإرسال النتيجة إلى تصحيح. فإنه سيتم أيضا إجراء جولة الأولي التي لم يتم تضمين ذلك لتجاهل وقت بدء التشغيل. (لاحظ: أنها تدعم نمط المتاح)

وحتى هذه

int x;
using (var iterationDebugTimer = new IterationDebugTimer("Add", 100000))
{
    iterationDebugTimer.Run(() =>
    {
        x = 1+4;
    });
}

وسيتم إخراج هذا

Add: Iterations 100000 
Total 1.198540 ms 
Single 0.000012 ms

وفقط لإضافة للآخرين ما قاله بالفعل حول استخدام ساعة توقيت وقياس متوسط.

تأكد من استدعاء الأسلوب الخاص بك قبل القياس. وإلا سوف قياس الوقت اللازم لJIT ترجمة التعليمات البرمجية أيضا. وهذا قد تحرف الأرقام الخاصة بك بشكل كبير.

وبالإضافة إلى ذلك، تأكد من قياس رمز وضع الافراج كما يتم تشغيل أمثل إيقاف افتراضيا ليبني التصحيح. ضبط تصحيح رمز هو إيمهو لا طائل منه.

وتأكد من أنك قياس ما تريد في الواقع لقياس. عندما ركلة تحسينات في ومترجم / JIT المترجم قد إعادة ترتيب الشفرة أو إزالته تماما، لذلك قد ينتهي بك الأمر قياس شيئا مختلفا قليلا من المقصود. على الأقل نلقي نظرة على الشفرة التي تم إنشاؤها لجعل لم يتم تجريد كود متأكدا.

واعتمادا على ما كنت تحاول قياس نضع في اعتبارنا، أن نظام حقيقي سيؤكد وقت تشغيل مختلف عن تطبيق اختبار نموذجي. ترتبط بعض مشاكل الأداء لمثل كيفية تجميع البيانات المهملة الكائنات. وهذه المشاكل عادة لا تظهر في تطبيق اختبار بسيط.

في الواقع، فإن أفضل نصيحة هي لقياس أنظمة حقيقية مع بيانات حقيقية عن اختبارات رمل قد تتحول إلى أن تكون غير دقيقة للغاية.

استخدم على التعريف الحقيقي مثل dotTrace.

وعينة لفئة Stopwatch

    using System.Diagnostics;
    ......
    ...
    ..
    Stopwatch sw = new Stopwatch();
    sw.Start();
    //Your Code Here

    sw.Stop();
    Console.WriteLine("Elapsed={0}",sw.Elapsed);

ويمكنك استخدام Stopwatch، على افتراض انك تستخدم NET 2.0 أو أحدث.

System.Diagnostics.Stopwatch.StartNew();

والطبقة Stopwatch لديها أيضا حقل للقراءة فقط العام <لأ href = "https://msdn.microsoft.com/en-us/library/system.diagnostics.stopwatch.ishighresolution.aspx" يختلط = "نوفولو noreferrer" > IsHighResolution من شأنها أن تتيح لك معرفة ما إذا كان يستند على ساعة توقيت عداد الأداء عالية الدقة. إذا كان لا، لأنه يقوم على توقيت النظام.

ولست متأكدا ما يلزم لساعة توقيت لأن يستند عالية الدقة عداد الأداء. هناك بعض المكالمات API ولكن الرقم الأول إذا لم ساعة توقيت استخدام دقة عالية، ثم API قد لا يكون هناك.

وانظر جواب هل DateTime.Now أفضل طريقة لقياس أداء الدالة؟ للحصول على تفسير أو قراءة لي <لأ href = "http://blog.freakcode.com/2008/09/high-precision-performance-measurement.html "يختلط =" نوفولو noreferrer "> بلوق وظيفة عن قياس الأداء العالي

والمشكلة هي أن التاريخ والوقت لديها من القرار حول 15ms، فإنه لا يمكن أن يكون أكثر دقة من ذلك. ساعة توقيت، ومع ذلك، يمكن لل.

وهنا كتابة لطيفة حتى في MSDN حول كيفية تنفيذ باستمرار تحديث ، عالية الدقة مزود الوقت لويندوز

وهنا يكمن عينة شفرة المصدر لهذه المادة (C ++).

https://andreyakinshin.gitbooks.io/performancebookdotnet/content/science/ microbenchmarking.html

https://github.com/PerfDotNet/BenchmarkDotNet

و"في الواقع، microbencmarking من الصعب جدا إذا تستغرق عملية 10-100ns، وقياس عملية يشكل تحديا كبيرا، وأنا أقترح عليك استخدام BenchmarkDotNet للمعايير الخاصة بك. انها المكتبة التي يمكن أن تساعدك على جعل معيارا صادقا و الحصول على القياسات بدقة جيدة، وبطبيعة الحال، يمكن أن تكتب القياسي الخاص دون أي مكتبات إضافية في هذا القسم، ونحن نتحدث عن السبب في انه من المحتمل ان يكون فكرة سيئة وما يجب أن تعرفه قبل أن تبدأ. "

ويوضح هذا المقال مشروع قانون كيفية استخدام عالية توقيت أداء لقياس سرعة تنفيذ التعليمات البرمجية:

http://www.codeproject.com/KB/cs/highperformancetimercshar.aspx

وهنا يمكنك العثور على عدد من المصادر المفتوحة C # المحللون:

http://csharp-source.net/open-source/profilers

هناك خيار آخر وهو إدخال رمز المؤقت تلقائيًا فودي.وهذا يجعل قراءة التعليمات البرمجية الخاصة بك أكثر سهولة لأنه يفصل بين اهتماماتك الشاملة.أعتقد أن هذا قريب مما يسمى الجانب برمجة, ، ولكن يتم ذلك في وقت ما بعد الترجمة.

يرى https://github.com/Fody/MethodTimer للملحق fody الذي يقوم بتوقيت الطريقة.

نقلا عن التمهيدي:

باستخدام جهاز اعتراضي، في مكان ما في مجموعتك:

public static class MethodTimeLogger {
  public static void Log(MethodBase methodBase, long milliseconds)
  {
    //Do some logging here
  } 
}

الرمز الخاص بك،

public class MyClass
{
    [Time]
    public void MyMethod()
    {
        //Some code u are curious how long it takes
        Console.WriteLine("Hello");
    }
}

يتم تجميعها لهذا:

public class MyClass
{
    public void MyMethod()
    {
        var stopwatch = Stopwatch.StartNew();
        try
        {
            //Some code u are curious how long it takes
            Console.WriteLine("Hello");
        }
        finally
        {
            stopwatch.Stop();
            MethodTimeLogger.Log(methodof(MyClass.MyMethod), stopwatch.ElapsedMilliseconds);
        }
    }
}

ويمكن التخلص منها أسلوب Stopwatch يعمل على نحو أفضل بالنسبة لي.

class VWatch : IDisposable {
    Stopwatch watch = new Stopwatch();
    public VWatch() {
        this.watch.Start();
    }
    public void Dispose() {
        this.watch.Stop();
        Console.WriteLine("Finished. Elapsed={0}", this.watch.Elapsed);
    }
}

وبعد ذلك:

using (new VWatch()) {
    /// do something for time measurement
}

وبلدي الأفضليات الذهاب ل BenchmarkDotNet التي كتبهاEvgeniy المذكورة. ربما يتم تجاوز رده لأنه لا يوجد التعليمات البرمجية المتكررة، ولكن لأن هذا هو البند المعقد يستحق على الأقل أن نلقي نظرة في المكتبة قبل أن يذهب أول رئيس في شيء مفصل.

ولأن بعض التعليمات البرمجية دائما يمسك العينين، وهنا هو مثال من نقل الموقع عن:

using System;
using System.Security.Cryptography;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

namespace MyBenchmarks
{
    [ClrJob(baseline: true), CoreJob, MonoJob, CoreRtJob]
    [RPlotExporter, RankColumn]
    public class Md5VsSha256
    {
        private SHA256 sha256 = SHA256.Create();
        private MD5 md5 = MD5.Create();
        private byte[] data;

        [Params(1000, 10000)]
        public int N;

        [GlobalSetup]
        public void Setup()
        {
            data = new byte[N];
            new Random(42).NextBytes(data);
        }

        [Benchmark]
        public byte[] Sha256() => sha256.ComputeHash(data);

        [Benchmark]
        public byte[] Md5() => md5.ComputeHash(data);
    }

    public class Program
    {
        public static void Main(string[] args)
        {
            var summary = BenchmarkRunner.Run<Md5VsSha256>();
        }
    }
}

في بعض الأحيان قد يكون من الأفضل أن ننظر لماذا تحتاج إلى وقت العملية؟ هل تشغيل البطيء؟ أم أنك مجرد لافتة؟ القاعدة الأولى للالأمثل هو "لا تفعل ذلك". لذلك، اعتمادا على ما كنت قياس في الواقع، يمكن أن تغير الرأي حول ما الأداة هو الانسب لهذه المهمة.

ولقد تقدمت التمديد التي يعود ميلي ثانية من القراد.

public static int GetTotalRunningTimeInMilliseconds(this DateTime start)
{
    var endTicks = DateTime.Now.Ticks - start.Ticks;
    return TimeSpan.FromTicks(endTicks).Milliseconds;
}

والاستعمال:

 var start = DateTime.Now;

 //...your long running code here

 var endTime = start.GetTotalRunningTimeInMilliseconds();

ولقد فعلت طريقة بسيطة جدا الذي يقيس سرعة التنفيذ ل<لأ href = "https://docs.microsoft.com/en-gb/dotnet/api/system.action؟view=netframework-4.5 "يختلط =" نوفولو noreferrer "> العمل ، التي لديها بالنسبة لي الفوائد التي يمكنني إعادة استخدامها كلما كنت في حاجة إليها، وأيهما كود لدي لقياس.

وبالنسبة لي كان التاريخ والوقت ما يكفي، لكنها قابلة للتكيف بسهولة من التاريخ والوقت إلى <لأ href = "https://docs.microsoft.com/en-gb/dotnet/api/system.diagnostics.stopwatch؟view=netframework- 4.5 "يختلط =" نوفولو noreferrer "> ساعة توقيت .

public static TimeSpan MeasureTime(Action action)
{
    DateTime start = DateTime.Now;

    if (action == null)
    {
        throw new ArgumentNullException("action");
    }

    try
    {
        action();
    }
    catch (Exception ex)
    {
        Debugger.Log(1, "Measuring",ex.ToString());
    }

    return DateTime.Now - start;
}

وكيفية استخدامه:؟

private static void StressTest()
{
    List<TimeSpan> tss = new List<TimeSpan>();

    for (int i = 0; i < 100; i++)
    {
        // here is the measuring:
        var ts = MeasureTime(() => instance.Method("param1"));

        tss.Add(ts);
    }

    Console.WriteLine("Max: {0}", tss.Max());
    Console.WriteLine("Min: {0}", tss.Min());
    Console.WriteLine("Avg: {0}", TimeSpan.FromMilliseconds(tss.Average(i => i.TotalMilliseconds)));
}

وأو:

var ts = MeasureTime(() =>
                        {
                            // Some intensive stuff here
                            int a = 1;

                            // more here
                            int b = 2;

                            // and so on
                        });

لقياس الأداء مع الفرق بين القياسات I استخدام هذه الفئة. الطبقة ساعة توقيت لايوجد طريقة Split.

/// <summary>
/// Stopwatch like class that keeps track of timelapses.
/// Probably low-res because of the usage of DateTime.
/// </summary>
public class ChronoMeter
{
    /// <summary>
    /// The name given when the Chronometer was constructed.
    /// </summary>
    public string Name { get; private set; }
    /// <summary>
    /// The moment in time Start was called.
    /// </summary>
    public DateTime Started { get; private set; }

    /// <summary>
    /// All time recordings are added to this list by calling Split and Stop.
    /// </summary>
    public List<ChronoRecord> Records { get; private set; }

    private readonly Stopwatch _stopWatch = new Stopwatch();

    private bool _hasBeenStopped = false;

    /// <summary>
    /// Constrcutor
    /// </summary>
    /// <param name="pName">The name is used in logging</param>
    /// <param name="pLoggingType">The type of logging appriate for the information yielded by this time recording.</param>
    public ChronoMeter(string pName)
    {
        Name = pName;
        Records = new List<ChronoRecord>();
    }

    /// <summary>
    /// Not calling Stop is bad practise. Therefore a little safety net zo the end is still recorderd.
    /// Keep in mind that the garbase collector invokes the destructor, so the moment of time probably doesn't make much sense.
    /// It is more to notify that you should have used Stop for the latest split.
    /// </summary>
    ~ChronoMeter()
    {
        if (!_hasBeenStopped)
        {
            Stop("Destructor safety net");
        }
    }

    /// <summary>
    /// TimeElapsedSinceStart of a ChronoRecord is relative to the moment ChronoMeter was started by calling this function.
    /// </summary>
    public void Start()
    {
        _stopWatch.Start();
        Started = DateTime.Now;
    }

    /// <summary>
    /// Splits the timerecording and add a record of this moment to the list of split records.
    /// </summary>
    /// <param name="pSplitName"></param>
    public void Split(string pSplitName)
    {
        _stopWatch.Stop();
        var created = Started + _stopWatch.Elapsed;
        var previousRecord = Records.LastOrDefault();
        Records.Add(new ChronoRecord(pSplitName, Started, created, previousRecord));
        _stopWatch.Start();
    }

    /// <summary>
    /// Indicates you are done and the records will be written to the log.
    /// </summary>
    public void Stop(string pSplitName)
    {
        Split(pSplitName);
        _stopWatch.Stop();
        _hasBeenStopped = true;
    }

    public class ChronoRecord
    {
        public string Name { get; private set; }
        public TimeSpan TimeElapsedSinceStart { get; private set; }
        public TimeSpan TimeElapsedSincePrevious { get; private set; }
        public DateTime Start { get; private set; }
        public DateTime Created { get; private set; }

        public ChronoRecord(string pName, DateTime pStartDateTime, DateTime pCreated, ChronoRecord pPreviousRecord=null)
        {
            if (pCreated == default(DateTime)) //Ignore DefaultDateTimeComparison
            {
                pCreated = DateTime.Now;
            }
            Created = pCreated;
            Name = pName;
            Start = pStartDateTime;

            TimeElapsedSinceStart = Created - Start;
            if (pPreviousRecord != null)
            {
                TimeElapsedSincePrevious = Created - pPreviousRecord.Created;
            }
            else
            {
                TimeElapsedSincePrevious = TimeElapsedSinceStart;
            }
        }
    }
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top