Pergunta

Através de perfis eu descobri que o sprintf aqui leva um longo tempo. Existe uma alternativa melhor desempenho que ainda lida com os zeros à esquerda do y / m / d h / m / s campos?

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);

}

Nota: O OP explica nos comentários que este é um exemplo despojado. O loop "real" contém código adicional que usos variados valores de tempo a partir de um banco de dados. Profiling tem sprintf() apontado como o agressor.

Foi útil?

Solução

Se você estava escrevendo sua própria função para fazer o trabalho, uma tabela de referência dos valores de seqüência de 0 .. 61 evitaria ter que fazer qualquer aritmética para tudo além do ano.

edit: Note que para lidar com segundos bissextos (e para corresponder strftime() ), você deve ser capaz de imprimir valores segundos de 60 e 61.

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

Como alternativa, como sobre strftime() ? Eu não tenho nenhuma idéia de como o desempenho se compara (que poderia muito bem ser apenas chamando sprintf ()), mas vale a pena olhar para (e que poderia estar fazendo a acima lookup em si).

Outras dicas

Você poderia tentar enchendo cada caractere na saída, por sua vez.

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] = '-';

... etc ...

Não é bonito, mas você começa a foto. Se nada mais, ele pode ajudar a explicar por que sprintf não vai ser tão rápido.

OTOH, talvez você poderia armazenar em cache o último resultado. Dessa forma, você só precisa gerar um a cada segundo.

Printf precisa lidar com um monte de diferentes formatos. Você certamente poderia agarrar a fonte para printf e usá-lo como base para rolo sua própria versão que lida especificamente com o sysTime estrutura. Dessa forma, você passar em um argumento, e ele faz exatamente o trabalho que precisa ser feito e nada mais.

O que você quer dizer com um "longo" tempo - desde o sprintf() é a única declaração em seu loop e o "encanamento" do loop (incremento, comparação) é desprezível, o sprintf() tem para consumir a maior parte do tempo.

Lembre-se da velha piada sobre o homem que perdeu seu anel de casamento na 3rd Street uma noite, mas olhou para ele na quinta porque a luz era mais brilhante lá? Você construiu um exemplo que é projetado para "provar" a sua suposição de que sprintf() é ineffecient.

Seus resultados serão mais precisos se você perfil código "real" que contém sprintf(), além de todas as outras funções e algoritmos que você usa. Alternativamente, tente escrever a sua própria versão de que a conversão numérica endereços específico com zeros à esquerda que você precisa.

Você pode se surpreender com os resultados.

Looks como Jaywalker está sugerindo um método muito semelhante (bater-me pelo menos de uma hora).

Além dos já sugeriu pesquisa método de tabela (n2s [] array abaixo), como sobre a geração de tamponar o seu formato para que o sprintf usual é menos intensivo? O código abaixo só terá que preencher o minuto e segundo de cada vez através do laço a menos que o ano / mês / dia / hora mudaram. Obviamente, se qualquer um deles mudaram você tomar uma outra batida sprintf mas no geral pode não ser mais do que aquilo que você está testemunhando (quando combinado com a pesquisa array).


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]);

}

Como cerca de cache os resultados? Isso não é uma possibilidade? Considerando que este sprintf particular () chamada é feita muitas vezes em seu código, eu estou supondo que entre a maioria destas chamadas consecutivas, o ano, mês e dia não mudam.

Assim, podemos implementar algo como o seguinte. Declare um velho e uma estrutura SYSTEMTIME atual:

SYSTEMTIME sysTime, oldSysTime;

Além disso, declarar partes separadas para manter a data ea hora:

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

Para, pela primeira vez, você terá que preencher ambos sysTime, oldSysTime, bem como datepart e Timepart. Mas sprintf subseqüente () 's pode ser feita muito mais rápido como dado abaixo:

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));

código acima tem alguma redundância para tornar o código mais fácil de entender. Você pode fatorar facilmente. Você pode acelerar ainda mais se você sabe que mesmo hora e os minutos não vai mudar mais rapidamente do que a sua chamada para a rotina.

