Penghe Geng
Al igual que muchos otros desarrolladores, estoy muy entusiasmado con el nuevo lenguaje Swift de Apple. Apple ha afirmado que su velocidad es más rápida que Objective C y puede usarse para escribir el sistema operativo. Y por lo que aprendí hasta ahora, es un lenguaje de tipo estático y puede tener un control preciso sobre el tipo de datos exacto (como la longitud del entero). Entonces parece tener un buen potencial para manejar tareas críticas de rendimiento, como el procesamiento de imágenes, ¿verdad?
Eso es lo que pensé antes de realizar una prueba rápida. El resultado realmente me sorprendió.
Aquí hay un fragmento de código simple en C:
prueba.c:
#include <stdio.h>
#include <stdint.h>
#include <string.h>
uint8_t pixels[640*480];
uint8_t alpha[640*480];
uint8_t blended[640*480];
void blend(uint8_t* px, uint8_t* al, uint8_t* result, int size)
{
for(int i=0; i<size; i++) {
result[i] = (uint8_t)(((uint16_t)px[i]) *al[i] /255);
}
}
int main(void)
{
memset(pixels, 128, 640*480);
memset(alpha, 128, 640*480);
memset(blended, 255, 640*480);
// Test 10 frames
for(int i=0; i<10; i++) {
blend(pixels, alpha, blended, 640*480);
}
return 0;
}
Lo compilé en mi Macbook Air 2011 con el siguiente comando:
clang -O3 test.c -o test
El tiempo de procesamiento de 10 fotogramas es de aproximadamente 0,01 s. En otras palabras, el código C tarda 1 ms en procesar un cuadro:
$ time ./test
real 0m0.010s
user 0m0.006s
sys 0m0.003s
Luego tengo una versión Swift del mismo código:
prueba.swift:
let pixels = UInt8[](count: 640*480, repeatedValue: 128)
let alpha = UInt8[](count: 640*480, repeatedValue: 128)
let blended = UInt8[](count: 640*480, repeatedValue: 255)
func blend(px: UInt8[], al: UInt8[], result: UInt8[], size: Int)
{
for(var i=0; i<size; i++) {
var b = (UInt16)(px[i]) * (UInt16)(al[i])
result[i] = (UInt8)(b/255)
}
}
for i in 0..10 {
blend(pixels, alpha, blended, 640*480)
}
La línea de comando de compilación es:
xcrun swift -O3 test.swift -o test
aqui uso lo mismo O3
Indicador de optimización de nivel para que la comparación sea justa. Sin embargo, la velocidad resultante es 100 veces más lenta:
$ time ./test
real 0m1.172s
user 0m1.146s
sys 0m0.006s
En otras palabras, a Swift le toma ~120 ms procesar un cuadro, lo que a C le toma solo 1 ms.
¿Qué sucedió?
Actualización: estoy usando clang:
$ gcc -v
Configured with: --prefix=/Applications/Xcode6-Beta.app/Contents/Developer/usr --with-gxx-include-dir=/usr/include/c++/4.2.1
Apple LLVM version 6.0 (clang-600.0.34.4) (based on LLVM 3.5svn)
Target: x86_64-apple-darwin13.2.0
Thread model: posix
Actualización: más resultados con diferentes iteraciones en ejecución:
Aquí está el resultado para un número diferente de “fotogramas”, es decir, cambiar el principal for
bucle número de 10 a otros números. Tenga en cuenta que ahora obtengo un tiempo de código C aún más rápido (¿caché caliente?), Mientras que el tiempo de Swift no cambia demasiado:
C Time (s) Swift Time (s)
1 frame: 0.005 0.130
10 frames(*): 0.006 1.196
20 frames: 0.008 2.397
100 frames: 0.024 11.668
Actualización: `-Ofast` ayuda
Con -Ofast
sugerido por @mweathers, la velocidad de Swift sube a un rango razonable.
En mi laptop la versión Swift con -Ofast
obtiene 0,013 s por 10 fotogramas y 0,048 s por 100 fotogramas, cerca de la mitad del rendimiento de C.
Edificio con:
xcrun swift -Ofast test.swift -o test
Estoy recibiendo tiempos de:
real 0m0.052s
user 0m0.009s
sys 0m0.005s
-
@JeremyBanks: -Ofast cambia la semántica del lenguaje. No es seguro. Estás convirtiendo a Swift en un lenguaje similar a C++. Los desbordamientos de enteros, los desbordamientos de matrices, etc. se ignoran silenciosamente.
– Jukka Suomela
8 de junio de 2014 a las 8:43
-
Más ejemplos del impacto de -Ofast aquí: stackoverflow.com/questions/24101718/…
– Jukka Suomela
8 de junio de 2014 a las 9:17
-
@grasGendarme: la verificación de límites de matriz sí no implica una desaceleración de factor 100 (debería ser más como un factor
– Jukka Suomela
8 de junio de 2014 a las 11:33
-
@JukkaSuomela La verificación de límites de matriz ciertamente puede explicar una desaceleración de 100x en un ciclo interno como este con más de un millón de iteraciones
– gordo
08/06/2014 a las 17:50
-
@gordy: Vea esto para algunas comparaciones de Swift vs. Java vs. Python vs. C++. Y tenga en cuenta que Java también verifica los límites (y más, también tiene que verificar si hay un puntero nulo).
– Jukka Suomela
8 de junio de 2014 a las 19:07
Concentrémonos en la respuesta a la pregunta, que comenzó con un “Por qué”: porque no activó las optimizaciones y Swift depende en gran medida de la optimización del compilador.
Dicho esto, hacer el procesamiento de imágenes en C es realmente tonto. Para eso tienes CGImage y amigos.
Por curiosidad, ¿ayuda reemplazar el cálculo de mezcla con
var b = (UInt16)(px[i]) &* (UInt16)(al[i])
que si leo los documentos correctamente hará que Swift evite la comprobación de desbordamiento.– rico
8 de junio de 2014 a las 3:36
¿Qué sucede si ajusta el código para realizar el mismo proceso dos veces (es decir, amplía las iteraciones de 10 a 20)? Me imagino que iniciar el tiempo de ejecución de Swift cuesta un poco más que iniciar el tiempo de ejecución de C.
– tommy
8 de junio de 2014 a las 3:52
¿Puedes volcar el código ensamblador? supongo que la versión clang puede estar optimizando la división por constante 255
– gordo
8 de junio de 2014 a las 3:53
Intente perfilar solo el
blend
función. Es probable que el llenado de la matriz y las diferencias en la configuración del entorno desempeñen un papel.– Factura
8 de junio de 2014 a las 3:58
Al leer el enlace completo, se puede ver “Cambiar el compilador Swift: el nivel de optimización en Xcode a ‘Más rápido, sin marcar’ aceleró esto para que sea comparable con su C ++”.
–Jim Balter
8 de junio de 2014 a las 4:21