2010-10-15 22 views
18

Mi aplicación atraviesa un árbol de directorios y en cada directorio intenta abrir un archivo con un nombre particular (usando File.OpenRead()). Si esta llamada arroja FileNotFoundException, entonces sabe que el archivo no existe. ¿Prefiero tener una llamada File.Exists() antes de eso para verificar si el archivo existe? ¿Sería esto más eficiente?¿Las excepciones reducen el rendimiento?

+17

Las excepciones están diseñadas para manejar cosas inesperadas, las pondría para proteger su aplicación de fallas fatales (punteros nulos, etc.). Usarlos en el flujo de programa normal es una mala práctica. –

+1

@Yarek Si bien es un buen consejo en general, es inútil aquí si no le das una alternativa. – CodesInChaos

+0

Estaba comentando su pregunta. Creo que fue respondida bastante bien por Mark Byers. –

Respuesta

5

¡Depende!

Si hay una gran posibilidad de que el archivo esté allí (usted sabe esto para su escenario, pero como un ejemplo algo así como desktop.ini) Prefiero tratar directamente de abrirlo. De todos modos, en caso de utilizar File.Exist debe poner File.OpenRead en try/catch por razones de concurrencia y evitar cualquier excepción de tiempo de ejecución, pero aumentaría considerablemente el rendimiento de la aplicación si la posibilidad de que el archivo exista es bajo. Ostrich algorithm

24

actualización

que corrieron estos dos métodos en un bucle y cronometrados cada uno:

void throwException() 
{ 
    try 
    { 
     throw new NotImplementedException(); 
    } 
    catch 
    { 
    } 
} 

void fileOpen() 
{ 
    string filename = string.Format("does_not_exist_{0}.txt", random.Next()); 
    try 
    { 
     File.Open(filename, FileMode.Open); 
    } 
    catch 
    { 
    } 
} 

void fileExists() 
{ 
    string filename = string.Format("does_not_exist_{0}.txt", random.Next()); 
    File.Exists(filename); 
} 

Random random = new Random(); 

Estos son los resultados sin el depurador asociado y corriendo una versión de lanzamiento:

 
Method   Iterations per second 
throwException     10100 
fileOpen       2200 
fileExists      11300 

El costo de lanzar una excepción es mucho más alto de lo que esperaba, una Llamar FileOpen a un archivo que no existe parece mucho más lento que comprobar la existencia de un archivo que no existe.

En el caso donde el archivo a menudo no estará presente, parece ser más rápido verificar si el archivo existe. Me imagino que en el caso contrario, cuando el archivo está generalmente presente, encontrará que es más rápido detectar la excepción. Si el rendimiento es crítico para su aplicación, le sugiero que realice una evaluación comparativa de ambos apporaches sobre datos realistas.

Como se menciona en otras respuestas, recuerde que incluso cuando comprueba la existencia del archivo antes de abrirlo, debe tener cuidado con la condición de carrera si alguien elimina el archivo después de su comprobación de existencia pero justo antes de abrirlo. Aún necesita manejar la excepción.

+1

Responde ignorando por completo el hecho de que el archivo que se va a abrir puede faltar en la mayoría de los directorios. En tal caso, su recomendación sería muy costosa en realidad. –

+4

Pero antes de que el framework sepa que necesita lanzar una excepción, tiene que hacer algo de trabajo. ¿Estás diciendo que ese trabajo NO es trabajo de E/S? –

+1

¿Puede brindar más información al respecto? En el caso de que la excepción no sea aciertada (y el archivo existente) es definitivamente más rápido, pero presumiblemente si el archivo existe, lo está leyendo, lo cual no es mucho más lento que la determinación del archivo, ¿verdad? (a menos que el archivo sea un archivo vacío y su existencia es lo único que importa). Si se lanza la excepción y la pila se desenrolla, ¿no es más lenta? (stat + stack unwind v. stat + jump). Si conoce algún artículo, libro o enlace sobre excepciones y E/S, bríndelos. Gracias. –

1

Yo diría que, en general, ¡las excepciones "aumentan" el "rendimiento" general de su sistema!

En su muestra, de todos modos, es mejor utilizar File.Exists ...

