Schnellstes Lesen von Dateien in C

Lesezeit: 6 Minuten

Im Moment verwende ich fread(), um eine Datei zu lesen, aber in einer anderen Sprache ist fread() ineffizient, wie mir gesagt wurde. Ist das in C genauso? Wenn ja, wie würde das schnellere Lesen von Dateien erfolgen?

  • welche “andere” sprache meinst du?

    – Lukas

    8. Juni 2010 um 23:49 Uhr

  • Siehe auch eine ähnliche Frage, die ich gestellt habe: stackoverflow.com/questions/732463/…

    – Traumlax

    9. Juni 2010 um 0:13 Uhr

  • @dreamlax: Ich habe gerade deine Frage gelesen. Laut meinem Benchmark ist fgets() jedoch in der Geschwindigkeit meinem handgeschriebenen gepufferten read() ähnlich. Ich habe an einer 1,3-GB-Datei mit 8,3 Millionen Zeilen getestet. Macht es Ihnen etwas aus, Ihr Beispielprogramm zu posten, das zeigt, dass gepuffertes read() 4X schneller ist als fgets()? Danke im Voraus. Was Jays Frage betrifft, denke ich, dass fread() in den meisten Fällen ähnlich oder eher schneller ist als read().

    – Benutzer172818

    9. Juni 2010 um 1:56 Uhr


  • Textdatei oder binär? Die Schnittstellen der C-Laufzeitbibliothek führen möglicherweise eine kleine Konvertierung für Zeilenumbrüche durch, wenn es sich um eine Textdatei handelt. Wenn Sie diese Konvertierung nicht benötigen, können Sie sie umgehen, indem Sie eine andere API verwenden.

    – Adrian McCarthy

    9. Juni 2010 um 2:28 Uhr

  • C++ wäre genau die Sprache, die damals in Frage kam.

    – Jay

    9. Juni 2010 um 4:49 Uhr


Benutzer-Avatar
Thanatos

Es sollte eigentlich egal sein.

Wenn Sie von einer tatsächlichen Festplatte lesen, wird es langsam sein. Die Festplatte ist Ihr Flaschenhals, und das war’s.

Nun, wenn Sie mit Ihrem Aufruf von read/fread/whatever albern sind und sagen, fread()-ing ein Byte auf einmal, dann ja, es wird langsam sein, da der Overhead von fread() übertrifft der Mehraufwand beim Lesen von der Platte.

Wenn Sie read/fread/whatever aufrufen und eine anständige Portion Daten anfordern. Dies hängt davon ab, was Sie tun: Manchmal ist alles, was Sie wollen/brauchen, 4 Bytes (um ein uint32 zu erhalten), aber manchmal können Sie große Stücke einlesen (4 KiB, 64 KiB usw.). RAM ist billig, entscheiden Sie sich für etwas Bedeutendes .)

Wenn Sie kleine Lesevorgänge durchführen, werden Ihnen einige der Aufrufe auf höherer Ebene wie fread() tatsächlich helfen, indem sie Daten hinter Ihrem Rücken puffern. Wenn Sie große Lesevorgänge durchführen, ist dies möglicherweise nicht hilfreich, aber der Wechsel von fread zu read wird wahrscheinlich keine so große Verbesserung bringen, da Sie bei der Festplattengeschwindigkeit einen Engpass haben.

Kurz gesagt: Fordern Sie, wenn Sie können, beim Lesen einen großzügigen Betrag an und versuchen Sie, das, was Sie schreiben, zu minimieren. Bei großen Mengen sind Potenzen von 2 tendenziell freundlicher als alles andere, aber natürlich ist es vom Betriebssystem, der Hardware und dem Wetter abhängig.

Mal sehen, ob dies Unterschiede hervorbringen könnte:

#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>

#define BUFFER_SIZE (1 * 1024 * 1024)
#define ITERATIONS (10 * 1024)

