كيف يمكنني تحسين/استبدال sprintf، الذي قمت بقياسه على أنه نقطة اتصال للأداء؟

StackOverflow https://stackoverflow.com/questions/271971

سؤال

من خلال التوصيف اكتشفت أن سباق السرعة هنا يستغرق وقتًا طويلاً.هل هناك بديل أفضل أداءً لا يزال يتعامل مع الأصفار البادئة في الحقول y/m/d h/m/s؟

SYSTEMTIME sysTime;
GetLocalTime( &sysTime );
char buf[80];
for (int i = 0; i < 100000; i++)
{

    sprintf(buf, "%4d-%02d-%02d %02d:%02d:%02d",
        sysTime.wYear, sysTime.wMonth, sysTime.wDay, 
        sysTime.wHour, sysTime.wMinute, sysTime.wSecond);

}

ملحوظة:يوضح OP في التعليقات أن هذا مثال مجرد.تحتوي الحلقة "الحقيقية" على تعليمات برمجية إضافية تستخدم قيم زمنية مختلفة من قاعدة البيانات.وقد تم تحديد التنميط sprintf() كالجاني.

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

المحلول

إذا كنت تكتب وظيفة الخاصة للقيام بهذه المهمة، جدول بحث القيم سلسلة من 0 .. 61 أن تجنب الحاجة إلى القيام بأي الحساب في كل شيء وبصرف النظر عن السنة.

وتحرير: لاحظ أن للتعامل مع قفزة ثانية (ولمباراة strftime() ) يجب أن تكون قادرة على طباعة ثواني قيم 60 و 61.

char LeadingZeroIntegerValues[62][] = { "00", "01", "02", ... "59", "60", "61" };

وبدلا من ذلك، وكيف حول strftime() ؟ ليس لدي فكرة كيف يقارن أداء (يمكن أيضا أن يكون مجرد الدعوة sprintf ())، لكنه يستحق النظر (ويمكن أن يكون القيام البحث أعلاه نفسه).

نصائح أخرى

هل يمكن أن تحاول ملء كل حرف في إخراج بدوره.

buf[0] = (sysTime.wYear / 1000) % 10 + '0' ;
buf[1] = (sysTime.wYear / 100) % 10 + '0';
buf[2] = (sysTime.wYear / 10) % 10 + '0';
buf[3] = sysTime.wYear % 10 + '0';
buf[4] = '-';

... الخ ...

وليس جميلا، ولكن يمكنك الحصول على الصورة. إذا أي شيء آخر، قد تساعد في تفسير سبب sprintf لن يكون بهذه السرعة.

وOTOH، وربما كنت قد ذاكرة التخزين المؤقت نتيجة الماضية. وبهذه الطريقة كنت بحاجة فقط لتوليد واحدة كل ثانية.

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

وماذا تعني فترة "طويلة" - منذ sprintf() هو البيان الوحيد في حلقة الخاص بك و"السباكة" من الحلقة (زيادة، مقارنة) لا يكاد يذكر، وsprintf() <م> قد تستهلك معظم الوقت.

وتذكر نكتة قديمة عن الرجل الذي فقد خاتم الزواج له في شارع 3 ليلة واحدة، ولكن بدا له على 5 لأن الضوء كان أكثر إشراقا هناك؟ كنت قد بنيت على سبيل المثال التي تهدف إلى "إثبات" توليكم أن sprintf() هو ineffecient.

ونتائجك ستكون أكثر دقة إذا محة كود "الفعلي" الذي يحتوي sprintf() بالإضافة إلى جميع وظائف والخوارزميات الأخرى التي تستخدمها. بدلا من ذلك، حاول كتابة نسختك الخاصة التي تعالج تحويل رقمي صفر مبطن معين الذي تطلبه.

وأنت قد يفاجأ في النتائج.

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

