2

Hmm i wonder whether is a way to read a FILE faster than using fscanf()

For example suppose that i have this text

4

55 k

52 o

24 l

523 i

First i want to read the first number which gives us the number of following lines.

Let this number be called N.

After N, I want to read N lines which have an integer and a character. With fscanf it would be like this

fscanf(fin,"%d %c",&a,&c);
Matteo Italia
  • 123,740
  • 17
  • 206
  • 299
Sotiris
  • 49
  • 1
  • 4
  • 6

6 Answers6

3

You do almost no processing so probably the bottleneck is the file system throughput. However you should measure first if it really is. If you don't want to use a profiler, you can just measure the running time of your application. The size of input file divided by the running time can be used to check if you've reached the file system throughput limit.

Then if you are far away from aforementioned limit you probably need to optimize the way you read the file. It may be better to read it in larger chunks using fread() and then process the buffer stored in memory with sscanf().

You also can parse the buffer yourself which would be faster than *scanf().

[edit]

Especially for Drakosha:

$ time ./main1
Good entries: 10000000

real    0m3.732s
user    0m3.531s
sys 0m0.109s
$ time ./main2
Good entries: 10000000

real    0m0.605s
user    0m0.496s
sys 0m0.094s

So the optimized version makes ~127MB/s which may be my file system's bottleneck or maybe OS caches the file in RAM. The original version is ~20MB/s.

Tested with a 80MB file:

10000000

1234 a

1234 a
...

main1.c

#include <stdio.h>

int ok = 0;
void processEntry(int a, char c) {
    if (a == 1234 && c == 'a') {
        ++ok;
    }
}

int main(int argc, char **argv) {
    FILE *f = fopen("data.txt", "r");
    int total = 0;
    int a;
    char c;
    int i = 0;

    fscanf(f, "%d", &total);
    for (i = 0; i < total; ++i) {
        if (2 != fscanf(f, "%d %c", &a, &c)) {
            fclose(f);
            return 1;
        }
        processEntry(a, c);
    }
    fclose(f);
    printf("Good entries: %d\n", ok);
    return (ok == total) ? 0 : 1;
}

main2.c

#include <stdio.h>
#include <stdlib.h>

int ok = 0;
void processEntry(int a, char c) {
    if (a == 1234 && c == 'a') {
        ++ok;
    }
}

