Optymalizowanie czasu tworzenia się plików tekstowych

0

Zrobiłem taki prosty testowy kod, który tworzy 10 tysiący pustych plików tekstowych w podfolderze:

#include <iostream>
#include <time.h>
#include <string>
#include <fstream>

void CreateFiles()
{
    int i = 1;
    while (i <= 10000) {
        int filename = i;
        std::string string_i = std::to_string(i);
        std::string file_dir = ".\\results\\"+string_i+".txt";
        std::ofstream outfile(file_dir);
        i++;
    }
}

int main()
{
    clock_t tStart1 = clock();
    CreateFiles();
    printf("\nCzas generowania plikow: %.2fs\n", (double)(clock() - tStart1)/CLOCKS_PER_SEC);
    std::cin.get();
    return 0;
}

Wszystkie 10 tyś. pustych plików stworzą się (na moim komputerze) w jakieś 3.5 sekundy.
Pytanie 1: pomijając te konwersje inta do stringa itp., czy jest coś co mógłbym już na tym etapie zoptymalizować aby było szybciej? Chodzi mi o użycie std::ofstream outfile - być może użycie czegoś innego jest znacznie szybsze w wykonywaniu się?

W każdym razie, 3.5 sekundy jest całkiem zadowalające względem tego:

Dodałem do funkcji jakiś prosty prototyp, który wypełnia owe pliki .txt: kilka razy zmienną i i jakimś stałym tekstem:

void CreateFiles()
{
    int i = 1;
    while (i <= 10000) {
        int filename = i;
        std::string string_i = std::to_string(i);
        std::string file_dir = ".\\results\\"+string_i+".txt";
        std::ofstream outfile(file_dir);

        // tutaj mam to wypełnianie plików
        outfile << i << " stała " << i << " którą " << i << " wypełniam " << i << " plik " << i << " //coś// " << i
        << " tam " << i;
        i++;
    }
}

I teraz całość (tworzenie plików + ich wypełnianie) wykonuje się w... ~37 sekundy, więc jest już kolosalna różnica. A to tylko (lub aż?) 10 tysięcy plików.
Pytanie 2: czy da się tutaj coś zoptymalizować? Może jakaś szybsza alternatywa wypełniania plików, albo może zapomniałem o czymś oczywistym co spowalnia cały proces wykonywania się?

A może trochę wyolbrzymiam i taki czas jest całkiem normalny? Jest tu pole do optymalizowania?

3

Raczej patrzyłbym tutaj w stronę systemu operacyjnego/systemu plików/hardware na którym te pliki są niż na mikrooptymalizacje przy stringach.

1

Po co ci te 10 tysięcy plików?

0

Jak wyżej wspomniane sztuczki z systemem. Ale skoro są to jakieś niezależne pliki to możesz zawsze spróbować zrobić to wielowątkowo. Tyle że nie koniecznie może przynieść to jakieś efekty
ze stacka: klik

2

A może trochę wyolbrzymiam i taki czas jest całkiem normalny? Jest tu pole do optymalizowania?

Zdaje się, że problemem jest I/O po stronie systemu operacyjnego (dyski nie mają nieograniczonej szybkości koniec końców), więc niestety z punktu widzenia aplikacji niewiele możesz zdziałać.

Jeśli potem na tych plikach wykonujesz jeszcze jakieś dodatkowe operacje, mógłbyś je zapisywać do /dev/shm, wykonywać te magiczne operacje i dopiero potem zrzucać na dysk - dzięki temu przenosisz najwolniejszą operację na sam koniec; przy czym to jest tylko poniekąd obejście "problemu".

Równolegle do tematu: masz HDD czy SSD?

0

Na SSD całość wykonuje się w jakieś 35~37 sekund, na HDD w 30~32. Nie wiem jak to możliwe. Wątpię, abym miał uszkodzony dysk. Jedynie co, to ten SSD jest bardziej zasyfiony.

0

Sorry za double post, ale nadal nie rozumiem jak to działa.

Gdy tworzę 10k pustych plików, całość wykonuje się w 3 sekundy.

Gdy wypełniam te pliki tym:

outfile << i << " stała " << i << " którą " << i << " wypełniam " << i << " plik " << i << " //coś// " << i
        << " tam " << i;

całość wykonuje się w ~35 sekund.

Natomiast gdy tworzę tylko jeden plik .txt, i wypełniam ten jeden plik tym czym wypełniłbym wszystkie 10 000 razem wzięte:

while (i < 10000) {
        outfile << i << " some " << i << " constant " << i << " text " << i << " . . . "
        << i << " --more text-- " << i << " --even more-- " << i << std::endl;
        i++;
        }