وبالإضافة إلى طريقة جدول بحث اقترح بالفعل (N2S [] مجموعة أدناه)، عن كيفية توليد عازلة شكل الخاص بك بحيث sprintf المعتاد هو أقل كثافة؟ رمز أدناه سوف يكون فقط لملء في الدقيقة والثانية في كل مرة خلال الحلقة ما لم تتغير سنة / شهر / يوم / ساعة. من الواضح، إذا كان أي من تلك تغيرت كنت لا تأخذ ضربة sprintf آخر ولكن عموما قد لا يكون أكثر من ما نشهده حاليا (عندما يقترن بحث مجموعة).


static char fbuf[80];
static SYSTEMTIME lastSysTime = {0, ..., 0};  // initialize to all zeros.

for (int i = 0; i < 100000; i++)
{
    if ((lastSysTime.wHour != sysTime.wHour)
    ||  (lastSysTime.wDay != sysTime.wDay)
    ||  (lastSysTime.wMonth != sysTime.wMonth)
    ||  (lastSysTime.wYear != sysTime.wYear))
    {
        sprintf(fbuf, "%4d-%02s-%02s %02s:%%02s:%%02s",
                sysTime.wYear, n2s[sysTime.wMonth],
                n2s[sysTime.wDay], n2s[sysTime.wHour]);

        lastSysTime.wHour = sysTime.wHour;
        lastSysTime.wDay = sysTime.wDay;
        lastSysTime.wMonth = sysTime.wMonth;
        lastSysTime.wYear = sysTime.wYear;
    }

    sprintf(buf, fbuf, n2s[sysTime.wMinute], n2s[sysTime.wSecond]);

}

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

وهكذا، يمكننا تنفيذ شيء كما يلي. أعلن قديمة وبنية SYSTEMTIME الحالي:

SYSTEMTIME sysTime, oldSysTime;

وبالإضافة إلى ذلك، أعلن أجزاء منفصلة لعقد التاريخ والوقت:

char datePart[80];
char timePart[80];

ل، وهي المرة الأولى، سيكون لديك لملء في كل sysTime، oldSysTime وكذلك datePart وtimePart. لكن sprintf لاحق () الصورة يمكن إجراء تماما أسرع على النحو المبين أدناه:

sprintf (timePart, "%02d:%02d:%02d", sysTime.wHour, sysTime.wMinute, sysTime.wSecond);
if (oldSysTime.wYear == sysTime.wYear && 
  oldSysTime.wMonth == sysTime.wMonth &&
  oldSysTime.wDay == sysTime.wDay) 
  {
     // we can reuse the date part
     strcpy (buff, datePart);
     strcat (buff, timePart);
  }
else {
     // we need to regenerate the date part as well
     sprintf (datePart, "%4d-%02d-%02d", sysTime.wYear, sysTime.wMonth, sysTime.wDay);
     strcpy (buff, datePart);
     strcat (buff, timePart);
}

memcpy (&oldSysTime, &sysTime, sizeof (SYSTEMTIME));

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

سأفعل بعض الأشياء...

  • قم بتخزين الوقت الحالي حتى لا تضطر إلى إعادة إنشاء الطابع الزمني في كل مرة
  • قم بتحويل الوقت يدويًا.أبطأ جزء من printfوظائف -family هي تحليل سلسلة التنسيق، ومن السخافة تخصيص دورات لهذا التحليل في كل تنفيذ حلقة.
  • حاول استخدام جداول بحث ثنائية البايت لجميع التحويلات ({ "00", "01", "02", ..., "99" }).هذا لأنك تريد تجنب الحساب المعياري، والجدول المكون من 2 بايت يعني أنه يتعين عليك استخدام وحدة واحدة فقط لهذا العام.

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

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

أنا أعمل على مشكلة مماثلة في الوقت الراهن.

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

قال أحدهم "لا يمكنك قياس/ملاحظة شيء ما دون تغيير ما تقوم بقياسه/مراقبته."

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