int main(int argc, char **argv) {
    FILE *f = fopen("data.txt", "r");
    int total = 0;
    int a;
    char c;
    int i = 0;
    char *numberPtr = NULL;
    char buf[2048];
    size_t toProcess = sizeof(buf);
    int state = 0;
    int fileLength, lengthLeft;

    fseek(f, 0, SEEK_END);
    fileLength = ftell(f);
    fseek(f, 0, SEEK_SET);

    fscanf(f, "%d", &total);  // read the first line

    lengthLeft = fileLength - ftell(f);

    // read other lines using FSM
    do {
        if (lengthLeft < sizeof(buf)) {
            fread(buf, lengthLeft, 1, f);
            toProcess = lengthLeft;
        } else {
            fread(buf, sizeof(buf), 1, f);
            toProcess = sizeof(buf);
        }
        lengthLeft -= toProcess;
        for (i = 0; i < toProcess; ++i) {
            switch (state) {
                case 0:
                    if (isdigit(buf[i])) {
                        state = 1;
                        a = buf[i] - '0';
                    }
                    break;
                case 1:
                    if (isdigit(buf[i])) {
                        a = a * 10 + buf[i] - '0';
                    } else {
                        state = 2;
                    }
                    break;
                case 2:
                    if (isalpha(buf[i])) {
                        state = 0;
                        c = buf[i];
                        processEntry(a, c);
                    }
                    break;
            }
        }
    } while (toProcess == sizeof(buf));

    fclose(f);
    printf("Good entries: %d\n", ok);
    return (ok == total) ? 0 : 1;
}
ssmir
  • 1,522
  • 8
  • 10
  • What is the time diff? On what file size did u test? – Drakosha Jan 31 '11 at 15:48
  • @ssmir: +1 for the effort. Can you please test in then opposite order, i.e. main2, after that main1. I believe the difference is because of FS caching. Even better in the code to measure the time of IO, this way all the rest of the time will be calculations. – Drakosha Jan 31 '11 at 22:38
  • 1
    @Drakosha Order doesn't matter, I ran each of them for about five times in a row and posted the final result, when it has stabilized. And of cause FS caching plays big role here because I've reached 285 MB/s on another PC with SATA HDD. If I change the way of reading the file to plain fgetc (http://codepad.org/r5gqC7S4), I get 1.806s on the system where I ran other two versions. – ssmir Feb 01 '11 at 09:44
  • @ssmir: Thanks, i wonder whether the reads make the difference or your implementation of the parsing. – Drakosha Feb 01 '11 at 20:05
  • @Drakosha Both, of course. If you read a file byte-by-byte, your C library would not read it ahead because it may block e.g. when you read from stdin. So every `fgetc()` would need at leas one system call. But when you use `fread()` you read a large chunk in one system call. It's much faster. My parsing algorithm is definitely very fast. – ssmir Feb 01 '11 at 20:15
  • @ssmir: i'd read in larger chunks, i.e. 256K, this should give additional improvement imho. – Drakosha Feb 01 '11 at 20:30
  • @Drakosha I tried reading in 4k chunks but there was no difference. Why 256k? – ssmir Feb 01 '11 at 20:40
  • @ssmir: from what i know, the IO time is built from two parts, (1) setup time = const and (2) the io time. (2) is supposed to be pretty constant till 128K-256K and after than become linear in IO size. – Drakosha Feb 02 '11 at 08:52
  • @Drakosha (1) is mostly a context switch and (2) is plain copying from a system buffer to a user buffer (if the whole file is already cached by the OS). So (1)+(2) would be quite constant until (1) ~= (2). Maybe 128k is when copy is as expensive as a context switch. However it's quite possible that with big chunk sizes I/O operation may be preempted so it's not good to read in very large chunks. Anyway the optimal chunk size have to be selected manually after series of tests. – ssmir Feb 02 '11 at 10:43
  • I'm implementing AES with a faster algorithm! Reading the file was a bottleneck for the speed. This article is a lot helpful! Thanks @ssmir –  Mar 30 '17 at 01:57
1

It is unlikely you can significantly speed-up the actual reading of the data. Most of the time here will be spent on transferring the data from disk to memory, which is unavoidable.

You might get a little speed-up by replacing the fscanf call with fgets and then manually parsing the string (with strtol) to bypass the format-string parsing that fscanf has to do, but don't expect any huge savings.

In the end, it is usually not worth it to heavily optimise I/O operations, because they will typically be dominated by the time it takes to transfer the actual data to/from the hardware/peripherals.

Bart van Ingen Schenau
  • 15,488
  • 4
  • 32
  • 41
0

As usual, start with profiling to make sure this part is indeed a bottleneck. Actually, FileSystem cache should make the small reads that you are doing not very expensive, however reading larger parts of the file to memory and then operating on memory might be (a little) faster. In case (which i believe is extremely improbable) is that you need to save every CPU cycle, you might write your own fscanf variant, since you know the format of the string and you only need to support only one variant. But this improvement would bring low gains also, especially on modern CPUs.

The input looks like in various programming contests. In this case - optimize the algorithm, not the reading.

Drakosha
  • 11,925
  • 4
  • 39
  • 52
0

fgets() or fgetc() are faster, as they don't need to drag the whole formatting/variable argument list ballet of fscanf() into the program. Either one of those two functions will leave you with a manual character(s)-to-integer conversion however. Still, the program as whole will be much faster.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • How can reading a file byte by byte be faster then reading the file in large chunks? – ssmir Jan 31 '11 at 13:39
  • @Drakosha. A lot. Naturally, it depends entirely on CPU, OS and compiler. – Lundin Jan 31 '11 at 13:40
  • @ssmir The actual read time from the disk is likely constant no matter the algorithm. This depends entirely on the hardware and system, neither was mentioned in the original post. Without the hardware access time in account, we only have the algorithm overhead left to play with. And all printf/scanf functions are notoriously slow. – Lundin Jan 31 '11 at 13:43
  • @Lundin: the distribution of time probably will be: disk > 99% fscanf < 1%. That's why i believe "A lot" might be in terms of fscanf, but not in terms of the whole task. – Drakosha Jan 31 '11 at 15:46
  • @Drakosha No, you're assuming that the file is on a hard drive and not in flash memory, RAM or EEPROM. Nobody mentioned PC programming :) – Lundin Jan 31 '11 at 22:21
0

Not much hope to read file faster as it is a system call. But there is many ways to parse it faster than scanf with specialised code.

kriss
  • 23,497
  • 17
  • 97
  • 116
  • There is nothing in the C standard stating that fscanf() must be calling some OS routine, however. In some cases, fscanf() may be the only layer on top of the hardware. – Lundin Jan 31 '11 at 13:47
  • @Lundin: do you know of of any actual system where it is the case ? It would be quite strange to have the lowest level API at 'FILE*' level and doing both reading and parsing, even more so if it is done at hardware level. But, OK it's not impossible... just poor design. – kriss Feb 21 '11 at 07:31
  • I have seen fscanf() etc functions implemented as flash programming routines on several microcontrollers. I don't find it strange or poor design, a computer may very well have a file system but no OS. One example of application would be mp3 players. – Lundin Feb 21 '11 at 07:48
  • @Lundi: ok, it make sense. I mostly disagreed because when you spoke of "hardware" I didn't thought of flash programs, was more thinking of ASICs. But OK, at some level you can't modify what is provided in such embedded libraries. I would still find it strange if to read some raw bytes from a file I would have to loop using `fscanf(f, "%c", &c);` instead of doing `read(fd, buf, n)` or `fread(p, 1, n, f);` if plain read is not available. But I would bet that fread was also available on the systems you were speaking of. – kriss Feb 23 '11 at 18:56
0

Checkout read and fread. As you practice for programming contests, you can ignore all warnings about disk IO buttle neck, cause files can be in memory or pipes from other processes generating tests "on-the-fly".

Put your tests into /dev/shm (new solution for tmpfs) or make test generator and pipe it.

I've found on programming contests, parsing numbers in manner to atoi can give much performance boost over scanf/fscanf (atoi might be not present, so be prepared to implement it by hand - it's easy).

Grzegorz Wierzowiecki
  • 10,545
  • 9
  • 50
  • 88