W rezultacie powstaje jeden plik .txt który waży ~1Mb - całość wykonuje się w 0.09 sekundy.

Zatem:

  • tworzenie się plików jest szybkie
  • wypełnianie jednego plików sporą ilości danych jest bardzo szybkie
  • ale wypełnianie wielu plików małą ilością danych jest bardzo wolne

Dlaczego tak się dzieję, i czy mogę jakoś temu zapobiec?

Na marginesie: jest ktoś w stanie u siebie skompilować ten program, stworzyć podfolder "Results" w lokalizacji skompilowanego .exeka i sprawdzić w ile czasu się to wykona u Ciebie?

2

Za dużo wnioskujesz.

  • tworzenie pustych plików jest tanie, zapewne jest na to special case w kodzie FS
  • generowanie danych jest relatywnie szybkie (porównaj też jak to wygląda bez flushowania)
  • buforowany zapis jest relatywnie szybki

Pobaw się bezpośrednio z API systemowym (WinAPI/POSIX) i zobacz jakie tam uzyskujesz czasy. Zobacz jak wygląda to samo z komendą touch i zapisaniem danych za pomocą skryptu bashowego. Podepnij ramdisk i zobacz na nim.

1

Jeszcze mozna sprobowac to zrownoleglic, powinno pojsc calkiem sprawnie o ile nie opierasz sie o limity sprzetowego I/O.

Pare lat temu zrobilem proste porownanie odczytu liniowego oraz zapisu duzych plikow (rozmiar w GB). Zysk WriteFile/ReadFile vs iostream to bylo okolo 50%, dla mnie wtedy to bylo wystarczajace jednak dzisiejsze kompilatory oraz kilka generacji systemu Windows do przodu to zupelnie inny temat i nalezaloby przeprowadzic taki test ponownie.

3

Jak chcesz to naprawde szybko zrobic to jest mozliwe ze duzo szybciej bedzie to dzialac na customowym file systemie. Zwlaszcza case "zrob 1 mln pustych plikow" mozna pewnie zrobic ze 100x szybciej od jakiegokolwiek OSa. Ktos od gamedev albo embedded powinien Ci umiec pomoc.

Wazne zebys nie myslal o tym jak o uniwersalnym rozwiazaniu - bo jesli takie to ma byc to raczej sie nie uda. Trzeba wziac pod uwage tylko wymagania zlecone.

Szukaj pod haslem "custom file system" plus nazwa OS. Na Linux powinno byc latwiej niz na Windows.

Pytanie jaka wydajnosc uzyskujesz na linuksowym ramdysku?

5

Pytanie czy ty w ogóle musisz tworzyć te pliki. Jeśli zawartość każdego pliku jest niezależna od pozostałych plików, to może wystarczy sam FUSE, gdzie wystawisz informację o wszystkich istniejących plikach, i będziesz ich wartość obliczał tylko wtedy gdy zajdzie taka potrzeba (tak działa np. procfs).

0

@hauleth: raczej muszę
@vpiotr: dzięki za radę, ale nie mam za bardzo pojęcia o czym piszesz i czuję, że spędziłbym przy tym zbyt wiele czasu. A potencjalny zysk może nie być tego wart. Liczyłem na znalezienie prostego rozwiązania, myślałem że robię coś źle przy wkładaniu danych do pliku, itp.
@kq: program ma działać też na linuxie, ale z ciekawości sprawdzę w przyszłości z tym WinAPI.

To co na Windowsie wykonuje mi się w ~35 sekund na innym komputerze z linuxem wykonuje się w mniej niż sekundę. Chyba zostawię to tak jak jest i gdyby koordynator projektu się czepiał chyba zwalę winę na Windowsa. : D

2
Hodor napisał(a):

Zatem:

  • tworzenie się plików jest szybkie

Tworzenie pustego pliku to zapisanie jedynie metadanych (nazwa pliku, prawa dostępu, czas utworzenia, ...)

  • wypełnianie jednego plików sporą ilości danych jest bardzo szybkie

Bo odpowiada za to buforowanie czyli zbieranie danych w pamięci i zapis dopiero gdy będzie odpowiednio dużo.
Daj flush() po każdym fragmencie to zobaczysz różnicę.

  • ale wypełnianie wielu plików małą ilością danych jest bardzo wolne

Bo dla każdego pliku trzeba zapisać metadane+dane i dopiero po tym zabrać się za kolejny plik.

Zapisywać na dysku można ustalone wielkości danych. Dane na dysku zajmują wielokrotność tej wielkości. Zobacz ile 'waży' cały katalog z 10000 małych plików i porównaj z wielkością tych plików.

0

