2012-02-29 8 views
7

Así que estaba jugando con un poco de código y quería ver qué método de conversión de std :: string a mayúsculas era más eficiente. Pensé que los dos serían algo similares en cuanto a rendimiento, pero estaba terriblemente equivocado. Ahora me gustaría averiguar por qué. El primer método de conversión de la cadena funciona de la siguiente manera: para cada carácter de la cadena (guarde la longitud, repita de 0 a la longitud), si está entre 'a' y 'z', cámbiela para que sea entre 'A' y 'Z' en su lugar.Conversión de std :: string a mayúsculas: ¿diferencia de rendimiento importante?

El segundo método funciona de la siguiente manera: para cada carácter en la cadena (empiece desde 0, continúe hasta que lleguemos a un terminador nulo), aplique la construcción en la función toupper().

Aquí está el código:

#include <iostream> 
#include <string> 

inline std::string ToUpper_Reg(std::string str) 
{ 
    for (int pos = 0, sz = str.length(); pos < sz; ++pos) 
    { 
     if (str[pos] >= 'a' && str[pos] <= 'z') { str[pos] += ('A' - 'a'); } 
    } 

    return str; 
} 

inline std::string ToUpper_Alt(std::string str) 
{ 
    for (int pos = 0; str[pos] != '\0'; ++pos) { str[pos] = toupper(str[pos]); } 

    return str; 
} 


int main() 
{ 
    std::string test = " [email protected]#$%^&*()_+=-`'{}[]\\|\";:<>,./?"; 

    for (size_t i = 0; i < 100000000; ++i) { ToUpper_Reg(test); /* ToUpper_Alt(test); */ } 

    return 0; 
} 

El primer método ToUpper_Reg tomó cerca de 169 segundos por 100 millones de iteraciones.
El segundo método Toupper_Alt tomó aproximadamente 379 segundos por 100 millones de iteraciones.

¿Qué ofrece?


Editar: he cambiado el segundo método para que itera la cadena de cómo el primero de ellos no (establecer la longitud de lado, bucle while menos de longitud) y es un poco más rápido, pero todavía alrededor de dos veces lento.


Edición 2: Gracias a todos por sus presentaciones! La información en la que la utilizaré está garantizada como ASCII, así que creo que me quedaré con el primer método por el momento. Tendré en cuenta que toupper es específico de la localidad para cuando/si lo necesito.

+7

toupper es más lento que lo que haces en _Reg porque hace más de lo que haces en Reg? – Almo

+4

¿Por qué no agrega también la transformación en C++ estándar, 'std :: transform (s.begin(), s.end(), s.begin(), (int (*) (int)) std :: toupper); '? (Necesita '#include ', '' y ''.) –

+0

Guau, eso es un bocado. Por curiosidad, ¿qué pasa con la parte '(int (*) (int))'? –

Respuesta

13

std::toupper utiliza la configuración regional actual para realizar conversiones de casos, lo que implica una llamada de función y otras abstracciones. Entonces, naturalmente, será más lento. Pero también funcionará en texto que no sea ASCII.

3

toupper() tiene en cuenta la configuración regional para que pueda manejar (algunos) caracteres internacionales y es mucho más complejo que solo manejar el rango de caracteres 'a' - 'z'.

5

toupper() hace más que simplemente desplazar caracteres en el rango [a-z]. En primer lugar, depende de la configuración regional y puede manejar algo más que ASCII.

0

El segundo encendido implica una llamada de función. una llamada a función es una operación costosa en un bucle interno. toupper también usa configuraciones regionales para determinar cómo debe cambiarse el personaje.

Los avances de la llamada es que es estándar y funcionará independientemente de la codificación de caracteres en el ordenador central

Dicho esto, recomiendo encarecidamente utilizar la función de impulso:

boost::algorithm::to_upper 

Es una plantilla, por lo tanto, es más que probable que esté en línea, sin embargo, implica locales. Todavía lo usaría.

http://www.boost.org/doc/libs/1_40_0/doc/html/boost/algorithm/to_upper.html

