(To nie jest odpowiedź, ale ważna uwaga na nikogo pracy z dużymi zbiorami danych w Linuksie)
To nie jest to, jak użyć bardzo duże - rzędu terabajtów i up - zestawów danych w Linuksie .
Podczas korzystania malloc()
lub mmap()
(biblioteka GNU C użyje mmap()
wewnętrznie dla dużych przydziałów tak) przydzielić prywatnej pamięci, jądro ogranicza wielkość do wielkości (teoretycznie) dostępnej pamięci RAM i swap, pomnożonej przez overcommitu czynnik.
Po prostu wiemy, że większy niż RAM zbiory danych mogą być zamienione na zewnątrz, więc wielkość bieżącej wymiany wpłynie jak duże przydziały są dozwolone.
Aby to obejść, tworzymy plik, który ma służyć jako "wymiana" danych i mapuje go przy użyciu flagi MAP_NORESERVE
. To mówi jądru, że nie chcemy używać standardowej wymiany dla tego odwzorowania. (Oznacza to również, że jeśli z jakiegokolwiek powodu jądro nie może uzyskać nowej strony kopii zapasowej, aplikacja otrzyma sygnał SIGSEGV
i zginie).
Większość systemów plików w systemie Linux obsługuje pliki rozrzedzone. Oznacza to, że można mieć plik terabajt wielkości, że zajmuje tylko kilka kilobajtów rzeczywistej przestrzeni dyskowej, jeśli większość jego zawartość nie są zapisywane (a zatem są zerami). (Tworzenie rzadkich plików jest łatwe, po prostu pomijane są długie zera, a dziurkowanie jest trudniejsze, ponieważ zapis zer nie używa normalnej przestrzeni dyskowej, należy zamiast tego użyć innych metod).
Oto przykładowy program które można wykorzystać do poszukiwań, mapfile.c:
#define _POSIX_C_SOURCE 200809L
#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
const char *filename;
size_t page, size;
int fd, result;
unsigned char *data;
char dummy;
if (argc != 3 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
fprintf(stderr, " %s MAPFILE BYTES\n", argv[0]);
fprintf(stderr, "\n");
return EXIT_FAILURE;
}
page = sysconf(_SC_PAGESIZE);
if (page < 1) {
fprintf(stderr, "Unknown page size.\n");
return EXIT_FAILURE;
}
filename = argv[1];
if (!filename || !*filename) {
fprintf(stderr, "No map file name specified.\n");
return EXIT_FAILURE;
}
if (sscanf(argv[2], " %zu %c", &size, &dummy) != 1 || size < 3) {
fprintf(stderr, "%s: Invalid size in bytes.\n", argv[2]);
return EXIT_FAILURE;
}
if (size % page) {
/* Round up to next multiple of page */
size += page - (size % page);
fprintf(stderr, "Adjusted to %zu pages (%zu bytes)\n", size/page, size);
}
do {
fd = open(filename, O_RDWR | O_CREAT | O_EXCL, 0600);
} while (fd == -1 && errno == EINTR);
if (fd == -1) {
fprintf(stderr, "Cannot create %s: %s.\n", filename, strerror(errno));
return EXIT_FAILURE;
}
do {
result = ftruncate(fd, (off_t)size);
} while (result == -1 && errno == EINTR);
if (result == -1) {
fprintf(stderr, "Cannot resize %s: %s.\n", filename, strerror(errno));
unlink(filename);
close(fd);
return EXIT_FAILURE;
}
data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_NORESERVE, fd, 0);
if ((void *)data == MAP_FAILED) {
fprintf(stderr, "Mapping failed: %s.\n", strerror(errno));
unlink(filename);
close(fd);
return EXIT_FAILURE;
}
fprintf(stderr, "Created file '%s' to back a %zu-byte mapping at %p successfully.\n", filename, size, (void *)data);
fflush(stdout);
fflush(stderr);
data[0] = 1U;
data[1] = 255U;
data[size-2] = 254U;
data[size-1] = 127U;
fprintf(stderr, "Mapping accessed successfully.\n");
munmap(data, size);
unlink(filename);
close(fd);
fprintf(stderr, "All done.\n");
return EXIT_SUCCESS;
}
skompilować go za pomocą np
gcc -Wall -O2 mapfile.c -o mapfile
i uruchomić go bez argumentów, aby zobaczyć użycie.
Program po prostu konfiguruje mapowanie (dostosowanego do wielokrotności rozmiaru bieżącej strony), a dostęp do dwóch pierwszych i dwóch ostatnich bajtów mapowania.
Na moim komputerze, pracując z jądrem 4.2.0-42-generiC# 49 ~ 14.04.1-Ubuntu na systemie x86-64, na systemie plików ext4, nie mogę zmapować pełnego petabajta. Wydaje się, że maksimum wynosi około 17 592,186,040,320 bajtów (2 -4096) - 16 TiB - 4 KiB - co daje 4 294 967 296 stron o 4096 bajtach (2 stron o 2 bajtów każdy). Wygląda na to, że system plików ext4 narzuca limit, ponieważ awaria występuje w wywołaniu ftruncate()
(zanim mapowanie zostanie wypróbowane).
(Na tmpfs mogę uzyskać do około 140,187,732,541,440 bajtów lub 127,5 TiB, ale to tylko chwyt, ponieważ tmpfs jest wspierany przez RAM i swap, a nie rzeczywiste urządzenie magazynujące, więc nie jest to opcja dla prawdziwych dużych danych . praca wydaje mi się przypomnieć XFS będzie pracować dla naprawdę dużych plików, ale jestem zbyt leniwy, aby sformatować partycję lub nawet zajrzeć do specyfikacji; nie sądzę, ktoś rzeczywiście czytać ten post, mimo że informacje tu był bardzo przydatne dla mnie w ciągu ostatnich kilkunastu lat)
Oto jak to wygląda na przykład bieg na moim komputerze (przy użyciu powłoki bash).
$ ./mapfile datafile $[(1<<44)-4096]
Created file 'datafile' to back a 17592186040320-byte mapping at 0x6f3d3e717000 successfully.
Mapping accessed successfully.
All done.
.
Jakiś powód, który kwalifikujesz jako "lotny"? Jest to bardzo prawdopodobne bzdury i uniemożliwia optymalizacje. – Olaf
@Olaf 'volatile', aby uniknąć optymalizacji, w której nie ma dostępu do pamięci, i będzie działać w rejestrach. Aby pokazać, że naprawdę pracujemy z pamięcią. – Alex
To powiedziawszy, po prostu odkurzenie, '(int)' rzutowanie nie jest wymagane w '...% d \ n", (int) (ptr [petabajt - 1LL])); ', to jest niejawne –