¿Hay un comstackdor de C que no comstack esto?

Pasé un tiempo en mi perfilador tratando de averiguar cómo acelerar un analizador de registro común que se encontraba en un cuello de botella alrededor del análisis de fecha, y probé varios algoritmos para acelerar el proceso.

Lo que intenté que fue más rápido para mí también fue, con mucho, el C más legible, pero potencialmente no estándar.

Esto funcionó bastante bien en GCC , icc y mi comstackdor SGI realmente viejo y delicado. Como es una optimización bastante legible, ¿dónde no hace lo que quiero?

static int parseMonth(const char *input) { int rv=-1; int inputInt=0; int i=0; for(i=0; i<4 && input[i]; i++) { inputInt = (inputInt << 8) | input[i]; } switch(inputInt) { case 'Jan/': rv=0; break; case 'Feb/': rv=1; break; case 'Mar/': rv=2; break; case 'Apr/': rv=3; break; case 'May/': rv=4; break; case 'Jun/': rv=5; break; case 'Jul/': rv=6; break; case 'Aug/': rv=7; break; case 'Sep/': rv=8; break; case 'Oct/': rv=9; break; case 'Nov/': rv=10; break; case 'Dec/': rv=11; break; } return rv; } 

Solaris 10 – SPARC – Comstackdor SUN.

Código de prueba:

 #include  static int parseMonth(const char *input) { int rv=-1; int inputInt=0; int i=0; for(i=0; i<4 && input[i]; i++) { inputInt = (inputInt << 8) | input[i]; } switch(inputInt) { case 'Jan/': rv=0; break; case 'Feb/': rv=1; break; case 'Mar/': rv=2; break; case 'Apr/': rv=3; break; case 'May/': rv=4; break; case 'Jun/': rv=5; break; case 'Jul/': rv=6; break; case 'Aug/': rv=7; break; case 'Sep/': rv=8; break; case 'Oct/': rv=9; break; case 'Nov/': rv=10; break; case 'Dec/': rv=11; break; } return rv; } static const struct { char *data; int result; } test_case[] = { { "Jan/", 0 }, { "Feb/", 1 }, { "Mar/", 2 }, { "Apr/", 3 }, { "May/", 4 }, { "Jun/", 5 }, { "Jul/", 6 }, { "Aug/", 7 }, { "Sep/", 8 }, { "Oct/", 9 }, { "Nov/", 10 }, { "Dec/", 11 }, { "aJ/n", -1 }, }; #define DIM(x) (sizeof(x)/sizeof(*(x))) int main(void) { size_t i; int result; for (i = 0; i < DIM(test_case); i++) { result = parseMonth(test_case[i].data); if (result != test_case[i].result) printf("!! FAIL !! %s (got %d, wanted %d)\n", test_case[i].data, result, test_case[i].result); } return(0); } 

Resultados (GCC 3.4.2 y Sol):

 $ gcc -O xx.c -o xx xx.c:14:14: warning: multi-character character constant xx.c:15:14: warning: multi-character character constant xx.c:16:14: warning: multi-character character constant xx.c:17:14: warning: multi-character character constant xx.c:18:14: warning: multi-character character constant xx.c:19:14: warning: multi-character character constant xx.c:20:14: warning: multi-character character constant xx.c:21:14: warning: multi-character character constant xx.c:22:14: warning: multi-character character constant xx.c:23:14: warning: multi-character character constant xx.c:24:14: warning: multi-character character constant xx.c:25:14: warning: multi-character character constant $ ./xx $ cc -o xx xx.c $ ./xx !! FAIL !! Jan/ (got -1, wanted 0) !! FAIL !! Feb/ (got -1, wanted 1) !! FAIL !! Mar/ (got -1, wanted 2) !! FAIL !! Apr/ (got -1, wanted 3) !! FAIL !! May/ (got -1, wanted 4) !! FAIL !! Jun/ (got -1, wanted 5) !! FAIL !! Jul/ (got -1, wanted 6) !! FAIL !! Aug/ (got -1, wanted 7) !! FAIL !! Sep/ (got -1, wanted 8) !! FAIL !! Oct/ (got -1, wanted 9) !! FAIL !! Nov/ (got -1, wanted 10) !! FAIL !! Dec/ (got -1, wanted 11) $ 

