Leza pelê C/C++ I/O bêyî ku teriyê bişikîne

Leza pelê C/C++ I/O bêyî ku teriyê bişikîne

Pêşniyar

Di cîhanê de amûrek wusa hêsan û pir bikêr heye - BDelta, û wusa çêbû ku ew ji bo demek pir dirêj di pêvajoya hilberîna me de cih girtibû (her çend ne gengaz bû ku guhertoya wê saz bike, lê bê guman ew ne ya paşîn a berdest bû). Em wê ji bo mebesta wê bikar tînin - avakirina paçên binary. Ger hûn li tiştên ku di depoyê de hene binihêrin, ew hinekî xemgîn dibe: bi rastî, ew demek dirêj berê hate terikandin û piraniya wê pir kevnar e (hevkarê min ê berê carekê li wir çend guherandin çêkir, lê ew demek dirêj berê bû) . Bi gelemperî, min biryar da ku ez vê mijarê ji nû ve vejînim: Min çiqandin, tiştê ku min plan nedikir ku bikar bînim avêtim, proje bar kir cmake, mîkrofonksiyonên "germ" xêz kirin, rêzikên mezin ji stikê rakirin (û rêzikên bi dirêjahiya guhêrbar, ku bi eşkereyî min dike "bombê"), profîler carek din xebitand - û fêr bû ku ji% 40-ê wextê li ser derbas dibe. fwrite...

Ji ber vê yekê bi fwrite re çi ye?

Di vê kodê de, fwrite (di doza ceribandina min a taybetî de: çêkirina paçek di navbera pelên nêzî 300 MB de, daneyên têketinê bi tevahî di bîranînê de ye) bi mîlyonan caran bi mezinahiyek tamponek piçûk tê gotin. Eşkere ye ku ev tişt dê hêdî bibe, û ji ber vê yekê ez dixwazim bi rengekî bandor li ser vê şermê bikim. Hê jî xwestek tune ku cûrbecûr çavkaniyên daneyê, I/O asynchronous bicîh bîne, min xwest ku çareseriyek hêsantir bibînim. Yekem tiştê ku hate bîra me zêdekirina mezinahiya tamponê bû

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

lê min di encamê de pêşkeftinek girîng nedît (naha fwrite bi qasî 37% ji demê tê hesibandin) - ev tê vê wateyê ku ew hîn jî ne mijara nivîsandina daneyan li ser dîskê ye. Li "di bin serpêhatiya" fwrite digerin, hûn dikarin bibînin ku avahiyek FILE ya kilît / venekirin di hundurê tiştek wusa diqewime (pseudo-kod, hemî analîz di binê Visual Studio 2017 de hate kirin):


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;
}

Li gorî profîler, _fwrite_nolock tenê ji% 6-ê wextê hesab dike, ya mayî serma ye. Di doza min a taybetî de, ewlehiya mijarê bi zelalî zêde ye, ji ber vê yekê ez ê wê qurban bikim bi guheztina banga fwrite bi _fwrite_nolock - Tewra ne hewce ye ku hûn bi argumanan jîr bin. Bi tevahî: ev manîpulasyona hêsan bi giranî lêçûna tomarkirina encamê kêm kir, ku di guhertoya orîjînal de hema hema nîvê wextê derbas bûye. Bi awayê, di cîhana POSIX de fonksiyonek wusa heye - fwrite_unlocked. Bi gelemperî, heman tişt ji bo fread re derbas dibe. Bi vî rengî, bi karanîna cotek #defines, hûn dikarin çareseriyek bi tevahî cross-platformê bêyî qefleyên nehewce bistînin heke ew ne hewce bin (û ev pir caran diqewime).

fwrite, _fwrite_nolock, setvbuf

Давайте абстрагируемся от оригинального проекта и займёмся тестированием конкретного случая: записи большого файла (512 Мб) предельно малыми порциями — в 1 байт. Тестовая система: AMD Ryzen 7 1700, 16 Гб ОЗУ, HDD 7200 rpm 64 Мб кэша, Windows 10 1809, бинарь строился 32-х битный, оптимизации включены, библиотека статически прилинкована.

Nimûne ji bo ceribandinê:


#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;
}

Guherbar dê bibin TEST_BUFFER_SIZE, û ji bo çend rewşan em ê fwrite_unlocked bi fwrite veguherînin. Ka em bi doza fwrite dest pê bikin bêyî ku bi eşkere mezinahiya tamponê destnîşan bikin (setvbuf û koda têkildar şîrove bikin): dem 27048906 µs, leza nivîsandinê - 18.93 MB/s. Naha em mezinahiya tamponê bidin 64 KB: dem - 25037111 μs, leza - 20.44 Mb/s. Naha werin em xebata _fwrite_nolock bêyî bangkirina setvbuf biceribînin: 7262221 µs, leza - 70.5 Mb/s!

Dûv re, em bi mezinahiya tamponê (setvbuf) biceribînin:

Leza pelê C/C++ I/O bêyî ku teriyê bişikîne

Daneyên bi navînî 5 ceribandinan hatin wergirtin; Ez ji hesabkirina xeletiyan pir tembel bûm. Wekî ji bo min, 93 MB/s dema ku 1 byte li HDD-ya birêkûpêk dinivîse encamek pir baş e, hûn tenê hewce ne ku mezinahiya tamponê ya çêtirîn hilbijêrin (di rewşa min de, 256 KB rast e) û fwrite bi _fwrite_nolock/fwrite_unlocked veguherînin ( di rewşê de ku ewlehiya tîrêjê ne hewce ye, bê guman).
Bi heman awayî bi fread di şert û mercên wekhev de. Ji ber ku li ber destê min makîneyek hardware ya bi Linux tune ye (komputerên yek-board nayên hejmartin), min biryar da ku ez ceribandinek sînorkirî li ser makîneyek virtual (Hyper-V, OpenSUSE 15, GCC 8.3.1) bikim - nimûne, di prensîbê de, heman e: fwrite "tazî" 20 Mb/s, fwrite + 256 KB tampon 23 Mb/s hatî hilberandin, fwrite_vekirî bi heman tamponê - 35 Mb/s (binary 64-bit, g++ -o2 - s -static-libgcc -static-libstdc++ fwrite_test. cpp -o fwrite_test).

Paşê

Armanca nivîsandina vê gotarê ew bû ku di gelek rewşan de teknîkek sade û bibandor rave bike (min berê qet li fonksiyonên _fwrite_nolock/fwrite_unlocked rast nehatibû, ew ne pir populer in - lê bêwate). Ez naxwazim ku materyal nû ye, lê ez hêvî dikim ku gotar dê ji civakê re kêrhatî be.

Source: www.habr.com

Ji bo malperên bi parastina DDoS, serverên VPS VDS mêvandariya pêbawer bikirin 🔥 Hostinga malperê ya pêbawer bi parastina DDoS, serverên VPS VDS bikirin | ProHoster