Immer wieder geht es in der IT um Vergleiche, z.B. bei der Web-Entwicklung (NodeJS vs. PHP), der Datenhaltung (MongoDB vs. SQL-Datenbanken) oder auch scheinbaren Kleinigkeiten wie das Einlesen einer Datei in den Arbeitsspeicher.
Letztgenannter Vorgang würde bspw. in der Programmiersprache C unter Linux und anderen POSIX-Systemen wie folgt aussehen (Prüfungen auf Fehler habe ich der Übersichtlichkeit halber weggelassen):
void* read_file(char const *path, size_t *length) {
int fd;
struct stat stats;
void *buf, *pos;
ssize_t current_read;
size_t remain;
fd = open(path, O_RDONLY);
fstat(fd, &stats);
buf = malloc(stats.st_size);
pos = buf;
remain = stats.st_size;
for (;;) {
current_read = read(fd, pos, remain);
if (current_read == 0) {
break;
}
*(char**)&pos += current_read;
remain -= (size_t)current_read;
}
close(fd);
*length = stats.st_size;
return buf;
}
Die Alternative dazu wäre die Verwendung von mmap():
void* read_file_via_mmap(char const *path, size_t *length) {
int fd;
struct stat stats;
void *addr, *buf;
fd = open(path, O_RDONLY);
fstat(fd, &stats);
addr = mmap(NULL, stats.st_size, PROT_READ, MAP_SHARED, fd, 0);
buf = malloc(stats.st_size);
memcpy(buf, addr, stats.st_size);
munmap(addr, stats.st_size);
close(fd);
*length = stats.st_size;
return buf;
}
Deren Verfechter werben damit, dass die Anzahl der Systemaufrufe pro Datei konstant ist. Dem kann ich nicht widersprechen – die Datei wird mit open() geöffnet und mit mmap() in den Adressbereich integriert. Damit kann auf den Inhalt zugegriffen werden als befände er sich im Arbeitsspeicher. Das ermöglicht das anschließende Kopieren mit Hilfe von memcpy().
Aber das hilft einem nur weiter, wenn das auch tatsächlich messbar Zeit spart – dies ist definitiv nicht der Fall:
aklimov@ws-aklimov:~$ time ./read garbage real 0m8.108s user 0m0.000s sys 0m3.664s aklimov@ws-aklimov:~$ time ./mmap garbage real 0m8.099s user 0m0.652s sys 0m3.000s aklimov@ws-aklimov:~$
Das Lesen einer Datei (3 GB) hat im konkreten Fall nicht viel weniger Zeit in Anspruch genommen. Die Verwendung von mmap() hat lediglich ca. 660 Millisekunden von der linken in die rechte Tasche transferiert. Um genau zu sein – die Systemaufrufe der konventionellen Methode (strace):
open("garbage", O_RDONLY) = 3 read(3, "\377\0\377\0\377\0\377\0\377\0\377\0\377\0\377\0\377\0\377\0\377\0\377\0\377\0\377\0\377\0\377\0"..., 3221225472) = 2147479552 read(3, "\377\0\377\0\377\0\377\0\377\0\377\0\377\0\377\0\377\0\377\0\377\0\377\0\377\0\377\0\377\0\377\0"..., 1073745920) = 1073745920 read(3, "", 0) = 0 close(3) = 0
… vs. mmap():
open("garbage", O_RDONLY) = 3 mmap(NULL, 3221225472, PROT_READ, MAP_SHARED, 3, 0) = 0x7ff5423c6000 munmap(0x7ff5423c6000, 3221225472) = 0 close(3) = 0
Zwar benötigt die konventionelle Methode im konkreten Fall einen Systemaufruf mehr, aber angesichts der Dateigröße ist das meiner Meinung nach trotzdem vergleichsweise effizient.
Fazit
Nicht alles was glänzt ist auch Gold. Und mit Kaffeesatzlesen ist niemandem geholfen.
0 Kommentare