double now()
{
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return tv.tv_sec + tv.tv_usec / 1000000.;
}

int main()
{
    unsigned char buffer[BUFFER_SIZE]; // 1 MiB buffer

    double end_time;
    double total_time;
    int i, x, y;
    double start_time = now();

#ifdef USE_FREAD
    FILE *fp;
    fp = fopen("/dev/zero", "rb");
    for(i = 0; i < ITERATIONS; ++i)
    {
        fread(buffer, BUFFER_SIZE, 1, fp);
        for(x = 0; x < BUFFER_SIZE; x += 1024)
        {
            y += buffer[x];
        }
    }
    fclose(fp);
#elif USE_MMAP
    unsigned char *mmdata;
    int fd = open("/dev/zero", O_RDONLY);
    for(i = 0; i < ITERATIONS; ++i)
    {
        mmdata = mmap(NULL, BUFFER_SIZE, PROT_READ, MAP_PRIVATE, fd, i * BUFFER_SIZE);
        // But if we don't touch it, it won't be read...
        // I happen to know I have 4 KiB pages, YMMV
        for(x = 0; x < BUFFER_SIZE; x += 1024)
        {
            y += mmdata[x];
        }
        munmap(mmdata, BUFFER_SIZE);
    }
    close(fd);
#else
    int fd;
    fd = open("/dev/zero", O_RDONLY);
    for(i = 0; i < ITERATIONS; ++i)
    {
        read(fd, buffer, BUFFER_SIZE);
        for(x = 0; x < BUFFER_SIZE; x += 1024)
        {
            y += buffer[x];
        }
    }
    close(fd);

#endif

    end_time = now();
    total_time = end_time - start_time;

    printf("It took %f seconds to read 10 GiB. That's %f MiB/s.\n", total_time, ITERATIONS / total_time);

    return 0;
}

…ergibt:

$ gcc -o reading reading.c
$ ./reading ; ./reading ; ./reading 
It took 1.141995 seconds to read 10 GiB. That's 8966.764671 MiB/s.
It took 1.131412 seconds to read 10 GiB. That's 9050.637376 MiB/s.
It took 1.132440 seconds to read 10 GiB. That's 9042.420953 MiB/s.
$ gcc -o reading reading.c -DUSE_FREAD
$ ./reading ; ./reading ; ./reading 
It took 1.134837 seconds to read 10 GiB. That's 9023.322991 MiB/s.
It took 1.128971 seconds to read 10 GiB. That's 9070.207522 MiB/s.
It took 1.136845 seconds to read 10 GiB. That's 9007.383586 MiB/s.
$ gcc -o reading reading.c -DUSE_MMAP
$ ./reading ; ./reading ; ./reading 
It took 2.037207 seconds to read 10 GiB. That's 5026.489386 MiB/s.
It took 2.037060 seconds to read 10 GiB. That's 5026.852369 MiB/s.
It took 2.031698 seconds to read 10 GiB. That's 5040.119180 MiB/s.

… oder kein merklicher Unterschied. (fread gewinnt manchmal, manchmal gelesen)

Notiz: Die langsame mmap ist überraschend. Dies könnte daran liegen, dass ich darum gebeten habe, den Puffer für mich zuzuweisen. (Ich war mir nicht sicher über die Anforderungen für die Bereitstellung eines Zeigers …)

Kurz gesagt: Optimieren Sie nicht vorschnell. Lass es laufen, mach es richtig, mach es schnell, diese Reihenfolge.


Zurück auf vielfachen Wunsch habe ich den Test mit einer echten Datei durchgeführt. (Die ersten 675 MiB der Ubuntu 10.04 32-Bit-Desktop-Installations-CD ISO) Dies waren die Ergebnisse:

# Using fread()
It took 31.363983 seconds to read 675 MiB. That's 21.521501 MiB/s.
It took 31.486195 seconds to read 675 MiB. That's 21.437967 MiB/s.
It took 31.509051 seconds to read 675 MiB. That's 21.422416 MiB/s.
It took 31.853389 seconds to read 675 MiB. That's 21.190838 MiB/s.
# Using read()
It took 33.052984 seconds to read 675 MiB. That's 20.421757 MiB/s.
It took 31.319416 seconds to read 675 MiB. That's 21.552126 MiB/s.
It took 39.453453 seconds to read 675 MiB. That's 17.108769 MiB/s.
It took 32.619912 seconds to read 675 MiB. That's 20.692882 MiB/s.
# Using mmap()
It took 31.897643 seconds to read 675 MiB. That's 21.161438 MiB/s.
It took 36.753138 seconds to read 675 MiB. That's 18.365779 MiB/s.
It took 36.175385 seconds to read 675 MiB. That's 18.659097 MiB/s.
It took 31.841998 seconds to read 675 MiB. That's 21.198419 MiB/s.

…und ein sehr Gelangweilter Programmierer später haben wir die CD-ISO von der Platte gelesen. 12 mal. Vor jedem Test wurde der Disk-Cache geleert, und während jedes Tests war genug und ungefähr gleich viel RAM frei, um die CD-ISO zweimal im RAM zu halten.

Eine interessante Anmerkung: Ich habe ursprünglich ein großes malloc() verwendet, um den Speicher zu füllen und so die Auswirkungen des Festplatten-Cachings zu minimieren. Es kann sich lohnen, darauf hinzuweisen mmap hat hier furchtbar gespielt. Die anderen beiden Lösungen liefen lediglich, mmap lief und aus Gründen, die ich mir nicht erklären kann, begann, den Speicher zum Austauschen zu drängen, was seine Leistung zerstörte. (Soweit ich weiß, war das Programm nicht undicht (der Quellcode ist oben) – der tatsächliche “benutzte Speicher” blieb während der Versuche konstant.)

read() hat insgesamt die schnellste Zeit gepostet, fread() hat wirklich konstante Zeiten gepostet. Dies könnte jedoch während des Tests zu einem kleinen Schluckauf geführt haben. Alles in allem waren die drei Methoden ungefähr gleich. (Besonders fread und read…)

  • 9533,226368 MiB/s … irgendwie glaube ich, dass dein Code falsch ist. Das ist oft schneller als RAM arbeitet, geschweige denn Festplatten.

    – Billy ONeal

    9. Juni 2010 um 1:25 Uhr

  • Dies ist so, dass mmap es tatsächlich liest, andernfalls Es tauscht die Daten nicht aus <-- Aus diesem Grund ist mmap normalerweise schneller. Oft benötigt man nicht alle Bytes der Datei. Die Speicherzuordnung hilft auch mehr beim Schreiben, da es dem Betriebssystem ermöglicht, das Schreiben zu verschieben, bis es effizient ist, das Schreiben selbst durchzuführen.

    – Billy ONeal

    9. Juni 2010 um 1:49 Uhr

  • @Thantos: Sie können kein Benchmarking mit /dev/zero durchführen. Benchmark mit einer echten Datei – Metadateien wie /dev/zero werden der realen Nutzung des IO-Systems nicht entsprechen, da sie keine Daten lesen.

    – Billy ONeal

    9. Juni 2010 um 2:10 Uhr


  • @mcabral: Das wird nur die CPU belasten, nicht das E / A-System. @lh3: CPU-Overhead spielt für das IO-System keine Rolle. Viel wichtiger ist, wie gut das System reale E/A-Geräte wie Festplatten nutzt. Lösungen, die in der Lage sind, mehrere Lesevorgänge zusammenzufassen, benötigen mehr CPU-Zeit, erzielen jedoch eine bessere Leistung, wenn Sie sie auf reale E/A-Lasten (z. B. Festplatten) richten. Wenn @Thanatos eine echte Datei für seine Benchmarks verwendet, würde ich mein Downvote in ein Upvote ändern.

    – Billy ONeal

    9. Juni 2010 um 2:38 Uhr

  • Es ist amüsant zu sehen, wie viele Leute weiterhin über vorzeitige Optimierung predigen, als hätten sie bessere Kenntnisse über die Aufgabe des Fragestellers. Wir stellen diese Fragen auf der Suche nach Möglichkeiten, die Hölle unseres Codes tatsächlich zu optimieren, nicht um eine Wiederholung von Knuths Philosophie zu hören.

    – rr-

    20. Januar 2016 um 9:31 Uhr