Eu faria algumas coisas ...

  • Cache a hora atual de modo que você não tem para regenerar o timestamp cada vez
  • fazer a conversão hora manualmente. A parte mais lenta das funções printf-família é a análise formato de corda, e que é bobagem para ser dedicando ciclos para que a análise em cada execução do loop.
  • tente usar tabelas de 2 bytes de pesquisa para todas as conversões ({ "00", "01", "02", ..., "99" }). Isso é porque você quer evitar aritmética moduluar, e um 2-byte meio da tabela que você só tem que usar um módulo, para o ano.

Você provavelmente obter w aumento perf à mão rolando uma rotina que estabelece os dígitos no buf retorno, desde que você poderia evitar repetidamente analisar uma string de formato e não teria que lidar com um monte de casos mais complexos sprintf alças . Estou detestam para realmente recomendo fazer isso embora.

Eu recomendaria tentando descobrir se você pode de alguma forma reduzir a quantidade que você precisa para gerar essas cadeias, eles são somegtimes opcionais, eles podem ser armazenados em cache, etc.

Eu estou trabalhando em um problema semelhante no momento.

Eu preciso para registrar instruções de depuração com timestamp, nome do arquivo, o número da linha etc em um sistema embarcado. Nós já temos um registrador no lugar, mas quando eu girar o botão para 'log completo', ele come todos os nossos ciclos proc e coloca o nosso sistema em estados terríveis, estados que nenhum dispositivo de computação nunca deve ter a experiência.

Alguém disse "Você não pode medir / observar algo sem alterar o que você está medindo / observando."

Então, eu estou mudando as coisas para melhorar o desempenho. O actual estado de coisas é que Im 2x mais rápido do que o original chamada de função (o gargalo nesse sistema de registro não está na chamada de função, mas no leitor de log que é um executável separado, que pode descartar se eu escrever a minha própria pilha de registro).

A interface de I precisa fornecer é algo como- void log(int channel, char *filename, int lineno, format, ...). Eu preciso acrescentar o nome do canal (que atualmente faz um Pesquisa linear dentro de uma lista! Para cada declaração de depuração única!) E timestamp incluindo contador de milissegundo. Aqui estão algumas das coisas que estou fazendo para tornar isso mais rápido -

  • stringify nome do canal para que eu possa strcpy em vez de pesquisar a lista. definir LOG(channel, ...etc) macro como log(#channel, ...etc). Você pode usar memcpy se você corrigir o comprimento da corda, definindo LOG(channel, ...) log("...."#channel - sizeof("...."#channel) + *11*) para ficar fixo 10 comprimentos de canal byte
  • Gerar timestamp seqüência de um par de vezes por segundo. Você pode usar asctime ou algo assim. Então memcpy a string de comprimento fixo para cada declaração de depuração.
  • Se você deseja gerar a seqüência timestamp em tempo real, em seguida, um olhar a tabela com atribuição (não memcpy!) É perfeito. Mas isso só funciona para 2 números do dígito e talvez para o ano.
  • E sobre três dígitos (milissegundos) e de cinco dígitos (NUM_LINHA)? Eu não gosto de itoa e eu não gosto o costume itoa (digit = ((value /= value) % 10)) ou porque divs e mods são lento . Eu escrevi as funções abaixo e mais tarde descobriu que algo semelhante está no manual otimização AMD (em conjunto), que me dá confiança de que estes são sobre as implementações mais rápidas 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 */
    }
    

    Da mesma forma, para os números de linha,

    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 */
    }
    

No geral, o meu código é muito rápido agora. O vsnprintf() eu preciso usar leva cerca de 91% do tempo e do resto do meu código leva apenas 9% (enquanto o resto do código ou seja, exceto vsprintf() costumava levar 54% anteriormente)

Os dois formatadores rápidos que eu testei estão FastFormat e Karma :: gerar (parte de Aumento Espírito ).

Você também pode achar que é útil para referência ou pelo menos olhar para benchmarks existentes.

Por exemplo um presente (FastFormat que está faltando):

integer rápido a conversão de cadeia em C ++

StringStream é a sugestão que eu tenho de Google.

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

É difícil imaginar que você está indo para vencer sprintf em números inteiros de formatação. Tem certeza sprintf é o seu problema?

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top