0

supongo que es porque el segundo llama a una función de la biblioteca estándar C, que por un lado no se colocarán en línea, por lo que tiene la sobrecarga de una llamada a la función. Pero aún más importante, esta función probablemente hace mucho más que solo dos comparaciones, dos saltos y dos adiciones enteras. Realiza controles adicionales en el personaje y toma en cuenta la configuración regional actual y todo eso.

3

Bueno, ToUpper_Reg() no funciona. Por ejemplo, no convierte mi nombre en todos los caracteres en mayúsculas. Dicho esto, ToUpper_Alt() tampoco funciona porque toupper() obtiene un valor negativo en algunas plataformas, es decir, crea un comportamiento indefinido (normalmente un bloqueo) al usarlo con mi nombre. Esto se puede arreglar fácilmente, sin embargo, llamando correctamente, algo como esto:

toupper(static_cast<unsigned char>(str[pos])) 

Dicho esto, las dos versiones del código no son equivalentes: la versión onot usando toupper() no está escribiendo los personajes todo el tiempo mientras la última versión es: una vez que todo se convierte a mayúsculas, siempre toma la misma rama después de una prueba y luego no hace nada. Es posible que desee cambiar ToUpper_Alt() a parecerse a esto y volver a probar:

inline std::string ToUpper_Alt(std::string str) 
{ 
    for (int pos = 0; str[pos] != '\0'; ++pos) { 
     if (islower(static_cast<unsigned char>(str[pos])) { 
      str[pos] = toupper(static_cast<unsigned char>(str[pos])); 
     } 
    } 

    return str; 
} 

Conjeturaría la diferencia es la escritura: toupper() oficios la comparación de una serie de consulta. Se accede rápidamente a la configuración regional y todo toupper() es obtener el puntero actual y acceder a la ubicación en un desplazamiento determinado. Con los datos en la memoria caché, esto es probablemente tan rápido como la rama.

+0

Buena captura de la diferencia en el número de escrituras. –

+0

¿Es costoso construir una nueva configuración regional para cada llamada a 'std :: toupper'? ¿Deberían los usuarios generalmente almacenar en caché un objeto local para pasar? – caps

+0

@caps: es bastante caro crear un nuevo objeto 'std :: locale'. Una copia requiere acceder a un incremento de conteo de referencia sincronizado.Crear un nuevo 'std :: locale' y cambiar una faceta requiere un acceso de recuento de referencia sincronizado adicional por faceta. La construcción predeterminada de un 'std :: locale' necesita un acceso sincronizado al' std :: locale' global más el costo de una copia. Entonces, sí, debes mantener los objetos 'std :: locale' en su sitio. Tenga en cuenta que la versión de 'std :: toupper()' que no utiliza un parámetro 'std :: locale' no crea una: ¡en su lugar, accede a la entidad de configuración regional de C! –

0

std :: toupper utiliza la configuración regional actual y la razón por la que esto es más lenta que la función C es que la localización actual se comparte y mutable de diferentes temas, por lo que es necesario para bloquear el objeto de localizaciones cuando se accede a asegúrese de que no esté conectado durante la llamada. Esto ocurre una vez por llamada a toupper e introduce una sobrecarga bastante grande (la obtención del bloqueo podría requerir un syscall dependiendo de la implementación). Una solución alternativa si desea obtener el rendimiento y respetar la configuración regional es obtener primero el objeto de configuración regional (creando una copia local) y luego invocar la faceta de toupper en su copia, evitando así la necesidad de bloquear cada llamada de touperador. Vea el siguiente enlace para ver un ejemplo.

http://www.cplusplus.com/reference/std/locale/ctype/toupper/

0

La pregunta ya ha sido contestada, sino como un aparte, en sustitución de las tripas de su bucle en el primer método con:

std::string::value_type &c = str[pos]; 
if ('a' <= c && c <= 'z') { c += ('A' - 'a'); } 

hace que sea aún más rápido. Tal vez mi compilador solo apesta.

Cuestiones relacionadas