Tenga en cuenta que el último caso de prueba aún se aprobó, es decir, generó un -1.

Aquí hay una versión revisada, más detallada, de parseMonth () que funciona igual tanto en GCC como en el comstackdor de Sun C:

 #include  /* MONTH_CODE("Jan/") does not reduce to an integer constant */ #define MONTH_CODE(x) ((((((x[0]<<8)|x[1])<<8)|x[2])<<8)|x[3]) #define MONTH_JAN (((((('J'<<8)|'a')<<8)|'n')<<8)|'/') #define MONTH_FEB (((((('F'<<8)|'e')<<8)|'b')<<8)|'/') #define MONTH_MAR (((((('M'<<8)|'a')<<8)|'r')<<8)|'/') #define MONTH_APR (((((('A'<<8)|'p')<<8)|'r')<<8)|'/') #define MONTH_MAY (((((('M'<<8)|'a')<<8)|'y')<<8)|'/') #define MONTH_JUN (((((('J'<<8)|'u')<<8)|'n')<<8)|'/') #define MONTH_JUL (((((('J'<<8)|'u')<<8)|'l')<<8)|'/') #define MONTH_AUG (((((('A'<<8)|'u')<<8)|'g')<<8)|'/') #define MONTH_SEP (((((('S'<<8)|'e')<<8)|'p')<<8)|'/') #define MONTH_OCT (((((('O'<<8)|'c')<<8)|'t')<<8)|'/') #define MONTH_NOV (((((('N'<<8)|'o')<<8)|'v')<<8)|'/') #define MONTH_DEC (((((('D'<<8)|'e')<<8)|'c')<<8)|'/') static int parseMonth(const char *input) { int rv=-1; int inputInt=0; int i=0; for(i=0; i<4 && input[i]; i++) { inputInt = (inputInt << 8) | input[i]; } switch(inputInt) { case MONTH_JAN: rv=0; break; case MONTH_FEB: rv=1; break; case MONTH_MAR: rv=2; break; case MONTH_APR: rv=3; break; case MONTH_MAY: rv=4; break; case MONTH_JUN: rv=5; break; case MONTH_JUL: rv=6; break; case MONTH_AUG: rv=7; break; case MONTH_SEP: rv=8; break; case MONTH_OCT: rv=9; break; case MONTH_NOV: rv=10; break; case MONTH_DEC: rv=11; break; } return rv; } static const struct { char *data; int result; } test_case[] = { { "Jan/", 0 }, { "Feb/", 1 }, { "Mar/", 2 }, { "Apr/", 3 }, { "May/", 4 }, { "Jun/", 5 }, { "Jul/", 6 }, { "Aug/", 7 }, { "Sep/", 8 }, { "Oct/", 9 }, { "Nov/", 10 }, { "Dec/", 11 }, { "aJ/n", -1 }, { "/naJ", -1 }, }; #define DIM(x) (sizeof(x)/sizeof(*(x))) int main(void) { size_t i; int result; for (i = 0; i < DIM(test_case); i++) { result = parseMonth(test_case[i].data); if (result != test_case[i].result) printf("!! FAIL !! %s (got %d, wanted %d)\n", test_case[i].data, result, test_case[i].result); } return(0); } 