Wenn Sie bereit sind, über die C-Spezifikation hinaus in betriebssystemspezifischen Code zu gehen, wird die Speicherzuordnung im Allgemeinen als der effizienteste Weg angesehen.

Für Posix, check out mmap und für Windows auschecken OpenFileMapping

  • Eigentlich wollen Sie CreateFileMapping und MapViewOfFile. OpenFileMapping dient nur zum Öffnen eines vorhandenen benannten speicherabgebildeten Dateiobjekts, beispielsweise für gemeinsam genutzten Speicher. Aber +1 für den Vorschlag des Speicherzuordnungszeitraums.

    – Billy ONeal

    9. Juni 2010 um 1:24 Uhr

  • Während die Speicherzuordnung am einfachsten zu verwenden und gleichzeitig hocheffizient ist, ist sie nicht wirklich die effizienteste. Ungepufferte und möglicherweise asynchrone E/A werden schneller und skalierbarer sein, zumindest unter Windows.

    – Cory Nelson

    18. Oktober 2014 um 15:29 Uhr


  • @CoryNelson ist auch nicht der schnellste, wenn Sie es nur nacheinander auf einmal lesen möchten.

    – Kaihaku

    22. September 2021 um 16:48 Uhr

Benutzer-Avatar
Matt Curtis

Was bremst dich aus?

Wenn Sie das schnellstmögliche Lesen von Dateien benötigen (während Sie immer noch gut mit dem Betriebssystem spielen), gehen Sie direkt zu den Aufrufen Ihres Betriebssystems und stellen Sie sicher, dass Sie lernen, wie Sie sie am effektivsten verwenden.

  1. Wie sind Ihre Daten physisch angeordnet? Beispielsweise könnten rotierende Laufwerke Daten, die an den Rändern gespeichert sind, schneller lesen, und Sie möchten die Suchzeiten minimieren oder eliminieren.
  2. Werden Ihre Daten vorverarbeitet? Müssen Sie zwischen dem Laden von der Festplatte und der Verwendung etwas tun?
  3. Was ist die optimale Chunk-Größe zum Lesen? (Es könnte sogar ein Vielfaches der Sektorgröße sein. Überprüfen Sie Ihre Betriebssystemdokumentation.)

Wenn die Suchzeiten ein Problem darstellen, ordnen Sie Ihre Daten auf der Festplatte neu an (wenn Sie können) und speichern Sie sie in größeren, vorverarbeiteten Dateien, anstatt kleine Teile von hier und dort zu laden.

Wenn die Datenübertragungszeiten ein Problem darstellen, sollten Sie vielleicht die Daten komprimieren.

  • An alle, die das ablehnen: Mich würde interessieren, warum. Ist meine Antwort falsch oder irreführend?

    – Matt Curtis

    11. Juni 2010 um 12:38 Uhr

Benutzer-Avatar
Makabral

Ich denke an die lesen Systemaufruf.

Denken Sie daran, dass fread ein Wrapper für „read“ ist.

Auf der anderen Seite hat fread einen internen Puffer, also ist ‘read’ vielleicht schneller, aber ich denke, ‘fread’ wird effizienter sein.

