
priekÅ”vÄrds
PasaulÄ ir tik vienkÄrÅ”a un ļoti noderÄ«ga utilÄ«ta - , un sagadÄ«jÄs, ka mÅ«su ražoÅ”anas procesÄ tas bija iesakÅojies ļoti ilgu laiku (lai gan tÄ versiju nebija iespÄjams uzstÄdÄ«t, bet tÄ noteikti nebija pÄdÄjÄ pieejamÄ). MÄs to izmantojam paredzÄtajam mÄrÄ·im - binÄro ielÄpu veidoÅ”anai. Ja paskatÄs uz to, kas atrodas krÄtuvÄ, kļūst nedaudz skumji: patiesÄ«bÄ tas tika pamests jau sen un liela daļa no tÄ ir ļoti novecojusi (mans bijuÅ”ais kolÄÄ£is savulaik tur veica vairÄkus labojumus, bet tas bija sen) . KopumÄ es nolÄmu atjaunot Å”o lietu: es dakÅ”u, izmetu to, ko neplÄnoju izmantot, pÄrcÄlu projektu uz , iekļÄva ākarstÄsā mikrofunkcijas, no steka noÅÄma lielus masÄ«vus (un mainÄ«ga garuma masÄ«vus, kas, godÄ«gi sakot, padara mani par ābumbuā), palaida profilÄtÄju vÄlreiz ā un atklÄja, ka aptuveni 40% laika tiek pavadÄ«ts ...
Kas tad notiek ar fwrite?
Å ajÄ kodÄ fwrite (manÄ konkrÄtajÄ testa gadÄ«jumÄ: veidojot ielÄpu starp tuvu 300 MB failiem, ievades dati pilnÄ«bÄ atrodas atmiÅÄ) tiek izsaukts miljoniem reižu ar mazu bufera izmÄru. AcÄ«mredzot Ŕī lieta palÄninÄsies, un tÄpÄc es gribÄtu kaut kÄ ietekmÄt Å”o negodu. PagaidÄm nav vÄlmes ieviest dažÄda veida datu avotus, asinhrono ievadi-izvadi, gribÄju atrast vienkÄrÅ”Äku risinÄjumu. PirmÄ lieta, kas ienÄca prÄtÄ, bija palielinÄt bufera izmÄru
setvbuf(file, nullptr, _IOFBF, 64* 1024)bet es nesaÅÄmu bÅ«tisku rezultÄta uzlabojumu (tagad fwrite veidoja aptuveni 37% laika) ā tas nozÄ«mÄ, ka joprojÄm nav runa par biežu datu ierakstīŔanu diskÄ. Apskatot fwrite āzem pÄrsegaā, jÅ«s varat redzÄt, ka bloÄ·ÄÅ”anas/atbloÄ·ÄÅ”anas FILE struktÅ«ra notiek kaut kas lÄ«dzÄ«gs Å”im (pseidokods, visa analÄ«ze tika veikta programmÄ 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;
}
PÄc profilÄtÄja domÄm, _fwrite_nolock veido tikai 6% laika, pÄrÄjais ir pieskaitÄms. ManÄ konkrÄtajÄ gadÄ«jumÄ pavedienu droŔība ir nepÄrprotami pÄrmÄrÄ«ga, tÄpÄc es to ziedoÅ”u, aizstÄjot fwrite zvanu ar - jums pat nav jÄbÅ«t gudram ar argumentiem. KopÄ: Ŕī vienkÄrÅ”Ä manipulÄcija ievÄrojami samazinÄja rezultÄta ierakstīŔanas izmaksas, kas sÄkotnÄjÄ versijÄ bija gandrÄ«z puse no pavadÄ«tÄ laika. Starp citu, POSIX pasaulÄ ir lÄ«dzÄ«ga funkcija - . VispÄrÄ«gi runÄjot, tas pats attiecas uz fread. TÄdÄjÄdi, izmantojot #defines pÄri, jÅ«s varat iegÅ«t pilnÄ«gi starpplatformu risinÄjumu bez nevajadzÄ«gÄm slÄdzenÄm, ja tÄs nav vajadzÄ«gas (un tas notiek diezgan bieži).
fwrite, _fwrite_nolock, setvbuf
AttÄlinÄsimies no sÄkotnÄjÄ projekta un pievÄrsÄ«simies konkrÄta gadÄ«juma testÄÅ”anai: liela faila (512 MB) ierakstīŔana ÄrkÄrtÄ«gi mazos fragmentos ā pa vienam baitam katrÄ. Testa sistÄma: AMD Ryzen 7 1700, 16 GB RAM, 7200 apgr./min cietais disks, 64 MB keÅ”atmiÅa. Windows 10 1809. gadÄ binÄrais fails tika veidots kÄ 32 bitu, optimizÄcijas ir iespÄjotas, bibliotÄka ir statiski saistÄ«ta.
Eksperimenta paraugs:
#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;
}
MainÄ«gie lielumi bÅ«s TEST_BUFFER_SIZE, un dažos gadÄ«jumos mÄs aizstÄsim fwrite_unlocked ar fwrite. SÄksim ar fwrite gadÄ«jumu, nepÄrprotami iestatot bufera lielumu (komentÄjiet setvbuf un saistÄ«to kodu): laiks 27048906 µs, rakstīŔanas Ätrums - 18.93 MB/s. Tagad iestatÄ«sim bufera izmÄru uz 64 KB: laiks - 25037111 μs, Ätrums - 20.44 Mb/s. Tagad pÄrbaudÄ«sim _fwrite_nolock darbÄ«bu, neizsaucot setvbuf: 7262221 µs, Ätrums - 70.5 Mb/s!
TÄlÄk eksperimentÄsim ar bufera lielumu (setvbuf):

