Përshpejtimi i hyrjes/daljes së skedarëve C/C++, pa e ndjerë shumë

Përshpejtimi i hyrjes/daljes së skedarëve C/C++, pa e ndjerë shumë

Parathënie

Ka njihet njĂ« utilitar kaq i thjeshtĂ« dhe shumĂ« tĂ« dobishĂ«m — BDelta, dhe ndodhi qĂ« ai Ă«shtĂ« thellĂ«sisht i instaluar nĂ« procesin tonĂ« prodhues (e vĂ«rteta Ă«shtĂ« se nuk arritĂ«m ta instalojmĂ« versionin e tij, por me siguri nuk ishte ai mĂ« i fundit). E pĂ«rdorim pĂ«r qĂ«llimin e tij tĂ« drejtpĂ«rdrejtĂ« — ndĂ«rtimin e patch-ave binarĂ«. NĂ«se shikojmĂ« çfarĂ« ka atje nĂ« depo, bĂ«het pak e trishtueshme: nĂ« thelb, ajo Ă«shtĂ« braktisur prej kohĂ«sh dhe shumĂ« prej saj janĂ« tĂ«Dataset e vjetra (disa modifikime janĂ« bĂ«rĂ« aty nga ish-kolegu im, por ka qenĂ« kohĂ« e gjatĂ«). Pra, vendosa ta ringjall kĂ«tĂ«: e forkuam, hoqa atĂ« qĂ« nuk planifikoj tĂ« pĂ«rdor, e tĂ«rhoqa projektin nĂ« cmake, kam inlinuar mikrofunksionet “e nxehta”, heqa nga skedari array-t e mĂ«dha (dhe array-t me gjatĂ«si variabĂ«l, qĂ« mĂ« shqetĂ«sojnĂ« pa fund), e kam kaluar pĂ«rsĂ«ri profilerin — dhe kuptova se rreth 40% e kohĂ«s shpenzohet pĂ«r fwrite


ÇfarĂ« ndodh me fwrite?

NĂ« kĂ«tĂ« kod fwrite (nĂ« rastin tim konkret: ndĂ«rtimi i njĂ« patch-i midis dy skedarĂ«ve tĂ« ngjashĂ«m prej 300 Mb, tĂ« dhĂ«nat totale nĂ« memorie) thirret miliona herĂ« me njĂ« buffer tĂ« vogĂ«l. ËshtĂ« e qartĂ« se kjo do tĂ« ngadalĂ«sohet, dhe prandaj do tĂ« doja tĂ« ndikoj nĂ« kĂ«tĂ« pa e komplikuar situatĂ«n. Nuk kam dĂ«shirĂ« tĂ« integroj lloje tĂ« ndryshme burimesh tĂ« dhĂ«nash, I/O asinkron, desha tĂ« gjej njĂ« zgjidhje mĂ« tĂ« thjeshtĂ«. E para qĂ« mĂ« erdhi nĂ« mendje ishte tĂ« rrisja madhĂ«sinĂ« e buffer-it

setvbuf(file, nullptr, _IOFBF, 64 * 1024)

por nuk arrita ndonjĂ« pĂ«rmirĂ«sim tĂ« dukshĂ«m tĂ« rezultatit (tani fwrite kishte rreth 37% tĂ« kohĂ«s) — kjo tregon se problemi nuk Ă«shtĂ« shpesh nĂ« shkrimin e tĂ« dhĂ«nave nĂ« disk. Duke shqyrtuar “nĂ«n kapak” fwrite, mund tĂ« shihni se brenda ndodhin lock/unlock tĂ« strukturĂ«s FILE pĂ«rafĂ«rsisht kĂ«shtu (pseudokod, gjithĂ« analiza Ă«shtĂ« bĂ«rĂ« nĂ«n Visual Studio 2017):


size_t fwrite (const void *buffer, size_t size, size_t count, FILE *stream)
{
   size_t retval = 0;
   _lock_str(stream);   /* lock stream */
   __try
   {
      retval = _fwrite_nolock(buffer, size, count, stream);
   }
   __finally 
   {
       _unlock_str(stream);   /* unlock stream */
   }
   return retval;
}

NĂ«se i besoj profilerit, _fwrite_nolock merr vetĂ«m 6% tĂ« kohĂ«s, e gjithĂ« pjesa tjetĂ«r Ă«shtĂ« overhead. NĂ« rastin tim konkret, siguria e thread-it Ă«shtĂ« njĂ« luks i tepruar, tĂ« cilin do ta sakrifikoj duke zĂ«vendĂ«suar thirrjen fwrite me _fwrite_nolock — as nuk ka nevojĂ« tĂ« luaj me argumentet. Pra, kjo manipulim e thjeshtĂ« ndihmoi shumĂ« nĂ« zvogĂ«limin e shpenzimeve pĂ«r tĂ« shkruar rezultatin, qĂ« nĂ« variantin fillestar pĂ«rbĂ«nte pothuajse gjysmĂ«n e kohĂ«s totale. PĂ«r tĂ« qenĂ« i saktĂ«, nĂ« botĂ«n POSIX ka njĂ« funksion tĂ« ngjashĂ«m — fwrite_unlocked. NĂ« pĂ«rgjithĂ«si, kjo vlen edhe pĂ«r fread. KĂ«shtu, duke pĂ«rdorur disa #define mund tĂ« arrijmĂ« njĂ« zgjidhje krejtĂ«sisht tĂ« platformave pa bllokime tĂ« panevojshme nĂ« rastet kur ato nuk janĂ« tĂ« nevojshme (ndodhin shpesh).

