Aquí hay algunos hallazgos míos actualizados aunque estrechos con GCC 4.7.2 y Clang 3.2 para C ++.
ACTUALIZACIÓN: GCC 4.8.1 v clang 3.3 comparación adjunta a continuación.
ACTUALIZACIÓN: GCC 4.8.2 v clang 3.4 comparación se agrega a eso.
Mantengo una herramienta OSS creada para Linux con GCC y Clang, y con el compilador de Microsoft para Windows. La herramienta, coan, es un preprocesador y analizador de archivos fuente C / C ++ y líneas de código de los mismos: su perfil computacional se especializa en el análisis y manejo de archivos de descenso recursivo. La rama de desarrollo (a la que pertenecen estos resultados) comprende actualmente alrededor de 11K LOC en aproximadamente 90 archivos. Está codificado, ahora, en C ++ que es rico en polimorfismo y plantillas, pero aún está empantanado en muchos parches por su pasado no muy lejano en C. hackeado. La semántica de movimiento no se explota expresamente. Es de un solo hilo. No he dedicado ningún esfuerzo serio a optimizarlo, mientras que la "arquitectura" sigue siendo en gran medida ToDo.
Empleé Clang antes de 3.2 solo como compilador experimental porque, a pesar de su velocidad de compilación y diagnóstico superiores, su soporte estándar C ++ 11 se quedó atrás de la versión contemporánea de GCC en los aspectos ejercidos por coan. Con 3.2, esta brecha se ha cerrado.
Mi arnés de prueba de Linux para procesos de desarrollo actual de aproximadamente 70,000 archivos fuente en una mezcla de casos de prueba de analizador de un archivo, pruebas de estrés que consumen miles de archivos y pruebas de escenarios que consumen <1K archivos. Además de informar los resultados de la prueba, el arnés se acumula y muestra los totales de archivos consumidos y el tiempo de ejecución consumido en coan (simplemente pasa cada línea de comando coan al comando Linux time
y captura y suma los números informados). Los tiempos son halagados por el hecho de que cualquier número de pruebas que toman 0 tiempo medible sumarán 0, pero la contribución de tales pruebas es insignificante. Las estadísticas de tiempo se muestran al final de make check
esta manera:
coan_test_timer: info: coan processed 70844 input_files.
coan_test_timer: info: run time in coan: 16.4 secs.
coan_test_timer: info: Average processing time per input file: 0.000231 secs.
Comparé el rendimiento del arnés de prueba entre GCC 4.7.2 y Clang 3.2, siendo todo igual excepto los compiladores. A partir de Clang 3.2, ya no necesito ninguna diferenciación de preprocesador entre las secciones de código que GCC compilará y las alternativas de Clang. Construí la misma biblioteca C ++ (GCC) en cada caso y ejecuté todas las comparaciones consecutivamente en la misma sesión de terminal.
El nivel de optimización predeterminado para mi versión de lanzamiento es -O2. También probé con éxito las compilaciones en -O3. Probé cada configuración 3 veces consecutivas y promedié los 3 resultados, con los siguientes resultados. El número en una celda de datos es el número promedio de microsegundos consumidos por el ejecutable coan para procesar cada uno de los ~ 70K archivos de entrada (lectura, análisis y escritura de salida y diagnóstico).
| -O2 | -O3 |O2/O3|
----------|-----|-----|-----|
GCC-4.7.2 | 231 | 237 |0.97 |
----------|-----|-----|-----|
Clang-3.2 | 234 | 186 |1.25 |
----------|-----|-----|------
GCC/Clang |0.99 | 1.27|
Es muy probable que cualquier aplicación en particular tenga rasgos que jueguen injustamente a las fortalezas o debilidades de un compilador. La evaluación comparativa rigurosa emplea diversas aplicaciones. Con eso en mente, las características notables de estos datos son:
- -O3 optimización fue marginalmente perjudicial para GCC
- -O3 optimización fue muy beneficiosa para Clang
- En la optimización de -O2, GCC fue más rápido que Clang en solo un bigote
- En la optimización de -O3, Clang fue significativamente más rápido que GCC.
Otra comparación interesante de los dos compiladores surgió por accidente poco después de esos hallazgos. Coan emplea generosamente punteros inteligentes y uno de ellos es muy ejercido en el manejo de archivos. Este tipo de puntero inteligente en particular se había definido en versiones anteriores en aras de la diferenciación del compilador, para ser un std::unique_ptr<X>
si el compilador configurado tenía un soporte suficientemente maduro para su uso como ese, y de lo contrario un std::shared_ptr<X>
. El sesgo hacia std::unique_ptr
era tonto, ya que estos punteros de hecho se transfirieron, pero std::unique_ptr
parecía la opción más adecuada para reemplazar
std::auto_ptr
en un momento en que las variantes de C ++ 11 eran nuevas para mí.
En el curso de las compilaciones experimentales para medir la necesidad continua de Clang 3.2 de esta diferenciación similar, construí inadvertidamente
std::shared_ptr<X>
cuando tenía la intención de compilar std::unique_ptr<X>
, y me sorprendió observar que el ejecutable resultante, con la optimización predeterminada de O2, fue el más rápido. había visto, a veces alcanzando 184 ms. por archivo de entrada. Con este cambio en el código fuente, los resultados correspondientes fueron estos;
| -O2 | -O3 |O2/O3|
----------|-----|-----|-----|
GCC-4.7.2 | 234 | 234 |1.00 |
----------|-----|-----|-----|
Clang-3.2 | 188 | 187 |1.00 |
----------|-----|-----|------
GCC/Clang |1.24 |1.25 |
Los puntos de nota aquí son:
- Ninguno de los compiladores ahora se beneficia en absoluto de la optimización -O3.
- Clang supera a GCC con la misma importancia en cada nivel de optimización.
- El rendimiento de GCC solo se ve afectado marginalmente por el cambio de tipo de puntero inteligente.
- El rendimiento de Clang -O2 se ve afectado de manera importante por el cambio de tipo de puntero inteligente.
Antes y después del cambio de tipo de puntero inteligente, Clang puede construir un ejecutable coan sustancialmente más rápido en la optimización -O3, y puede construir un ejecutable igualmente más rápido en -O2 y -O3 cuando ese tipo de puntero es el mejor std::shared_ptr<X>
- para el trabajo.
Una pregunta obvia de la que no soy competente para comentar es por qué
Clang debería poder encontrar un 25% de aceleración de O2 en mi aplicación cuando un tipo de puntero inteligente muy usado cambia de único a compartido, mientras que GCC es indiferente al mismo cambio Tampoco sé si debería animar o abuchear el descubrimiento de que la optimización de -O2 de Clang alberga una sensibilidad tan grande a la sabiduría de mis elecciones de puntero inteligente.
ACTUALIZACIÓN: GCC 4.8.1 v clang 3.3
Los resultados correspondientes ahora son:
| -O2 | -O3 |O2/O3|
----------|-----|-----|-----|
GCC-4.8.1 | 442 | 443 |1.00 |
----------|-----|-----|-----|
Clang-3.3 | 374 | 370 |1.01 |
----------|-----|-----|------
GCC/Clang |1.18 |1.20 |
El hecho de que los cuatro ejecutables ahora tomen un tiempo promedio mucho mayor que antes para procesar 1 archivo no se refleja en el rendimiento de los últimos compiladores. Es debido al hecho de que la rama de desarrollo posterior de la aplicación de prueba ha adquirido mucha sofisticación de análisis mientras tanto y paga por la velocidad. Solo las proporciones son significativas.
Los puntos de nota ahora no son sorprendentemente novedosos:
- GCC es indiferente a la optimización de -O3
- clang se beneficia muy marginalmente de la optimización de -O3
- Clang supera a GCC por un margen igualmente importante en cada nivel de optimización.
Al comparar estos resultados con los de GCC 4.7.2 y clang 3.2, se destaca que GCC ha recuperado aproximadamente una cuarta parte de la ventaja de clang en cada nivel de optimización. Pero dado que la aplicación de prueba se ha desarrollado mucho mientras tanto, no se puede atribuir con confianza esto a una actualización en la generación de código de GCC. (Esta vez, noté la instantánea de la aplicación de la que se obtuvieron los tiempos y puedo usarla nuevamente).
ACTUALIZACIÓN: GCC 4.8.2 v clang 3.4
Terminé la actualización para GCC 4.8.1 v Clang 3.3 diciendo que me apegaría a la misma instantánea coan para obtener más actualizaciones. Pero decidí probar esa instantánea (rev. 301) y la última instantánea de desarrollo que tengo que pasa su conjunto de pruebas (rev. 619). Esto le da a los resultados un poco de longitud, y tuve otro motivo:
Mi publicación original señaló que no había dedicado ningún esfuerzo a optimizar Coan para la velocidad. Este seguía siendo el caso a partir de la rev. 301. Sin embargo, después de haber incorporado el aparato de sincronización en el arnés de prueba Coan, cada vez que ejecutaba el conjunto de pruebas, el impacto en el rendimiento de los últimos cambios me miraba a la cara. Vi que a menudo era sorprendentemente grande y que la tendencia era más abruptamente negativa de lo que creía que merecen las ganancias en la funcionalidad.
Por rev. 308 el tiempo de procesamiento promedio por archivo de entrada en el conjunto de pruebas se había más que duplicado desde la primera publicación aquí. En ese momento hice un cambio de sentido en mi política de 10 años de no preocuparme por el rendimiento. En la serie intensiva de revisiones, hasta 619 el rendimiento siempre fue una consideración y un gran número de ellos se limitó a reescribir portadores de carga clave en líneas fundamentalmente más rápidas (aunque sin usar ninguna función de compilador no estándar para hacerlo). Sería interesante ver la reacción de cada compilador a este cambio de sentido,
Aquí está la matriz de tiempos ahora familiar para las últimas compilaciones de dos compiladores de rev.301:
coan - rev.301 resultados
| -O2 | -O3 |O2/O3|
----------|-----|-----|-----|
GCC-4.8.2 | 428 | 428 |1.00 |
----------|-----|-----|-----|
Clang-3.4 | 390 | 365 |1.07 |
----------|-----|-----|------
GCC/Clang | 1.1 | 1.17|
La historia aquí solo cambia marginalmente de GCC-4.8.1 y Clang-3.3. La presentación de GCC es un poco mejor. Clang's es un poco peor. El ruido bien podría explicar esto. Clang todavía sale adelante -O2
y -O3
márgenes que no importarían en la mayoría de las aplicaciones, pero que importarían en algunas.
Y aquí está la matriz para rev. 619.
coan - rev.619 resultados
| -O2 | -O3 |O2/O3|
----------|-----|-----|-----|
GCC-4.8.2 | 210 | 208 |1.01 |
----------|-----|-----|-----|
Clang-3.4 | 252 | 250 |1.01 |
----------|-----|-----|------
GCC/Clang |0.83 | 0.83|
Tomando las figuras 301 y 619 una al lado de la otra, se expresan varios puntos.
Tenía el objetivo de escribir código más rápido, y ambos compiladores reivindican enfáticamente mis esfuerzos. Pero:
GCC paga esos esfuerzos mucho más generosamente que Clang. En la -O2
optimización, la construcción 619 de Clang es un 46% más rápida que su construcción 301: -O3
la mejora de Clang es del 31%. Bien, pero en cada nivel de optimización, la construcción 619 de GCC es más del doble de rápida que su 301.
GCC más que revierte la antigua superioridad de Clang. Y en cada nivel de optimización, GCC ahora supera a Clang en un 17%.
La capacidad de Clang en la compilación 301 para obtener más influencia que GCC a partir de la -O3
optimización desapareció en la compilación 619. Ninguno de los compiladores gana significativamente de -O3
.
Estaba tan sorprendido por esta inversión de fortunas que sospeché que podría haber hecho accidentalmente una construcción lenta de clang 3.4 (desde que lo construí desde la fuente). Así que volví a ejecutar la prueba 619 con el stock Clang 3.3 de mi distribución. Los resultados fueron prácticamente los mismos que para 3.4.
Entonces, en lo que respecta a la reacción al cambio de sentido: en los números aquí, Clang ha hecho mucho mejor que GCC a la velocidad de extracción de mi código C ++ cuando no lo estaba ayudando. Cuando me propuse ayudar, GCC hizo un trabajo mucho mejor que Clang.
No elevo esa observación a un principio, pero tomo la lección que dice "¿Qué compilador produce los mejores binarios?" es una pregunta que, incluso si especifica el conjunto de pruebas con el que la respuesta será relativa, todavía no es una cuestión clara de simplemente cronometrar los binarios.
¿Es su mejor binario el binario más rápido, o es el que mejor compensa el código de bajo costo? O mejores compensa costosamente
código diseñado que da prioridad a la mantenibilidad y la reutilización sobre la velocidad? Depende de la naturaleza y los pesos relativos de sus motivos para producir el binario, y de las restricciones bajo las cuales lo hace.
Y, en cualquier caso, si le importa mucho crear "los mejores" archivos binarios, será mejor que siga comprobando cómo las sucesivas iteraciones de compiladores cumplen su idea de "lo mejor" sobre las sucesivas iteraciones de su código.