Dati tika iegÅ«ti, vidÄji aprÄÄ·inot 5 eksperimentus; es biju pÄrÄk slinks, lai aprÄÄ·inÄtu kļūdas. Kas attiecas uz mani, 93 MB/s, rakstot 1 baitu uz parastu HDD, ir ļoti labs rezultÄts, tikai jÄizvÄlas optimÄlais bufera izmÄrs (manÄ gadÄ«jumÄ 256 KB ir tieÅ”i piemÄrots) un fwrite jÄaizstÄj ar _fwrite_nolock/fwrite_unlocked ( gadÄ«jumÄ, ja vÄ«tnes droŔība, protams, nav nepiecieÅ”ama).
TÄpat ar fread lÄ«dzÄ«gos apstÄkļos. TÄ kÄ man nav pie rokas aparatÅ«ras maŔīnas ar Linux (viena borta datori netiek skaitÄ«ti), es nolÄmu veikt ierobežotu eksperimentu ar virtuÄlo maŔīnu (Hyper-V, OpenSUSE 15, GCC 8.3.1) - modelis principÄ ir tas pats: āpliksā fwrite 20 Mb/s, fwrite + 256 KB buferis ražots 23 Mb/s, fwrite_unlocked ar to paÅ”u buferi - 35 Mb/s (64 bitu binÄrs, samontÄts g++ -o2 - s -static-libgcc -static-libstdc++ fwrite_test. cpp -o fwrite_test).
PÄcvÄrds
Å Ä« raksta rakstīŔanas mÄrÄ·is bija aprakstÄ«t vienkÄrÅ”u un daudzos gadÄ«jumos efektÄ«vu paÅÄmienu (es nekad iepriekÅ” neesmu saskÄries ar _fwrite_nolock/fwrite_unlocked funkcijÄm, tÄs nav Ä«paÅ”i populÄras - bet velti). Es neizliekos, ka materiÄls ir jauns, bet ceru, ka raksts bÅ«s noderÄ«gs sabiedrÄ«bai.
Avots: www.habr.com