fwrite, _fwrite_nolock, setvbuf

Le tĂ« abstrahohemi nga projekti origjinal dhe tĂ« merremi me testimin e njĂ« rasti tĂ« veçantĂ«: shkrimi i njĂ« skedari tĂ« madh (512 Mb) nĂ« porsona shumĂ« tĂ« vegjĂ«l — nĂ« 1 byte. Sistemi testues: AMD Ryzen 7 1700, 16 GB RAM, HDD 7200 rpm 64 Mb cache, Windows 10 1809, binari ishte ndĂ«rtuar 32-bit, optimizimet ishin aktivizuar, biblioteka ishte e lidhur statikisht.

Shembulli për të kryer eksperimentin:


#include <chrono>
#include <cstdio>
#include <inttypes.h>
#include <memory>

#ifdef _MSC_VER
#define fwrite_unlocked _fwrite_nolock
#endif

using namespace std::chrono;

int main()
{
    std::unique_ptr<FILE, int(*)(FILE*)> file(fopen("test.bin", "wb"), fclose);
    if (!file)
        return 1;

    constexpr size_t TEST_BUFFER_SIZE = 256 * 1024;
    if (setvbuf(file.get(), nullptr, _IOFBF, TEST_BUFFER_SIZE) != 0)
        return 2;

    auto start = steady_clock::now();
    const uint8_t b = 77;
    constexpr size_t TEST_FILE_SIZE = 512 * 1024 * 1024;
    for (size_t i = 0; i < TEST_FILE_SIZE; ++i)
        fwrite_unlocked(&b, 1, sizeof(b), file.get());

    auto end = steady_clock::now();
    auto interval = duration_cast<microseconds>(end - start);
    printf("Time: %lldn", interval.count());

    return 0;
}

Si variabla do tĂ« shĂ«rbejnĂ« TEST_BUFFER_SIZE, dhe gjithashtu pĂ«r disa raste do tĂ« zĂ«vendĂ«sojmĂ« fwrite_unlocked me fwrite. Le tĂ« fillojmĂ« me rastin fwrite pa vendosur madhĂ«sinĂ« e buffer-it (do ta komentojmĂ« setvbuf dhe kodin pĂ«rkatĂ«s): koha 27048906 ÎŒs, shpejtĂ«sia e shkrimit — 18.93 Mb/s. Tani do tĂ« vendosim madhĂ«sinĂ« e buffer-it nĂ« 64 KB: koha — 25037111 ÎŒs, shpejtĂ«sia — 20.44 Mb/s. Tani do tĂ« testojmĂ« funksionimin e _fwrite_nolock pa thirrjen e setvbuf: 7262221 ÎŒs, shpejtĂ«sia — 70.5 Mb/s!

Pastaj do të eksperimentojmë me madhësinë e buffer-it (setvbuf):

Përshpejtimi i hyrjes/daljes së skedarëve C/C++, pa e ndjerë shumë

TĂ« dhĂ«nat janĂ« marrĂ« nga mesatarizimi i 5 eksperimenteve, nuk u mundova tĂ« llogaris ndonjĂ« gabim. Sipas meje, 93 Mb/s kur shkruhet 1 byte nĂ« njĂ« HDD tĂ« zakonshĂ«m — kjo Ă«shtĂ« njĂ« rezultat shumĂ« i mirĂ«, vetĂ«m duhet tĂ« zgjidhni madhĂ«sinĂ« optimale tĂ« buffer-it (nĂ« rastin tim 256 KB — Ă«shtĂ« mĂ« se e pĂ«rshtatshme) dhe zĂ«vendĂ«soni fwrite me _fwrite_nolock/fwrite_unlocked (nĂ« rast se nuk kĂ«rkohet siguria e thread-it, sigurisht).
po ashtu edhe pĂ«r fread nĂ« kĂ«to kushte. Duke parĂ« se nuk kam njĂ« makinĂ« fizike me linux pranĂ« (nje laptop eshte marre parasysh), vendosa tĂ« kryej njĂ« eksperiment tĂ« kufizuar nĂ« njĂ« makinĂ« virtuale (Hyper-V, OpenSUSE 15, GCC 8.3.1) — lidhja Ă«shtĂ« gjithashtu e njĂ«jtĂ«: fwrite e pastĂ«r 20 Mb/s, fwrite + buffer nĂ« 256 KB dha 23 Mb/s, fwrite_unlocked me tĂ« njĂ«jtin buffer — 35 Mb/s (binari 64-bit, u ndĂ«rtua g++ -o2 -s -static-libgcc -static-libstdc++ fwrite_test.cpp -o fwrite_test).

Pasthënie

QĂ«llimi i kĂ«saj artikulli ishte pĂ«rshkrimi i njĂ« truku tĂ« thjeshtĂ« dhe efektiv nĂ« shumĂ« raste (me funksionet _fwrite_nolock/fwrite_unlocked nuk isha pĂ«rballur mĂ« parĂ«, nuk janĂ« shumĂ« popullore — dhe kot). Nuk pretendoj pĂ«r novatorizĂ«m, por shpresoj qĂ« artikulli do tĂ« jetĂ« i dobishĂ«m pĂ«r komunitetin.

Burimi: habr.com

Bli njĂ« hosting tĂ« besueshĂ«m pĂ«r faqet me mbrojtje DDoS, VPS VDS serverĂ« đŸ”„ Bli njĂ« hosting tĂ« besueshĂ«m pĂ«r faqet me mbrojtje DDoS, VPS VDS serverĂ« | ProHoster