@hauleth: czy ja wiem czy bezsensowne. Tutaj przykład trzech sytuacji jak długo zajmuje mi to tworzenie się plików w porównaniu do reszty wykonywania się programu.

Sytuacja 1: Plik wejściowy milion linijek, zostało utworzone 16 plików wyjściowych, więc każdy plik wyjściowy ma około 60+ tyś. linijek (powtórzeń). Bardzo często powtarzają się dane w pliku wejściowym.

title

Sytuacja 2: Plik wejściowy milion linijek, zostało utworzone 256 plików wyjściowych, więc każdy plik wyjściowy ma około 4 tyś.linijek (powtórzeń). Często powtarzają się dane w pliku wejściowym.

title

Sytuacja 3: Tym razem plik wejściowy tylko 10 tyś. linijek, zostało utworzone 9 tyś. plików wyjściowych, więc co ~dziesiąty plik ma dwie linijki, reszta po jednej. Prawie w ogóle nie powtarzają się dane w pliku wejściowym.

title

Dlatego chciałem to jakoś zoptymalizować (samo w sobie tworzenie i wkładanie danych do plików), ale jeśli mając prawie zerową wiedzę nie da się tego zrobić łatwo i szybko to chyba muszę to odpuścić (bo aktualnie nie mogę sobie pozwolić na ryzyko, że będę nad tym siedział np. 10 godzin i nie będzie efektu, tak to bym się tym "pobawił").

0

Opisz bardziej problem. Jeżeli nie w programie to może poza nim da radę coś wymyślić.

  1. Ten program ma działać w konkretnym systemie czy być przystosowany do różnych środowisk?
  2. Jak długo są potrzebne dane wyjściowe? Do wyłączenia systemu czy dłużej?
  3. Na konkretnym systemie plików? Czy można zrobić wcześniej odpowiednią partycję i na niej zapisywać?
  4. Wyjściowe pliki muszą być każdy oddzielnie czy możne je wrzucić w większy? Np. tekstowy z opisami tak by można było znaleźć szukane dane.

Im więcej danych tym łatwiej o właściwą pomoc.

0

@Delor:

  1. Windows, linux
  2. Dłużej - dopóki ich ręcznie nie usunę.
  3. Nie jest powiedziane, więc wątpię żeby było można.
  4. Muszą być oddzielnie.

Próbowałem także:

  • ładować do pliku za pomocą fprintf
  • ładować do ofstream wcześniej przygotowaną całą linijkę naraz, jednorazowo korzystając z operatora <<

Wszystko dało rezultat +- 5% taki sam, więc bez szału. (w porównaniu do Windows vs Linux, gdzie różnicę mam ~4500%).
Anyway raczej nie ma co tego overthinkować, dziękuję każdemu za pomoc.

1

zajrzałem do takiej książki jednej co mam:
https://helion.pl/ksiazki/c-optymalizacja-kodu-sprawdzone-techniki-zwiekszania-wydajnosci-guntheroth-kurt,a_03io.htm#format/e
Książka jest słaba moim zdaniem ale co my tu mamy o we/wy

void zapis_do_strumienia_wiersz_noflush(std::ostream& f, std::String const& wiersz)
{
f  << wiersz << "\n"
}

Tyle że pamiętaj o tym że w pewnym momencie trzeba zrobić f.flush() gdzieś żeby opróżnić bufor.

Gość jeszcze wspomina o std::cin i std::cout w połączeniu z std::sync_with_stdio(false)

1

Jeżeli możesz to postaraj się wybrać małą partycję na jakiej zapiszesz wyniki.
Interesuje Cię możliwie najmniejszy rozmiar klastra (w Windows)/bloku (Linux).
Przykładowe wartości domyślne dla tego pierwszego: https://support.microsoft.com/pl-pl/help/140365/default-cluster-size-for-ntfs-fat-and-exfat

Dyski na macierzach RAID też powinny być szybsze (w wersjach zwiększających wydajność).

SSD vs HDD sam sprawdziłeś.

I właściwie tyle.
To problem poza programem więc, w tym przypadku, nie ma co za dużo optymalizować.

3

@Hodor a jak często będziesz odpalał ten program? Bo widzę ok 15 s różnicy w czasie na Twoim przykładzie. Więc jeśli spędziłbyś na optymalizacji 1h, uzyskał taki sam czas działania jak w przypadku 16 plików, a program odpalał raz dziennie, to "spędzony czas" odzyskasz po ~217 dniach.

0

Zaznacz te dwa ptaszki we właściwościach dysku a Windows nie będzie bezsensownie zrzucał buforu po utworzeniu każdego pliku.

hdd.png

1 użytkowników online, w tym zalogowanych: 0, gości: 1