Do not use flags you do not fully understand

Sometimes developers use a flag or option that looks logical to them, but they do not fully understand it. It could even work in a particular situation but fail miserably under different circumstances. Let me explain what I mean.

At the company where I work, we typically ask candidates to do a test task. The task is very simple, and the algorithm is well known and clearly explained in 1973. For a good developer, it will take half a day. Maybe even less. The final part of that task is to copy lines from many smaller files into one big one. The smaller files are typically around 100 Mb in size.

Several days ago I got the test task from one of the candidates, and it was very slow. Typically, it is quite easy for me to spot why the task is slow. I’ve been checking this task for more than 10 years, and I can typically find it in less than a minute.

But this task was different. It was quite slow, and I couldn't find why. Everything looks right, and the application should be fast. I checked again and again without any success. This was the first (or perhaps second) time I started a profiler for a test task, but the profiler told me that the function ReadFile was very slow.

Initially, I suspected a very small buffer size, but I checked all parameters for FileStream creation, and everything looks correct. The person even specified FileOptions.SequentialScan, which looks logical.

Then I thought that my disk was dying. And it is important to point out that I’m using an old HDD for my tests because it is quite slow, and any inefficiency becomes immediately visible.

For example, one of the applications from candidates reads the input file just to find the longest line. On an SSD is it could take an extra second or even less. Gen 5 SSD can read 1 GB in just 0.1 seconds. This kind of thing can be easily missed when somebody writes it in a single line using LINQ, but with an HDD it does not work this fast, and the application will be slower. So it will be easier to spot.

But hard drives do not last forever. There are a lot of mechanical parts, and eventually they will die. So I added test code and read all lines from each file. It was fast. Then I changed the test code to read it the same way as the application did. Everything was still fast. Then I deleted the test code and commented out the writing to file. Still fast.

I suspected that the write code was inefficient, but when I replaced it with a standard WriteLine, it was still slow. Then I suspected that perhaps the buffer size was too small. But the buffer size was also fine. But this file stream also had FileOptions.SequentialScan. Out of curiosity, I removed it, and everything started to work really fast. Then I removed it from readers, and it became even faster.

Then I started reading about this flag. This flag forces Windows to aggressively prefetch data ahead of time. An HDD can take a lot of time to read the next block of data, so when the application reads 1 byte, Windows will read tens of kilobytes or perhaps even megabytes of data ahead of time, so they will be ready when the application requests them later.

I think only a subset of Windows cache can be used for this purpose, so perhaps all readers and the writer all fight for the same data in cache and evict each other's data. As a result, all of them have to constantly re-read data from the HDD, and HDD’s are quite slow for random access data.

After I removed the writer, I think it has almost enough cache, and the eviction became much less problematic. But when I removed this flag from readers and the writer, Windows will use quite a small cache for each of them, and that is enough for everyone, and everybody is happy.

Obviously, the candidate didn’t see this issue because most people are using SSDs these days. I think on an SSD this flag is probably completely or partially ignored because it is very fast to get the next block of data. Or perhaps it is fast enough because an SSD is still quite fast at refilling the cache.

What is the conclusion here? You need to measure first and do not do “premature optimization” just because this flag sounds right. I’m pretty sure that even on an SSD with this flag, the application will be a little bit slower. Or at least not faster. So, if you don’t see a clear benefit, there is no reason to use a flag that is clearly designed for some specialized case. Keep it simple.

I hope it helps someone.

Comments

Post comment