성능 핫스팟으로 측정 한 Sprintf를 개선/교체하려면 어떻게해야합니까?

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

문제

프로파일 링을 통해 나는 여기에서 sprintf가 오랜 시간이 걸린다는 것을 발견했습니다. Y/M/DH/M/S 필드에서 여전히 주요 0을 처리하는 더 나은 성과 대안이 있습니까?

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의 조회 테이블은 0.

편집 : 도약에 대처하려면 (그리고 일치하는 것 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는 다양한 형식을 다루어야합니다. 당신은 확실히 그것을 잡을 수 있습니다 printf의 소스 그리고 그것을 구체적으로 다루는 자신의 버전을 굴리는 기초로 사용하십시오. Systime 구조. 그렇게하면 한 가지 논쟁을 통과하며,해야 할 일과 더 이상 아무것도 할 수없는 일을 정확하게 수행합니다.

"긴"시간은 무엇을 의미합니까? sprintf() 루프의 유일한 진술이며 루프의 "배관"(증분, 비교)은 무시할 수 있습니다. sprintf() 가지다 가장 많은 시간을 소비합니다.

어느 날 밤 3 번가에서 결혼 반지를 잃은 남자에 대한 오래된 농담을 기억하지만, 빛이 더 밝아서 5 일에 그것을 찾았습니까? 당신은 당신의 가정을 "증명"하도록 설계된 예를 만들었습니다. sprintf() 비효율적입니다.

포함 된 "실제"코드를 프로파일하면 결과가 더 정확합니다. sprintf() 사용하는 다른 모든 기능 및 알고리즘 외에도 또는 필요한 특정 제로 패드 숫자 변환을 다루는 자신의 버전을 작성해보십시오.

결과에 놀랄 수 있습니다.

Jaywalker가 매우 비슷한 방법을 제안하는 것처럼 보입니다 (1 시간 미만으로 나를 이길).

이미 제안 된 조회 테이블 방법 (아래 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 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-가족 기능은 형식 스트링 구문 분석이며, 모든 루프 실행에 대한 구문 분석에주기를 바치는 것은 바보입니다.
  • 모든 변환에 2 바이트 조회 테이블을 사용해보십시오 ({ "00", "01", "02", ..., "99" }). 이것은 모듈 루어 산술을 피하고 싶기 때문에 2 바이트 테이블은 연도에 하나의 모듈로만 사용하면됩니다.

형식 문자열을 반복적으로 구문 분석하지 못하고 더 복잡한 케이스 스프린트 핸들을 다룰 필요가 없기 때문에 리턴 BUF의 숫자를 제시하는 루틴을 손으로 굴려서 W 퍼브로 증가 할 것입니다. 그래도 실제로 그렇게하는 것이 좋습니다.

이 문자열을 생성하는 데 필요한 양을 줄일 수 있는지 알아내는 것이 좋습니다. 선택 사항은 선택적이며 캐시 될 수있는 등을 선택할 수 있습니다.

나는 현재 비슷한 문제를 해결하고 있습니다.

임베디드 시스템에 타임 스탬프, 파일 이름, 줄 번호 등으로 디버그 문을 로그어 로그를 작성해야합니다. 우리는 이미 로거가 제자리에 있지만 노브를 '전체 로깅'으로 돌리면 모든 Proc 사이클을 먹고 시스템을 끔찍한 상태에두면 컴퓨팅 장치가 경험할 필요가 없다고 말합니다.

누군가는 "당신은 당신이 측정/관찰하는 것을 바꾸지 않고 무언가를 측정/관찰 할 수 없습니다."라고 말했습니다.

그래서 저는 성능을 향상시키기 위해 물건을 바꾸고 있습니다. 현재의 상태는 원본보다 2 배 빠른 것입니다. 기능 호출 (그 로깅 시스템의 병목 현상은 기능 호출이 아니라 별도의 실행 파일 인 로그 리더에 있으며, 내 자신의 로깅 스택을 작성하면 폐기 할 수 있습니다).

내가 제공 해야하는 인터페이스는 다음과 같습니다. 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가 아님)가있는 조회 테이블이 완벽합니다. 그러나 그것은 2 자리 숫자와 아마도 연도에만 적용됩니다.
  • 3 자리 (밀리 초)와 5 자리 (Lineno)는 어떻습니까? 나는 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() 사용해야합니다 vsprintf() 54% 더 일찍 복용하던 중)

내가 테스트 한 두 개의 빠른 형태는입니다 FastFormat 그리고 카르마 :: 생성 (부분의 정신을 부스트하십시오).

또한 벤치마킹하거나 최소한 기존 벤치 마크를 찾는 것이 유용 할 수도 있습니다.

예를 들어 이 하나 (FastFormat이 없지만) :

Fast integer to string conversion in C++

StringStream은 Google에서 얻은 제안입니다.

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

정수를 포맷 할 때 Sprintf를 이길 것이라고 상상하기는 어렵습니다. Sprintf가 당신의 문제라고 확신합니까?

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top