+0

Creo que estoy de acuerdo, aunque si lo hago, me arriesgo a entrar en una condición de carrera, pero para mis propósitos está bien. ¿Por qué crees que es mejor usar File.Exists? – akonsu

+0

La condición de carrera solo ocurre si estos archivos se eliminan y vuelven a crear rápidamente, ¿no? –

+0

@Jared Updike: Exactamente lo que estaba escribiendo :) – Lorenzo

7

¿Este comportamiento es realmente excepcional? Si se espera, deberías probar con una instrucción if y no usar excepciones en absoluto. El rendimiento no es el único problema con esta solución y, por el sonido de lo que intenta hacer, el rendimiento no debería ser un problema. Por lo tanto, el estilo y un buen enfoque deberían ser los puntos de preocupación con esta solución.

Por lo tanto, para resumir, dado que espera que algunas pruebas fallen, utilice el archivo. Exige verificar en lugar de detectar excepciones después del hecho. Debería capturar otras excepciones que puedan ocurrir, por supuesto.

+0

Una excepción de IO es lo que Eric Lippert llama una [excepción exógena] (http://blogs.msdn.com/b/ericlippert/archive/2008/09/10/vexing-exceptions.aspx). Definitivamente debe ser manejado. Pero estoy de acuerdo con usted en que la verificación debe ser introducida dependiendo de si la falta de existencia del archivo es algo que se espera con la ejecución regular del programa. –

+4

Necesita capturar y manejar las excepciones de todos modos. En primer lugar, porque Exists crea una condición de carrera (y por lo tanto hace que el código sea más difícil de entender) y hay otras maneras en que File.OpenRead puede fallar. – CodesInChaos

+0

Muy buena respuesta, excepto por el hecho de que 'File.Exists' es un buen sustituto de' File.TryOpenRead'. No es lo mismo, no resuelve el problema, así que no lo uses. –

10

No, no. Si usa File.Exists, introduce un problema de simultaneidad. Si usted escribió este código:

if file exists then 
    open file 

entonces si otro programa borra el archivo entre el momento en que marcó File.Exists y antes de que realmente abre el archivo, el programa seguirá habiendo lanzar una excepción.

En segundo lugar, incluso si existe un archivo, eso no significa que realmente pueda abrir el archivo, es posible que no tenga permiso para abrirlo o que el archivo sea de solo lectura para que no pueda abrirlo en modo de escritura, etc.

E/S de archivo es mucho, mucho más caro que la excepción, no hay necesidad de preocuparse por el rendimiento de las excepciones.

EDIT: Benchmarking Excepción vs existe en Python en Linux

import timeit 
setup = 'import random, os' 

s = ''' 
try: 
    open('does not exist_%s.txt' % random.randint(0, 10000)).read() 
except Exception: 
    pass 
''' 
byException = timeit.Timer(stmt=s, setup=setup).timeit(1000000) 

s = ''' 
fn = 'does not exists_%s.txt' % random.randint(0, 10000) 
if os.path.exists(fn): 
    open(fn).read() 
''' 
byExists = timeit.Timer(stmt=s, setup=setup).timeit(1000000) 

print 'byException: ', byException # byException: 23.2779269218 
print 'byExists: ', byExists # byExists: 22.4937438965 
+1

¿Qué crees que hace el framework antes de saber que necesita lanzar la excepción? Actúas como si pudieras ejecutar mágicamente algún noop y sabes que el archivo no está allí. –

+0

Supongo que el framework simplemente intenta abrir el archivo usando la llamada del sistema operativo subyacente (probablemente 'CreateFile' en Windows, y si vuelve a funcionar sabe que tuvo éxito, de lo contrario sabe que falló. No hay condición de carrera. – dsolimano

+0

Tiene problemas de simultaneidad, pero el punto de referencia puede ser engañoso. Las excepciones en Python son prácticamente gratuitas: cada llamada de función ya tiene la sobrecarga de pasar una excepción. El CLR puede tener un comportamiento diferente. –

3

No sé acerca de la eficiencia, pero yo preferiría los File.Exists cheque. El problema son todas las otras cosas que podrían suceder: mal manejo del archivo, etc. Si la lógica de su programa sabe que a veces el archivo no existe y desea tener un comportamiento diferente para los archivos existentes versus los no existentes, use File. Existe. Si su falta de existencia es la misma que otras excepciones relacionadas con archivos, solo use el manejo de excepciones.

+0

Muy buena respuesta, excepto por el hecho de que 'File.Exists' es un buen sustituto de' File.TryOpenRead'. No es lo mismo, no resuelve el problema, así que no lo uses. –

+0

@Ben: es bueno saberlo, excepto que File.TryOpenRead no existe? (Google tiene cuatro hits, incluida esta pregunta.) –

1

El problema con el uso File.Exists primera es que abre el archivo también. Entonces terminas abriendo el archivo dos veces. No lo he medido, pero creo que esta apertura adicional del archivo es más costosa que las excepciones ocasionales.

Si la comprobación File.Exists mejora el rendimiento depende de la probabilidad de que el archivo exista. Si es posible, entonces no use File.Exists, si por lo general no existe, la verificación adicional mejorará el rendimiento.

+0

Bueno, estas excepciones no son ocasionales porque estoy buscando archivos y existen solo en un número limitado de directorios, así que recibo toneladas de excepciones ... – akonsu

+0

Así que fallan más verificaciones que tener éxito? En ese caso, puede probar y medir si existe es más rápido. Pero debe documentar que solo se trata de una optimización del rendimiento y que su código funciona incluso si se produce una condición de carrera. – CodesInChaos

+2

No, File.Exists no abre el archivo. No existe una sobrecarga de crear un objeto de sistema operativo para representar un controlador de archivo abierto; solo tiene que leer la entrada del directorio. Y ni siquiera incurrirá en ese costo dos veces, porque después de la comprobación de File.Exists, la entrada del directorio estará en el caché en memoria del sistema operativo. –

3

Sí, debe usar File.Exists. Se deben usar excepciones para situaciones excepcionales para no controlar el flujo normal de su programa. En su caso, un archivo que no está allí no es una ocurrencia excepcional. Por lo tanto, no deberías confiar en las excepciones.

ACTUALIZACIÓN:

para que todos puedan probarlo por sí mismos, voy a publicar mi código de prueba. Para los archivos no existentes, depender de File.Open para arrojar una excepción es aproximadamente 50 veces peor que consultar con File.Exists.

class Program 
{ 
    static void Main(string[] args) 
    { 
     TimeSpan ts1 = TimeIt(OpenExistingFileWithCheck); 

     TimeSpan ts2 = TimeIt(OpenExistingFileWithoutCheck); 

     TimeSpan ts3 = TimeIt(OpenNonExistingFileWithCheck); 

     TimeSpan ts4 = TimeIt(OpenNonExistingFileWithoutCheck); 
    } 

    private static TimeSpan TimeIt(Action action) 
    { 
     int loopSize = 10000; 

     DateTime startTime = DateTime.Now; 
     for (int i = 0; i < loopSize; i++) 
     { 
     action(); 
     } 

     return DateTime.Now.Subtract(startTime); 
    } 

    private static void OpenExistingFileWithCheck() 
    { 
     string file = @"C:\temp\existingfile.txt"; 
     if (File.Exists(file)) 
     { 
     using (FileStream fs = File.Open(file, FileMode.Open, FileAccess.Read)) 
     { 
     } 
     } 
    } 

    private static void OpenExistingFileWithoutCheck() 
    { 
     string file = @"C:\temp\existingfile.txt"; 
     using (FileStream fs = File.Open(file, FileMode.Open, FileAccess.Read)) 
     { 
     } 
    } 

    private static void OpenNonExistingFileWithCheck() 
    { 
     string file = @"C:\temp\nonexistantfile.txt"; 
     if (File.Exists(file)) 
     { 
     using (FileStream fs = File.Open(file, FileMode.Open, FileAccess.Read)) 
     { 
     } 
     } 
    } 

    private static void OpenNonExistingFileWithoutCheck() 
    { 
     try 
     { 
     string file = @"C:\temp\nonexistantfile.txt"; 
     using (FileStream fs = File.Open(file, FileMode.Open, FileAccess.Read)) 
     { 
     } 
     } 
     catch (Exception ex) 
     { 
     } 
    } 
} 

En mi computadora:

  1. TS1 = .75 segundos (lo mismo con o sin depurador asociado)
  2. TS2 = .56 segundos (lo mismo con o sin depurador asociado)
  3. TS3 = .14 segundos (lo mismo con o sin depurador conectado)
  4. ts4 = 14.28 segundos (con el depurador conectado)
  5. ts4 = 1.07 (sin deb ugger adjunta)

UPDATE:

I añadió detalles sobre si un dubgger estaba unido o no. Probé la versión de depuración y liberación, pero lo único que hizo la diferencia fue la función que terminó lanzando excepciones mientras el depurador estaba conectado (lo cual tiene sentido). Sin embargo, consultar con File.Exists es la mejor opción.

+1

Has explicado por qué se debe usar 'File.TryOpenRead' en lugar de' File.OpenRead'. Desafortunadamente 'File.TryOpenRead' no existe en realidad. Y 'File.Exists' no es en absoluto equivalente a' File.TryOpenRead'. –

+0

Eso me confundió ... no pude obtener ningún hits en Google para TryOpenRead. Supongo que si existiera ... ¿sería útil? –

+1

Sí, por lo general trato de seguir con las funciones que existen ..... –

4

File.Exists es una buena primera línea de defensa. Si el archivo no existe, puede obtener una excepción si intenta abrirlo. El control de existencia es más barato que el costo de lanzar y atrapar una excepción. (Tal vez no es mucho más barato, pero un poco.)

También hay otra consideración: depuración. Cuando se ejecuta en el depurador, el costo de lanzar y atrapar una excepción es mayor, porque el IDE tiene ganchos en el mecanismo de excepción que aumenta su sobrecarga. Y si ha marcado cualquiera de las casillas de verificación "Romper al lanzarse" en Depurar> Excepciones, todas las excepciones evitables se convierten en un gran punto de dolor. Por esa sola razón, yo argumentaría para prevenir excepciones cuando sea posible.

Sin embargo, todavía necesita el try-catch, por las razones señaladas por otras respuestas aquí. La llamada File.Exists es simplemente una optimización; no le ahorra la necesidad de atrapar excepciones debido a tiempos, permisos, destellos solares, etc.

+0

Buen punto. El costo de la depuración probablemente sea la consideración más relevante. – CodesInChaos

5

¿No sería más eficiente ejecutar una búsqueda en el directorio, encontrarla e intentar abrirla?

Dim Files() as string = System.IO.Directory.GetFiles("C:\", "SpecificName.txt", IO.SearchOption.AllDirectories) 

Luego obtendría una serie de cadenas que usted sabe que existen.

Ah, y como respuesta a la pregunta original, diría que sí, try/catch introduciría más ciclos de procesador, también asumiría que IO peeks en realidad lleva más tiempo que la sobrecarga de los ciclos del procesador.

Ejecutar el existe primero, luego el segundo abierto, son 2 funciones IO contra 1 de solo intentar abrirlo. Así que realmente, yo diría que el rendimiento general va a ser una decisión sobre el tiempo del procesador frente a la velocidad del disco duro en la PC en la que se ejecutará. Si tienes un procesador más lento, me gustaría ir con el cheque, si tienes un procesador rápido, podría ir con el try/catch en este.

+0

+1. Las excepciones son irrelevantes: si está recorriendo el árbol, * ya sabe si el archivo existe * (excepto en el escenario de la carrera, pero es mucho menos probable ahora que sabe aproximadamente dónde están los archivos). Usar la API diseñada para * este caso de uso exacto * es solo el siguiente paso lógico;) – SimonJ

+0

Obtendría archivos de todos los directorios, luego verificaría la existencia de archivos en la lista, cuando esté en dicho directorio. Sería rápido para los archivos que no se eliminan mientras tanto, y volvería a la velocidad 'lenta' si hay excepciones que se lanzarán. –

0

La sobrecarga de una excepción es notable, pero no es significativa en comparación con las operaciones de archivos.

Cuestiones relacionadas