Quería usar MONTH_CODE () pero los comstackdores no cooperaron.

 if ( !input[0] || !input[1] || !input[2] || input[3] != '/' ) return -1; switch ( input[0] ) { case 'F': return 1; // Feb case 'S': return 8; // Sep case 'O': return 9; // Oct case 'N': return 10; // Nov case 'D': return 11; // Dec; case 'A': return input[1] == 'p' ? 3 : 7; // Apr, Aug case 'M': return input[2] == 'r' ? 2 : 4; // Mar, May default: return input[1] == 'a' ? 0 : (input[2] == 'n' ? 5 : 6); // Jan, Jun, Jul } 

Un poco menos legible y no tanto de validación, pero quizás incluso más rápido, ¿no?

Estás calculando un hash de esos cuatro personajes. ¿Por qué no predefinir algunas constantes enteras que computan el hash de la misma manera y las usan? La misma legibilidad y usted no depende de ninguna idiosincrasia específica de la implementación del comstackdor.

 uint32_t MONTH_JAN = 'J' << 24 + 'a' << 16 + 'n' << 8 + '/'; uint32_t MONTH_FEB = 'F' << 24 + 'e' << 16 + 'b' << 8 + '/'; ... static uint32_t parseMonth(const char *input) { uint32_t rv=-1; uint32_t inputInt=0; int i=0; for(i=0; i<4 && input[i]; i++) { inputInt = (inputInt << 8) | (input[i] & 0x7f); // clear top bit } switch(inputInt) { case MONTH_JAN: rv=0; break; case MONTH_FEB: rv=1; break; ... } return rv; } 

Solo sé lo que dice el Estándar C sobre esto (C99):

El valor de una constante de caracteres enteros que contiene más de un carácter (por ejemplo, ‘ab’), o que contiene un carácter o secuencia de escape que no se asigna a un carácter de ejecución de un solo byte, se define por implementación. Si una constante de caracteres enteros contiene un solo carácter o secuencia de escape, su valor es el que resulta cuando un objeto con tipo char cuyo valor es el del carácter único o la secuencia de escape se convierte al tipo int.

(6.4.4.4/10 tomado de un borrador)

Así que es la implementación definida. Lo que significa que no se garantiza que funcione igual en todas partes, pero el comportamiento debe estar documentado por la implementación. Por ejemplo, si int tiene solo 16 bits de ancho en una implementación en particular, entonces 'Jan/' ya no se puede representar como usted pretende ( char debe tener al menos 8 bits, mientras que un literal de carácter siempre es del tipo int ).

 char *months = "Jan/Feb/Mar/Apr/May/Jun/Jul/Aug/Sep/Oct/Nov/Dec/"; char *p = strnstr(months, input, 4); return p ? (p - months) / 4 : -1; 

El comstackdor CVI 8.5 para Windows de National Instrument falla en su código original con múltiples advertencias:

  Warning: Excess characters in multibyte character literal ignored. 

y errores de la forma:

  Duplicate case label '77'. 

Tiene éxito en el código de Jonathan.

Hay al menos 3 cosas que evitan que este progtwig sea portátil:

  1. Las constantes de múltiples caracteres están definidas por la implementación, por lo que diferentes comstackdores pueden manejarlas de manera diferente.
  2. Un byte puede tener más de 8 bits, hay un montón de hardware donde la unidad más pequeña de memoria direccionable es de 16 o incluso 32 bits, por ejemplo, a menudo se encuentra esto en los DSP. Si un byte tiene más de 8 bits, también lo hará char ya que char es, por definición, un byte de longitud; Su progtwig no funcionará correctamente en tales sistemas.
  3. Por último, hay muchas máquinas en las que int es solo de 16 bits (que es el tamaño más pequeño permitido para la int), incluidos los dispositivos integrados y las máquinas heredadas; su progtwig también fallará en estas máquinas.

Recibo advertencias, pero no errores (gcc). Parece comstackr y operar bien. Sin embargo, ¡puede que no funcione para los sistemas big-endian!

Aunque no sugeriría este método. Quizás pueda xor en lugar de o-shift, para crear un solo byte. Luego use la statement de caso en un byte (o, más rápido, use una LUT de los primeros N bits).

Comstackdor comeau

 Comeau C/C++ 4.3.10.1 (Oct 6 2008 11:28:09) for ONLINE_EVALUATION_BETA2 Copyright 1988-2008 Comeau Computing. All rights reserved. MODE:strict errors C99 "ComeauTest.c", line 11: warning: multicharacter character literal (potential portability problem) case 'Jan/': rv=0; break; ^ "ComeauTest.c", line 12: warning: multicharacter character literal (potential portability problem) case 'Feb/': rv=1; break; ^ "ComeauTest.c", line 13: warning: multicharacter character literal (potential portability problem) case 'Mar/': rv=2; break; ^ "ComeauTest.c", line 14: warning: multicharacter character literal (potential portability problem) case 'Apr/': rv=3; break; ^ "ComeauTest.c", line 15: warning: multicharacter character literal (potential portability problem) case 'May/': rv=4; break; ^ "ComeauTest.c", line 16: warning: multicharacter character literal (potential portability problem) case 'Jun/': rv=5; break; ^ "ComeauTest.c", line 17: warning: multicharacter character literal (potential portability problem) case 'Jul/': rv=6; break; ^ "ComeauTest.c", line 18: warning: multicharacter character literal (potential portability problem) case 'Aug/': rv=7; break; ^ "ComeauTest.c", line 19: warning: multicharacter character literal (potential portability problem) case 'Sep/': rv=8; break; ^ "ComeauTest.c", line 20: warning: multicharacter character literal (potential portability problem) case 'Oct/': rv=9; break; ^ "ComeauTest.c", line 21: warning: multicharacter character literal (potential portability problem) case 'Nov/': rv=10; break; ^ "ComeauTest.c", line 22: warning: multicharacter character literal (potential portability problem) case 'Dec/': rv=11; break; ^ "ComeauTest.c", line 1: warning: function "parseMonth" was declared but never referenced static int parseMonth(const char *input) { ^ 

El hecho de que una constante de cuatro caracteres sea equivalente a un entero particular de 32 bits es una característica no estándar que se ve a menudo en los comstackdores para computadoras MS Windows y Mac (y PalmOS, AFAICR).

En estos sistemas, una cadena de cuatro caracteres se usa comúnmente como una etiqueta para identificar fragmentos de archivos de datos, o como una aplicación / identificador de tipo de datos (por ejemplo, “APPL”).

Para el desarrollador, es una ventaja que puedan almacenar dicha cadena en varias estructuras de datos sin tener que preocuparse por la terminación de cero bytes, los punteros, etc.

Dejando de lado los problemas de tamaño de las palabras de la máquina, su comstackdor puede promover la entrada [i] a un entero negativo que solo establecerá los bits superiores de inputInt con u operación, por lo que le sugiero que sea explícito acerca de la firmeza de las variables char.

Pero como en EE. UU. A nadie le importa el bit 8, es probable que no sea un problema para ti.

Me encantaría ver el perfil que muestra que este es su cuello de botella más importante, pero en cualquier caso, si va a hacer algo como esto, use una unión en lugar de 50 instrucciones de bucle y cambio. Aquí hay un pequeño progtwig de ejemplo, lo dejo a usted para que encaje en su progtwig.

 /* union -- demonstrate union for characters */ #include  union c4_i { char c4[5]; int i ; } ; union c4_i ex; int main (){ ex.c4[0] = 'a'; ex.c4[1] = 'b'; ex.c4[2] = 'c'; ex.c4[3] = 'd'; ex.c4[4] = '\0'; printf("%s 0x%08x\n", ex.c4, ex.i ); return 0; } 

Aquí está la salida del ejemplo:

 bash $ ./union abcd 0x64636261 bash $ 

Como lo mencionaron otros, ese código arroja un montón de advertencias y probablemente no sea seguro para los endianos.

¿Tu analizador de fecha original estaba escrito a mano también? ¿Has probado Strptime (3)?