2015-08-05 7 views
9

Występują problemy z pisaniem do pliku - a mianowicie, że nie jestem w stanie napisać wystarczająco szybko.Poprawianie/optymalizowanie prędkości zapisu pliku w C++

Moim celem jest uchwycenie strumienia danych przesyłanych przez gigabitowy Ethernet i po prostu zapisanie go w pliku.

Dane surowe przychodzą z prędkością 10MS/s, a następnie są zapisywane w buforze, a następnie zapisywane do pliku.

Poniżej znajduje się odpowiedni fragment kodu:

std::string path = "Stream/raw.dat"; 
    ofstream outFile(path, ios::out | ios::app| ios::binary); 

    if(outFile.is_open()) 
     cout << "Yes" << endl; 

    while(1) 
    { 
     rxSamples = rxStream->recv(&rxBuffer[0], rxBuffer.size(), metaData); 
     switch(metaData.error_code) 
     { 

      //Irrelevant error checking... 

      //Write data to a file 
       std::copy(begin(rxBuffer), end(rxBuffer), std::ostream_iterator<complex<float>>(outFile)); 
     } 
    } 

Problem mam napotykając jest to, że trwa zbyt długo pisać próbek do pliku. Po mniej więcej sekundzie urządzenie wysyłające raporty z próbkami przepełnia się. Po krótkim profilowaniu kodu, prawie cały czas wykonania jest poświęcony na std::copy(...) (w 99,96% czasu). Jeśli usuniemy tę linię, mogę uruchamiać program przez wiele godzin bez napotkania jakiegokolwiek przepełnienia.

Powiedziałem, że jestem raczej zaskoczony, w jaki sposób mogę poprawić szybkość zapisu. Przejrzałem kilka postów na tej stronie i wydaje mi się, że najczęstszą sugestią (jeśli chodzi o szybkość) jest implementacja zapisu pliku, tak jak to już zrobiłem - za pomocą std::copy.

Jeśli to pomocne, uruchamiam ten program na Ubuntu x86_64. Wszelkie sugestie będą mile widziane.

+2

Chodzi o USRP, czyż nie jest to –

+0

Interesujący ... czysty kierunek wskazujący na wskaźnik C może ci się lepiej. Jeśli znasz strukturę systemu operacyjnego, możesz szybciej uzyskać dostęp do pamięci. –

+0

Tak ... Używam USRP N210. – Mlagma

Odpowiedz

13

Tak więc głównym problemem jest to, że próbujesz pisać w tym samym wątku, co ty, co oznacza, że ​​twoje recv() może być wywołane ponownie dopiero po zakończeniu kopiowania. Kilka obserwacji:

  • Przenieś zapis do innego wątku. Chodzi o USRP, więc GNU Radio może naprawdę być wybranym przez ciebie narzędziem - z natury jest wielowątkowe.
  • Twój wyjściowy iterator prawdopodobnie nie jest najbardziej wydajnym rozwiązaniem. Po prostu "write()" do deskryptora pliku może być lepsze, ale to są pomiary wydajności, które należą do ciebie
  • Jeśli twój dysk twardy/system plików/OS/CPU nie są do wysokości stawek pochodzących z USRP, nawet jeśli oddzielasz otrzymywanie od pisania na wątku, to nic nie możesz zrobić - uzyskaj szybszy system.
  • Spróbuj zapisu na dysku RAM zamiast

W rzeczywistości, nie wiem, w jaki sposób wpadł na podejściu std::copy. rx_samples_to_file example that comes with UHD robi to za pomocą prostego zapisu i zdecydowanie powinieneś faworyzować to przez kopiowanie; Plik we/wy może, na dobrych systemach, często wykonywać jedną kopię mniej, a iteracja nad wszystkimi elementami jest prawdopodobnie bardzo powolna.

+3

Uzgodniono, dodając więcej: zapisz przychodzące dane do jednego lub wielu ogromnych buforów (w zależności od czasu opóźnienia między odebraniem danych a zapisaniem do pliku). Utwórz wątek czytający z tego bufora i zapisujący do pliku (w ** wielkich blokach **). Należy również używać jak największej pomocy sprzętowej, na przykład DMA. –

+0

