One of the responses I often get to this strategy is that handling exceptions is slow. Why risk a slow exception handler if you can avoid it most of the time with a quick File.Exists() check? I think this argument misses the point first of all for correctness reasons. You still need the exception handler, and using File.Exists() to avoid it is a mistake. But more than that, I think that is just plain wrong about the performance issue, too. Here’s why.
Yes, handling exceptions is expensive from a performance standpoint; very expensive. Let’s get that out of the way: I’m not trying to say that exceptions should be your first choice in every situation. The list of things you can do in programming that are slower is very short. However, the list is not empty. Do you know what’s worse than exceptions? I/O. Disk and Network are far and away worse. Let me explain. Here’s a link and except that show just how much worse they can be:
Latency Comparison Numbers -------------------------- L1 cache reference 0.5 ns Branch mispredict 5 ns L2 cache reference 7 ns 14x L1 cache Mutex lock/unlock 25 ns Main memory reference 100 ns 20x L2 cache, 200x L1 cache Compress 1K bytes with Zippy 3,000 ns Send 1K bytes over 1 Gbps network 10,000 ns 0.01 ms Read 4K randomly from SSD* 150,000 ns 0.15 ms Read 1 MB sequentially from memory 250,000 ns 0.25 ms Round trip within same datacenter 500,000 ns 0.5 ms Read 1 MB sequentially from SSD* 1,000,000 ns 1 ms 4X memory Disk seek 10,000,000 ns 10 ms 20x datacenter roundtrip Read 1 MB sequentially from disk 20,000,000 ns 20 ms 80x memory, 20X SSD Send packet CA->Netherlands->CA 150,000,000 ns 150 ms
If thinking in nanoseconds isn’t your thing, here’s another reference that normalizes a single CPU cycle as 1 second and scales from there:
1 CPU cycle 0.3 ns 1 s Level 1 cache access 0.9 ns 3 s Level 2 cache access 2.8 ns 9 s Level 3 cache access 12.9 ns 43 s Main memory access 120 ns 6 min Solid-state disk I/O 50-150 μs 2-6 days Rotational disk I/O 1-10 ms 1-12 months Internet: SF to NYC 40 ms 4 years Internet: SF to UK 81 ms 8 years Internet: SF to AUS 183 ms 19 years OS virt. reboot 4 s 423 years SCSI command time-out 30 s 3000 years Hardware virt. reboot 40 s 4000 years Physical system reboot 5 m 32 millenia
Taking even the best-case scenario for exceptions, you can access memory at least 480 times while waiting on the first response from a disk, and that’s assuming a very fast SSD. Many of us still need spinning hard-drives, where things get much, much worse.
For a comparison reference, Jon Skeet has blogged about exception handling, where he was able to handling them at a rate of between 42 and 188 per millisecond. While there were some issues with his benchmark, I think the point is spot on: relative to other options, exceptions may not be as bad as you think.
And that’s only the beginning of the story. When you use .Exists(), you incur this additional cost (and it is an addition: you have to do the same work again when you go to open the file) on every attempt. You pay this costs whether the file exists or not, because the disk still has to go look for it in it’s file tables. With the exception method, you only pay the extra costs like unwinding the call stack in the case of failure.
In other words, yes: exceptions are horribly costly. But compared to the disk check, it’s still faster — and not by just a small margin. Thankfully, this is unlikely to drive your app’s general performance… but I still want to put to bed the “exceptions are slow” argument for this specific task.