
Viruerteel
Et gëtt sou en einfachen a ganz nëtzlechen Utility op der Welt - , an et ass geschitt, datt et scho ganz laang an eisem Produktiounsprozess agebonne war (obwuel et net méiglech war seng Versioun z'installéieren, awer et war definitiv net déi lescht verfügbar). Mir benotzen et fir seng virgesinn Zweck - binäre Patches bauen. Wann Dir kuckt wat am Repository ass, gëtt et e bëssen traureg: Tatsächlech war et viru laanger Zäit verlooss a vill dovun ass ganz veroudert (meng fréiere Kolleg huet eng Kéier e puer Ännerungen do gemaach, awer dat war viru laanger Zäit) . Am Allgemengen hunn ech décidéiert dës Matière z'erliewen: Ech hunn forkéiert, erausgehäit wat ech net geplangt hunn ze benotzen, de Projet geplënnert , inlined "waarm" Mikrofunktiounen, hu grouss Arrays aus dem Stack geläscht (an Arrays vu variabelen Längt, déi mech éierlech gesot "Bomb") hunn de Profiler nach eng Kéier gelaf - a fonnt datt ongeféier 40% vun der Zäit op ...
Also wat ass mat fwrite?
An dësem Code gëtt fwrite (a mengem spezifesche Testfall: e Patch tëscht no 300 MB Dateien bauen, d'Inputdaten sinn ganz an der Erënnerung) Millioune Mol mat enger klenger Puffergréisst genannt. Selbstverständlech wäert dës Saach méi lues sinn, an dofir wéilt ech dës Schimmt iergendwéi beaflossen. Et gëtt nach kee Wonsch verschidden Aarte vun Datequellen ëmzesetzen, asynchron I/O, ech wollt eng méi einfach Léisung fannen. Déi éischt Saach, déi am Kapp komm ass, war d'Puffergréisst ze erhéijen
setvbuf(file, nullptr, _IOFBF, 64* 1024)awer ech hunn keng bedeitend Verbesserung am Resultat kritt (elo huet fwrite ongeféier 37% vun der Zäit ausgemaach) - dat heescht datt et nach ëmmer net eng Saach ass fir dacks Daten op Disk ze schreiwen. Wann Dir "ënnert der Hood" vu fwrite kuckt, kënnt Dir gesinn datt eng Spär / Spär FILE Struktur an esou eppes geschitt (Pseudo-Code, all Analyse gouf ënner Visual Studio 2017 duerchgefouert):
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;
}
Laut dem Profiler stellt _fwrite_nolock nëmmen 6% vun der Zäit aus, de Rescht ass iwwerhead. A mengem besonnesche Fall ass d'Fuedemsécherheet kloer iwwerkill, also wäert ech et opferen andeems ech de fwrite Uruff ersetzen mat - Dir musst net emol clever mat Argumenter sinn. Total: dës einfach Manipulatioun reduzéiert d'Käschte fir d'Resultat opzehuelen, wat an der Originalversioun bal d'Halschent vun der Zäit ausgaang ass. Iwwregens, an der POSIX Welt gëtt et eng ähnlech Funktioun - . Allgemeng gëllt datselwecht fir Fread. Also, andeems Dir e Paar #defines benotzt, kënnt Dir eng komplett Cross-Plattform Léisung ouni onnéideg Spär kréien, wann se net néideg sinn (an dat geschitt zimlech dacks).
fwrite, _fwrite_nolock, setvbuf
Loosst eis aus dem originale Projet abstrakt a fänken un e spezifesche Fall ze testen: eng grouss Datei (512 MB) an extrem klengen Portiounen schreiwen - 1 Byte. Testsystem: AMD Ryzen 7 1700, 16 GB RAM, HDD 7200 rpm 64 MB Cache, Windows 10 1809, de Binär gouf als 32-Bit gebaut, Optimisatiounen sinn aktivéiert, d'Bibliothéik ass statesch verlinkt.
Probe fir den Experiment:
#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;
}
D'Variabelen wäerten TEST_BUFFER_SIZE sinn, a fir e puer Fäll ersetzen mir fwrite_unlocked mat fwrite. Loosst eis mam fwrite Fall ufänken ouni explizit d'Puffergréisst ze setzen (kommentéieren setvbuf an den assoziéierten Code): Zäit 27048906 µs, Schreifgeschwindegkeet - 18.93 MB / s. Loosst eis elo d'Puffergréisst op 64 KB setzen: Zäit - 25037111 μs, Geschwindegkeet - 20.44 Mb/s. Loosst eis elo d'Operatioun vum _fwrite_nolock testen ouni setvbuf ze ruffen: 7262221 µs, Geschwindegkeet - 70.5 Mb/s!
Als nächst experimentéiere mer mat der Puffergréisst (setvbuf):

D'Donnéeën goufen duerch Moyenne 5 Experimenter kritt; Ech war ze faul fir d'Feeler ze berechnen. Wat mech ugeet, 93 MB / s wann Dir 1 Byte op eng normal HDD schreift ass e ganz gutt Resultat, Dir musst just déi optimal Puffergréisst auswielen (a mengem Fall ass 256 KB just richteg) an ersetzen fwrite mat _fwrite_nolock/fwrite_unlocked ( am Fall wann thread Sécherheet net néideg ass, natierlech).
Och mat fread an ähnleche Konditiounen. Well ech keng Hardware-Maschinn mat Linux bei der Hand hunn (Singleboard-Computeren zielen net), hunn ech beschloss e limitéierten Experiment op enger virtueller Maschinn ze maachen (Hyper-V, OpenSUSE 15, GCC 8.3.1) - den Muster ass am Prinzip d'selwecht: "plakeg" fwrite 20 Mb/s, fwrite + 256 KB Puffer produzéiert 23 Mb/s, fwrite_unlocked mam selwechte Puffer - 35 Mb/s (64-Bit binär, versammelt g++ -o2 - s -static-libgcc -static-libstdc++ fwrite_test. cpp -o fwrite_test).
Afterword
Den Zweck vun dësem Artikel ze schreiwen war eng einfach an efficace Technik a ville Fäll ze beschreiwen (Ech sinn nach ni op d'_fwrite_nolock/fwrite_unlocked Funktiounen begéint, si sinn net ganz populär - awer ëmsoss). Ech maachen net wéi wann d'Material nei ass, awer ech hoffen datt den Artikel fir d'Gemeinschaft nëtzlech ass.
Source: will.com