الواجهة التي أحتاج إلى توفيرها هي شيء من هذا القبيل- void log(int channel, char *filename, int lineno, format, ...).أحتاج إلى إلحاق اسم القناة (الذي يقوم حاليًا بإلحاق اسم القناة بـ البحث الخطي ضمن قائمة!لكل عبارة تصحيح واحدة!) والطابع الزمني بما في ذلك عداد المللي ثانية.فيما يلي بعض الأشياء التي أقوم بها لجعل هذا أسرع-

  • قم بتقييد اسم القناة حتى أتمكن من ذلك strcpy بدلاً من البحث في القائمة.تحديد الماكرو LOG(channel, ...etc) مثل log(#channel, ...etc).يمكنك استخدام memcpy إذا قمت بإصلاح طول السلسلة عن طريق تحديد LOG(channel, ...) log("...."#channel - sizeof("...."#channel) + *11*) للحصول على ثابتة 10 أطوال قناة البايت
  • إنشاء سلسلة الطابع الزمني عدة مرات في الثانية.يمكنك استخدام asctime أو شيء من هذا.ثم memcpy سلسلة ذات طول ثابت لكل عبارة تصحيح.
  • إذا كنت تريد إنشاء سلسلة الطابع الزمني في الوقت الفعلي، فإن جدول البحث مع المهمة (وليس memcpy!) يعد مثاليًا.لكن هذا يعمل فقط مع الأرقام المكونة من رقمين وربما للسنة.
  • ماذا عن ثلاثة أرقام (ملي ثانية) وخمسة أرقام (لينينو)؟أنا لا أحب itoa ولا أحب itoa المخصص (digit = ((value /= value) % 10)) إما لأن divs وmods موجودة بطيء.لقد كتبت الوظائف أدناه واكتشفت لاحقًا أن شيئًا مشابهًا موجود في دليل تحسين AMD (في التجميع) مما يمنحني الثقة في أن هذه هي أسرع تطبيقات C.

    void itoa03(char *string, unsigned int value)
    {
       *string++ = '0' + ((value = value * 2684355) >> 28);
       *string++ = '0' + ((value = ((value & 0x0FFFFFFF)) * 10) >> 28);
       *string++ = '0' + ((value = ((value & 0x0FFFFFFF)) * 10) >> 28);
       *string++ = ' ';/* null terminate here if thats what you need */
    }
    

    وبالمثل بالنسبة لأرقام الأسطر،

    void itoa05(char *string, unsigned int value)
    {
       *string++ = ' ';
       *string++ = '0' + ((value = value * 26844 + 12) >> 28);
       *string++ = '0' + ((value = ((value & 0x0FFFFFFF)) * 10) >> 28);
       *string++ = '0' + ((value = ((value & 0x0FFFFFFF)) * 10) >> 28);
       *string++ = '0' + ((value = ((value & 0x0FFFFFFF)) * 10) >> 28);
       *string++ = '0' + ((value = ((value & 0x0FFFFFFF)) * 10) >> 28);
       *string++ = ' ';/* null terminate here if thats what you need */
    }
    

بشكل عام، الكود الخاص بي أصبح سريعًا جدًا الآن.ال vsnprintf() أحتاج إلى الاستخدام يستغرق حوالي 91% من الوقت وبقية الكود الخاص بي يستغرق 9% فقط (في حين أن بقية الكود، أي.يستثني vsprintf() اعتاد أن يأخذ 54٪ في وقت سابق)

المنسقان السريعان اللذان اختبرتهما هما تنسيق سريع و الكرمة :: توليد (جزء من تعزيز الروح).

قد تجد أيضًا أنه من المفيد قياسها أو على الأقل البحث عن المعايير الحالية.

على سبيل المثال هذا (على الرغم من أنه يفتقد FastFormat):

Fast integer to string conversion in C++

وStringStream هو الاقتراح الذي حصلت عليه من جوجل.

http://bytes.com/forum/thread132583.html

ومن الصعب أن نتصور أن كنت تريد الذهاب للفوز sprintf في تنسيق أعداد صحيحة. هل أنت متأكد sprintf هي مشكلتك؟

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top