2012-06-09 10 views
13

lectura Comenzando F # - Robert Pickering que se centró en el siguiente párrafo:CLR vs OCaml excepción sobrecarga

programadores procedentes de un fondo OCaml deben tener cuidado al utilizando excepciones en C#. Debido a la arquitectura del CLR, arrojar una excepción es bastante caro, un poco más caro que en OCaml. Si lanza muchas excepciones, perfile su código cuidadosamente para decidir si los costos de rendimiento valen la pena. Si los costos de son demasiado altos, revise el código adecuadamente.

¿Por qué, debido a CLR, lanzar una excepción es más caro si F# que en OCaml? ¿Y cuál es la mejor manera de revisar el código de forma adecuada en este caso?

+1

Hubo una [discusión previa sobre la ligereza de las excepciones OCaml] (http://stackoverflow.com/questions/8564025/ocaml-internals-exceptions) –

+0

@JeffreyScofield gracias, sin embargo, estoy más interesado en los aspectos relacionados con CLR . – gliderkite

Respuesta

7

Reed ya explicó por qué las excepciones .NET se comportan de forma diferente que las excepciones OCaml. En general, las excepciones de .NET son adecuadas solo para situaciones excepcionales y están diseñadas para ese propósito. OCaml tiene un modelo más liviano, por lo que también se utilizan para implementar algunos patrones de flujo de control.

Para dar un ejemplo concreto, en OCaml puede usar la excepción para implementar la ruptura de un bucle. Por ejemplo, supongamos que tiene una función test que prueba si un número es el número que queremos. Los siguientes itera sobre números del 1 al 100 y los retornos que emparejan primer número:

// Simple exception used to return the result 
exception Returned of int 

try 
    // Iterate over numbers and throw if we find matching number 
    for n in 0 .. 100 do 
    printfn "Testing: %d" n 
    if test n then raise (Returned n) 
    -1     // Return -1 if not found 
with Returned r -> r // Return the result here 

Para implementar esto sin excepciones, usted tiene dos opciones. Se podría escribir una función recursiva que tiene el mismo comportamiento - si llama find 0 (y se compila a esencialmente el mismo código IL como el uso de return n dentro for bucle en C#):

let rec find n = 
    printfn "Testing: %d" n 
    if n > 100 then -1 // Return -1 if not found 
    elif test n then n // Return the first found result 
    else find (n + 1) // Continue iterating 

La codificación usando funciones recursivas puede sea ​​un poco largo, pero también puede usar funciones estándar proporcionadas por la biblioteca F #. A menudo, esta es la mejor manera de reescribir el código que usaría excepciones OCaml para el flujo de control. En este caso, se puede escribir:

// Find the first value matching the 'test' predicate 
let res = seq { 0 .. 100 } |> Seq.tryFind test 
// This returns an option type which is 'None' if the value 
// was not found and 'Some(x)' if the value was found. 
// You can use pattern matching to return '-1' in the default case: 
match res with 
| None -> -1 
| Some n -> n 

Si no está familiarizado con los tipos de opción, a continuación, echar un vistazo a algún material introductorio. F# wikibook has a good tutorial y MSDN documentation has useful examples también.

El uso de una función apropiada del módulo Seq a menudo hace que el código sea mucho más corto, por lo que es preferible. Puede ser un poco menos eficiente que usar recursividad directamente, pero en la mayoría de los casos, no necesita preocuparse por eso.

EDIT: Tenía curiosidad sobre el rendimiento real. La versión que usa Seq.tryFind es más eficiente si la entrada se genera de forma lenta secuencia seq { 1 .. 100 } en lugar de una lista [ 1 .. 100 ] (debido a los costos de asignación de lista). Con estos cambios, y test función que devuelve el elemento 25, el tiempo necesario para ejecutar el código 100000 veces en mi máquina es:

exceptions 2.400sec 
recursion 0.013sec 
Seq.tryFind 0.240sec 

Ésta es la muestra extremadamente trivial, así que creo que la solución usando Seq no funcionarán generalmente 10 veces más lento que el código equivalente escrito mediante recursión. La desaceleración probablemente se deba a la asignación de estructuras de datos adicionales (objetos que representan la secuencia, cierres, ...) y también a la indirección adicional (el código necesita numerosas llamadas a métodos virtuales, en lugar de simples operaciones y saltos numéricos). Sin embargo, las excepciones son aún más costosas y no hacen que el código sea más corto o más legible ...

+0

+1 Definitivamente debo leer su libro. Solo una cosa, ¿por qué usar una función de Seq puede ser menos eficiente que usar la recursión directamente? – gliderkite

+0

@gliderkite Ejecuto una prueba primitiva y agrego algo de información de rendimiento. –

12

Las excepciones en el CLR son muy abundantes y brindan muchos detalles. Rico Mariani publicó una publicación (antigua, pero relevante) en the cost of exceptions en el CLR que detalla algo de esto.

Como tal, lanzar una excepción en el CLR tiene un costo relativo mayor que en otros entornos, incluido OCaml.

¿Y cuál es la mejor manera de revisar el código adecuadamente en este caso?

Si esperas la excepción a ser criados en circunstancias normales, no excepcionales, puede reconsiderar su algoritmo y la API de una manera que evita la excepción del todo. Intente proporcionar una API alternativa en la que pueda probar las circunstancias antes de provocar la excepción, por ejemplo.

+1

El tipo 'option' es extremadamente relevante aquí. – ildjarn

+0

@ildjarn Sí, exactamente. A menudo, puede usar 'None' en lugar de usar excepciones. –

+0

Disculpa, no sé el tipo 'Ninguno' y' opción'. ¿Podría elaborar estos aspectos relevantes? – gliderkite

8

¿Por qué, debido a CLR, lanzar una excepción es más caro si F # que en OCaml?

OCaml fue muy optimizado para el uso de excepciones como flujo de control. Por el contrario, las excepciones en .NET no se han optimizado en absoluto.

Tenga en cuenta que la diferencia de rendimiento es enorme. Las excepciones son alrededor de 600 × más rápido en OCaml que en F #. Incluso C++ es alrededor de 6 × más lento que OCaml en este sentido, de acuerdo con mis puntos de referencia.

Aunque supuestamente las excepciones de .NET proporcionan más (OCaml proporciona rastros de pila, ¿qué más quieres?) No puedo pensar en ninguna razón por la que deberían ser tan lentas como son.

¿Y cuál es la mejor manera de revisar el código adecuadamente en este caso?

En F #, debe escribir las funciones "totales". Esto significa que su función debe devolver un valor de tipo de unión que indique el tipo de resultado, p. normal o excepcional.

En particular, las llamadas a find debe sustituirse con llamadas a tryFind que devuelven un valor del tipo de option que es ya sea Some valor o None si el elemento clave no estaba presente en la colección.

+0

'No puedo pensar en ninguna razón por la cual deberían ser tan lentos como lo son. Esto es exactamente lo que estoy preguntando. El enlace [_El costo de las excepciones_] (http://blogs.msdn.com/b/ricom/archive/2003/12/19/44697.aspx) publicado por Reed Copsey explica muy simplemente por qué esta lentitud. – gliderkite

+0

@gliderkite Ese artículo indica que .NET es lento y describe soluciones (evite esta parte de .NET) pero no creo que describa por qué. Creo que la respuesta es simplemente que Microsoft nunca se molestó en optimizarlo. –

+0

'No creo que describa por qué' desafortunadamente es así, pero no creo que Microsoft nunca se haya tomado la molestia de optimizarlo, ¿qué ganancia habría obtenido de esta manera? – gliderkite

Cuestiones relacionadas