@ThomasMatthews Ogólnie biorąc, większe porcje danych = lepsza wydajność, ale ma to również wady, a mianowicie, jeśli porcje nie stają się zbyt duże, system operacyjny może nie być zajęty z powodu zbyt długiego przetwarzania tych danych oraz w systemach, w których procesor rdzenie są rzadkie, czas, w którym system operacyjny jest zajęty przez operacje wejścia/wyjścia pliku, może stać się krytyczny, jeśli nie będzie w stanie nadążyć za uzyskiwaniem danych przez sieć jednocześnie. Linux radzi sobie całkiem nieźle z wieloma rdzeniami, więc jest to po prostu problem z jednordzeniowymi procesorami. –

+0

@ThomasMatthews Przerzuciłem się na 'write', tak jak sugerowałeś, i jest to ogromna poprawa - jeszcze się nie przepełniło. Zwiększyłem również rozmiar mojego bufora. – Mlagma

4

Zrobimy trochę matematyki.

Twoje próbki są (podobno) typu std::complex<std::float>. Biorąc pod uwagę (typowy) 32-bitowy float, oznacza to, że każda próbka ma 64 bity. Przy 10 MS/s oznacza to, że nieprzetworzone dane wynoszą około 80 megabajtów na sekundę - to tyle, ile można się spodziewać na dysku twardym (7200 obr./min), ale zbliżając się do limitu (który zwykle wynosi około 100 -100 megabajtów na sekundę lub mniej).

Niestety, mimo że std::ios::binary, faktycznie piszesz dane w formacie tekstowym (ponieważ std::ostream_iterator w zasadzie robi stream << data;).

To nie tylko traci trochę precyzji, ale zwiększa rozmiar danych, co najmniej z reguły. Dokładna wielkość wzrostu zależy od danych - niewielka wartość całkowita może faktycznie zmniejszyć ilość danych, ale dla arbitralnego wprowadzania danych wzrost wielkości zbliżony do 2: 1 jest dość powszechny. Przy wzroście 2: 1 twoje wychodzące dane wynoszą teraz około 160 megabajtów na sekundę - co jest szybsze niż to, z czym radzi sobie większość dysków twardych.

Oczywistym punktem wyjścia dla poprawy byłoby zapisać dane w formacie binarnym zamiast:

uint32_t nItems = std::end(rxBuffer)-std::begin(rxBuffer); 
outFile.write((char *)&nItems, sizeof(nItems)); 
outFile.write((char *)&rxBuffer[0], sizeof(rxBuffer)); 

Na razie używałem sizeof(rxBuffer) na założeniu, że jest to prawdziwy szyk. Jeśli w rzeczywistości jest to wskaźnik lub wektor, musisz obliczyć prawidłowy rozmiar (wymagana jest całkowita liczba bajtów do zapisania).

Zauważyłem również, że w tej chwili Twój kod ma jeszcze poważniejszy problem: ponieważ nie określił separatora między elementami podczas zapisywania danych, dane zostaną zapisane bez niczego do oddzielenia jeden przedmiot od następnego. Oznacza to, że jeśli zapisałeś dwie wartości (na przykład) 1 i 0.2, to, co przeczytałeś, nie byłoby 1 i 0.2, ale pojedynczą wartością 10.2. Dodanie separatorów do tekstu spowoduje dodanie jeszcze więcej narzutów (około 15% więcej danych) do procesu, który już nie działa, ponieważ generuje zbyt dużo danych.

Zapisywanie w formacie binarnym oznacza, że ​​każdy zmiennoprzecinkowy pochłonie dokładnie 4 bajty, więc ograniczniki nie są konieczne do poprawnego wczytania danych.

Następnym krokiem po tym będzie zejście do procedury we/wy pliku niższego poziomu. W zależności od sytuacji może to mieć lub nie wiele zmienić. W systemie Windows można określić FILE_FLAG_NO_BUFFERING po otwarciu pliku przy użyciu CreateFile. Oznacza to, że odczyty i zapisy do tego pliku zasadniczo pomijają pamięć podręczną i przechodzą bezpośrednio na dysk.

W twoim przypadku, to pewnie wygrana - przy 10 MS/s, prawdopodobnie będziesz zużywać przestrzeń pamięci podręcznej przez jakiś czas, zanim ponownie odczytasz te same dane. W takim przypadku, przekazanie danych do pamięci podręcznej nie daje praktycznie nic, ale kosztuje niektóre dane, aby skopiować dane do pamięci podręcznej, a następnie nieco później skopiować je na dysk. Co gorsze, prawdopodobnie zaśmieci pamięć podręczną wszystkimi tymi danymi, więc nie będzie już przechowywać innych danych, które z większym prawdopodobieństwem skorzystają na buforowaniu.

Powiązane problemy