2012-02-07 12 views
6

Estaba buscando una forma de hacer la codificación algorítmica adecuada usando .NET con todos los beneficios de los lenguajes modernos (por ejemplo, me gusta una verificación de tipo fuerte, sobrecarga del operador, lambdas, algoritmos genéricos). Normalmente escribo mis algoritmos (principalmente procesamiento de imágenes) en C++. Como F # como lenguaje parece ser bastante interesante, jugué un poco, pero parece ser muy lento. Como una prueba más sencilla que acabo de hacer un poco de manipulación de matrices -> aumento de brillo de una imagen:Optimización de código F # o es realmente tan lenta?

let r1 = rgbPixels |> Array.map (fun x -> x + byte(10)) 

parece ser un factor de al menos 8 veces más lento que una implementación en comparación C++ - aún peor para los algoritmos más complejos p.ej Convolución 2D. ¿Hay alguna manera más rápida o me pierdo alguna configuración específica del compilador (sí, creación de versión con optimización en ...)? Estoy dispuesto a pagar por la bonita y alta abstracción, pero una sobrecarga no es buena (necesitaría paralelizar en 8 núcleos para compensar :)) - al menos destruye la motivación para aprender más ... Mi otra La opción sería dejar mis algos más pesados ​​en C++ e interactuar con C++ administrado, pero esto no es bueno, ya que mantener el contenedor administrado sería toda una carga.

+1

Es posible que esté en línea, pero puede comenzar reemplazando la llamada a 'byte' con' 10uy'. – Daniel

+0

¿Puedes publicar tu implementación en C++ en [ideone] (http://ideone.com/) para que tengamos algo con qué trabajar? – Daniel

+0

¿Son [estos resultados] (http://ideone.com/Z4Gvj) comparables a los suyos? – Daniel

Respuesta

6

Si le preocupa el rendimiento, una de las cosas importantes para recordar es que F # por defecto no muta nada. Esto requiere copiar muchas implementaciones ingenuas de algoritmos, como el que describió.

EDITAR: no tengo ni idea de por qué, pero las pruebas simples del siguiente código proporcionan resultados inferiores a Array.map. Asegúrese de crear un perfil de cualquier algoritmo que pruebe al realizar este tipo de optimizaciones. Sin embargo, obtuve resultados muy similares entre for y map.

Array.map crea una nueva matriz para el resultado de la operación, en su lugar desea Array.iteri.

rgbPixels |> Array.iteri (fun i x -> rgbPixels.[i] <- x + 10uy) 

Tenga en cuenta que esto podría estar envuelto en su propio módulo como a continuación

module ArrayM = 
    let map f a = a |> Array.iteri (fun i x -> a.[i] <- f x) 

Desafortunadamente, este es un mal necesario ya que uno de los inquilinos principales de la programación funcional es apegarse a los objetos inmutables tanto como lo permita su algoritmo, y luego una vez terminado, cambie a mutación donde el rendimiento es crítico. Si sabe que su desempeño es crítico desde el primer momento, deberá comenzar con este tipo de ayudantes.

También tenga en cuenta que probablemente haya una biblioteca por ahí que proporciona esta funcionalidad, solo que no la conozco.

+1

O, incluso más rápido: ' para i = 0 a rgbPixels.Length-1 do rgbPixels. [i] <- rgbPixels. [i] + 10uy'. – Daniel

+0

@Daniel: Estaba tratando de mantenerme lo más cerca posible del código original. Supongo que la función está escrita por el JIT de todos modos. – Guvante

+0

@Guvante: Probé con FSI: el ciclo lleva menos de la mitad de tiempo ... probablemente debido a que no se verificaron los límites, como mencionó kvb. – Daniel

5

Creo que es seguro decir que idiomática F # será a menudo no logran igualar el rendimiento de C optimizado ++ para la manipulación de matrices, por varias razones:

  1. accesos de matriz se comparan con los límites de la matriz en .NET para garantizar la seguridad de la memoria. El compilador JIT de CLR es capaz de eludir las comprobaciones de límites de algún código estereotípico, pero esto normalmente requerirá que uses un bucle for con límites explícitos en lugar de más construcciones F # idiomáticas.
  2. Normalmente hay una pequeña cantidad de sobrecarga para usar abstracciones como lambdas (por ejemplo, fun i -> ...). En bucles estrechos, esta pequeña sobrecarga puede llegar a ser significativa en comparación con el trabajo que se realiza en el cuerpo del bucle.
  3. Por lo que sé, el compilador CLR JIT no aprovecha las instrucciones de SSE en la misma medida en que los compiladores de C++ son capaces de hacerlo.

En el otro lado de la balanza,

  1. que nunca tendrá un desbordamiento de búfer en Fa # código.
  2. Su código será más fácil de razonar. Como corolario, para un nivel dado de complejidad total del código, a menudo puede implementar un algoritmo más complejo en F # del que puede en C++.
  3. Si es necesario, puede escribir código unidiomatic que se ejecutará más cerca de la velocidad de C++, y también hay instalaciones para interoperar con código C++ inseguro si encuentra que necesita escribir C++ para cumplir con sus requisitos de rendimiento.
+0

1. Excepto que tiene este bucle en Array.map 2. Lo cual puede evitarse al alinear 3. Por otro lado, la paralelización es mucho más fácil en los lenguajes funcionales (como en agregar una sola llamada a función adicional) y las operaciones vectoriales pueden agregarse técnicamente de manera similar. –

+0

Estoy totalmente de acuerdo con todos los puntos anteriores y estoy absolutamente dispuesto a sacrificar algo de rendimiento (que fue mi motivación para comenzar a jugar con F #), pero los resultados medidos anteriormente muestran un factor de 5 para una operación simple (es por eso que me dije que Hice algo malo). Llegando a la paralelización: si empiezo con un factor de 5, mantendría 5 núcleos ocupados y de todos modos probablemente estaría donde estaba antes con un núcleo en C++ ... – TheBigW

+0

@Maciej - Para código que necesita escribir en la matriz , ninguna de las funciones de orden superior en el módulo 'Array' es equivalente a un ciclo for (es decir, en 'arr |> Array.iteri (fun iv -> arr. [i] <- v + 1)' the check para la lectura se eliminará, pero todavía habrá un cheque para escribir en cada iteración). – kvb

Cuestiones relacionadas