Wenn fread langsam ist, liegt es an den zusätzlichen Ebenen, die es dem zugrunde liegenden Betriebssystemmechanismus hinzufügt, um aus einer Datei zu lesen, die die Verwendung Ihres speziellen Programms beeinträchtigen fread. Mit anderen Worten, es ist langsam, weil Sie es nicht so verwenden, wie es optimiert wurde.

Allerdings würde ein schnelleres Lesen von Dateien erreicht, wenn Sie verstehen, wie die E/A-Funktionen des Betriebssystems funktionieren, und Ihre eigene Abstraktion bereitstellen, die die speziellen E/A-Zugriffsmuster Ihres Programms besser handhabt. Meistens können Sie dies mit der Speicherzuordnung der Datei tun.

Wenn Sie jedoch an die Grenzen des Computers stoßen, auf dem Sie arbeiten, wird die Speicherzuordnung wahrscheinlich nicht ausreichen. An diesem Punkt liegt es wirklich an Ihnen, herauszufinden, wie Sie Ihren I/O-Code optimieren können.

Es ist nicht das schnellste, aber es ist ziemlich gut und kurz.

#include <fcntl.h>
#include <unistd.h>

int main() {
    int f = open("file1", O_RDWR);

    char buffer[4096];

    while ( read(f, buffer, 4096) > 0 ) {
        printf("%s", buffer);
    }

}

Benutzer-Avatar
yanurabeya

Das Problem, das einige Leute hier bemerkt haben, ist, dass Sie abhängig von Ihrer Quelle, Ihrer Zielpuffergröße usw. einen benutzerdefinierten Handler für diesen speziellen Fall erstellen können, aber es gibt andere Fälle, wie Block-/Zeichengeräte, dh /dev/ * Wo Standardregeln wie diese gelten oder nicht gelten und Ihre Backing-Quelle möglicherweise etwas ist, das Zeichen ohne Pufferung seriell abhebt, wie ein I2C-Bus, Standard-RS-232 usw. Und es gibt einige andere Quellen, in denen sich Zeichengeräte befinden speicherabbildbare große Speicherbereiche, wie es Nvidia mit seinem Videotreiber Character Device (/dev/nvidiactl) tut.

Eine andere Designimplementierung, die viele Leute in Hochleistungsanwendungen gewählt haben, ist asynchrone statt synchrone E/A für die Behandlung, wie Daten gelesen werden. Schauen Sie sich libaio und die portierten Versionen von libaio an, die vorgefertigte Lösungen für asynchrone I/O bieten, und schauen Sie sich die Verwendung von Lesevorgängen mit gemeinsam genutztem Speicher zwischen einem Worker- und Consumer-Thread an (aber denken Sie daran, dass dies die Programmierkomplexität erhöht, wenn Sie gehen diese Strecke). Asynchrone E/A ist auch etwas, das Sie mit stdio nicht aus der Box bekommen können, das Sie mit Standard-OS-Systemaufrufen erhalten können. Seien Sie nur vorsichtig, da es Lesebits gibt, die gemäß der Spezifikation “portabel” sind, aber nicht alle Betriebssysteme (wie zum Beispiel FreeBSD) unterstützen POSIX-STREAMs (nach Wahl).

Eine andere Sache, die Sie tun können (je nachdem, wie portabel Ihre Daten sind), ist die Komprimierung und / oder Konvertierung in ein binäres Format wie Datenbankformate, dh BDB, SQL usw., zu untersuchen. Einige Datenbankformate sind mithilfe von Endianness-Konvertierungsfunktionen zwischen Computern portierbar.

Im Allgemeinen wäre es am besten, eine Reihe von Algorithmen und Methoden zu nehmen, Leistungstests mit den verschiedenen Methoden durchzuführen und den besten Algorithmus zu evaluieren, der die mittlere Aufgabe erfüllt, die Ihre Anwendung erfüllen würde. Das würde Ihnen helfen, den leistungsstärksten Algorithmus zu bestimmen.

1344210cookie-checkSchnellstes Lesen von Dateien in C

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy