C++ Rusia: si ndodhi

Nëse në fillim të dramës thoni se në mur është një kod në C++, atëherë deri në fund ai patjetër duhet të ju dëmtojë.

Bjarne Stroustrup

Nga 31 tetori deri mĂ« 1 nĂ«ntor nĂ« ShĂ«n PETËRBURG, u zhvillua konferenca C++ Russia Piter – njĂ« nga konferencat mĂ« tĂ« mĂ«dha pĂ«r programimin nĂ« Rusi, e organizuar nga JUG Ru Group. Mes folĂ«sve tĂ« ftuar ishin anĂ«tarĂ« tĂ« komitetit tĂ« standardizimit tĂ« C++, ligjĂ«rues nga CppCon, autorĂ« librash nga shtĂ«pia botuese O’Reilly, si dhe mirĂ«mbajtĂ«s tĂ« projekteve tĂ« tilla si LLVM, libc++ dhe Boost. Konferenca ishte e orientuar ndaj zhvilluesve tĂ« shkĂ«lqyer nĂ« C++ qĂ« dĂ«shirojnĂ« tĂ« thellojnĂ« ekspertizĂ«n e tyre dhe tĂ« shkĂ«mbejnĂ« pĂ«rvoja nĂ« njĂ« komunikim tĂ« drejtpĂ«rdrejtĂ«. StudentĂ«t, doktorantĂ«t dhe mĂ«suesit e universiteteve ofrohen zbritje shumĂ« tĂ« kĂ«ndshme.

Botimi i konferencës në Moskë do të mund të vizitohet që në prill të vitit të ardhshëm, ndërsa tanimë studentët tanë do të tregojnë se çfarë interesante kanë mësuar nga ngjarja e kaluar. 

C++ Rusia: si ndodhi

Foto nga albumi i konferencës

NĂ« lidhje me ne

PĂ«r kĂ«tĂ« post punuan dy studentĂ« nga NIU HSE – ShĂ«n Petersburg:

  • Liza Vasilenko – studente nĂ« vitin e katĂ«rt tĂ« bachelorit, qĂ« studion drejtimin 'GjuhĂ«t e Programimit' nĂ« kuadĂ«r tĂ« programit 'Matematika e aplikuar dhe InformaticĂ«'. Pas njohjes me gjuhĂ«n C++ nĂ« vitin e parĂ« tĂ« universitetit, mĂ« vonĂ« fitoi pĂ«rvojĂ« pune me tĂ« nĂ« praktikĂ« nĂ« industri. Pasioni pĂ«r gjuhĂ«t e programimit nĂ« pĂ«rgjithĂ«si dhe programimin funksional nĂ« veçanti ka ndikuar nĂ« zgjedhjen e ligjĂ«ratave nĂ« konferencĂ«.
  • Danya Smirnov – student nĂ« vitin e parĂ« tĂ« masterit nĂ« 'Programim dhe AnalizĂ« tĂ« DhĂ«nash'. Edhe nĂ« shkollĂ« shkruante detyra olimpike nĂ« C++, dhe mĂ« tej ndodhi qĂ« gjuhĂ« tĂ« shfaqej vazhdimisht nĂ« aktivitetet e studimeve dhe pĂ«rfundimisht u bĂ« gjuha kryesore e punĂ«s. Vendosi tĂ« marrĂ« pjesĂ« nĂ« konferencĂ« pĂ«r tĂ« pĂ«rmirĂ«suar njohuritĂ« e tij dhe gjithashtu pĂ«r tĂ« mĂ«suar mbi mundĂ«sitĂ« e reja.

Në dërgimin e informacionit, drejtoria e fakultetit shpesh ndan informacion rreth ngjarjeve edukative të lidhura me specialitetin tonë. Në shtator pamë informacionin për C++ Russia dhe vendosëm të regjistrohemi si dëgjues. Ky është eksperienca jonë e parë në pjesëmarrjen në konferenca të tilla.

Struktura e konferencës

  • LigjĂ«ratat

Brenda dy të dy ditëve, ekspertët lexuan 30 referate, duke ndriçuar shumë tema interesante: aplikimet e zgjuara të veçorive të gjuhës për zgjidhjen e problemeve praktike, përditësimet e ardhshme të gjuhës në lidhje me standardin e ri, kompromiset në dizajnin e C++ dhe masat paraprake për të punuar me pasojat e tyre, shembuj të arkitekturave interesante të projekteve, si dhe disa detaje nën kapak për infrastrukturën e gjuhës. Njëkohësisht, zhvilloheshin 3 prezantime, shpesh dy në rusisht dhe një në anglisht.

  • Zonat e diskutimit

Pas prezantimeve, të gjitha pyetjet e paqëna dhe diskutimet e papërfunduara kaloheshin në zona të veçanta komunikimi me prezantuesit, të pajisura me bordet për shkrim. Një mënyrë e shkëlqyer për të kaluar pushimin midis prezantimeve me një bisedë të këndshme.

  • Lightning Talks dhe diskutime informale

Nëse dëshironi të bëni një prezantim të shkurtër, mund të regjistroheni në bordin për shkrim për Lightning Talk-in e mbrëmjes dhe të merrni pesë minuta kohë për të folur për çfarëdo që lidhet me temën e konferencës. Për shembull, një hyrje e shpejtë në sanitizers për C++ (për disa ishte një risi) ose një histori për një bug në gjenerimin e sinusoidës, i cili mund të dëgjohet vetëm, por nuk mund të shihet.

Formati tjetër është paneli diskutues "Me Komitetin për Shpirtrat". Në skenë janë disa anëtarë të komitetit të standardizimit, në projektor është një oxhak (zyrtarisht për të krijuar një atmosferë të ngrohtë, por arsyeja "sepse gjithçka është në flakë" duket më argëtuese), pyetje janë rreth standardit dhe vizionit të përgjithshëm për C++, pa diskutime teknike të nxehta dhe polemika. U zbulua se në komitet ka edhe njerëz të gjallë që mund të jenë të pasigurt për diçka ose të mos dinë ndonjë gjë.

PĂ«r ata qĂ« duan polemika, ngeli njĂ« ngjarje tjetĂ«r — sesioni BOF "Go kundĂ«r C++". Merrni njĂ« adhurues tĂ« Go, njĂ« adhurues tĂ« C++, pĂ«rpara se tĂ« fillojĂ« sesioni ata bashkĂ« pĂ«rgatitin 100500 diapozitivĂ« mbi temĂ«n (siç janĂ« problemet me paketat nĂ« C++ ose mungesa e gjenerikĂ«ve nĂ« Go), dhe mĂ« pas ata diskutojnĂ« aktivisht me njĂ«ri-tjetrin e me audiencĂ«n, ndĂ«rsa audienca pĂ«rpiqet tĂ« kuptojĂ« dy kĂ«ndvĂ«shtrime njĂ«kohĂ«sisht. NĂ«se fillon njĂ« polemikĂ« jashtĂ« teme — moderatori intervin dhe pajton palĂ«t. Ky format Ă«shtĂ« i ngjashĂ«m: disa orĂ« pas fillimit, ishin kaluar vetĂ«m gjysmĂ«n e diapozitivĂ«ve. Fundi u detyrua tĂ« pĂ«rshpejtohej shumĂ«.

  • Standet e partnerĂ«ve

NĂ« hollin u paraqitĂ«n partnerĂ«t e konferencĂ«s — nĂ« stendat u treguan projektet aktuale, ofruan praktika dhe mundĂ«si punĂ«simi, organizuan kuize dhe garat tĂ« vogla, si dhe dhanĂ« dhurata tĂ« kĂ«ndshme. Disa kompani madje ofruan tĂ« kalonin fazat fillestare tĂ« intervistave, gjĂ« qĂ« mund tĂ« jetĂ« e dobishme pĂ«r ata qĂ« erdhĂ«n jo vetĂ«m pĂ«r tĂ« dĂ«gjuar prezantimet.

Detajet teknike të prezantimeve

Ne dĂ«gjuam prezantimet pĂ«r tĂ« dy ditĂ«t. NganjĂ«herĂ« ishte e vĂ«shtirĂ« tĂ« zgjidhje njĂ« prezantim nga ato qĂ« zhvillonin paralelisht – ne ranĂ« dakord tĂ« ndaheshim dhe tĂ« bashkĂ«punohej nĂ« periudhat ndĂ«rmjet. Edhe kĂ«shtu, duket se shumĂ« ka mbetur pa u pĂ«rmendur. KĂ«tu do tĂ« dĂ«shironim tĂ« flasim pĂ«r pĂ«rmbajtjen e disa prezantimeve qĂ« na dukeshin mĂ« interesante.

Përjashtimet në C++ përmes optimizimeve të kompiluesit, Roman Ruseajev

C++ Rusia: si ndodhi
Slide nga prezentimi

Ashtu siç kuptohet nga titulli, Roman shqyrtoi punën me përjashtimet duke përdorur shembuj të LLVM. Megjithatë, për ata që nuk përdorin Clang në punën e tyre, prezantimi mund të japë një pasqyrë të asaj se si kodi mund të optimizohet potencialisht. Kjo ndodh sepse zhvilluesit e kompiluesve dhe biblioteka standarde përkatëse komunikojnë me njëri-tjetrin dhe shumë zgjidhje të suksesshme mund të përputhen.

Pra, pĂ«r tĂ« trajtuar njĂ« pĂ«rjashtim Ă«shtĂ« e nevojshme tĂ« bĂ«hen shumĂ« veprime: tĂ« thirret kodi i trajtimit (nĂ«se ka) ose tĂ« lirohen burimet nĂ« nivelin aktual dhe tĂ« çlirohet staku mĂ« lart. E gjithĂ« kjo çon nĂ« atĂ« qĂ« pĂ«r thirrjet qĂ« potencialisht mund tĂ« japin pĂ«rjashtime, kompajleri shton instruksione shtesĂ«. Prandaj, nĂ«se nĂ« fakt pĂ«rjashtimi nuk do tĂ« thirret, programi do tĂ« vazhdojĂ« tĂ« ekzekutojĂ« veprime tĂ« panevojshme. PĂ«r tĂ« ulur ndarjen e kostove, nĂ« LLVM ka disa heuristika pĂ«r tĂ« pĂ«rcaktuar situatat ku kodi i trajtimit tĂ« pĂ«rjashtimeve nuk Ă«shtĂ« i nevojshĂ«m ose ku mund tĂ« ulet numri i instruksioneve “tĂ« panevojshme”.

Prezantuesi shqyrton rreth dhjetë prej tyre dhe tregon si situatat ku ato ndihmojnë në përshpejtimin e ekzekutimit të programit, ashtu edhe ato ku këto metoda nuk janë të aplicueshme.

Kështu, Roman Ruseajev e çon publikun në përfundimin se kodi që përmban punën me përjashtimet, aspak gjithmonë mund të ekzekutohet me kostot të zero, dhe jep këto këshilla:

  • Kur zhvilloni biblioteka, duhet tĂ« hiqni dorĂ« nga pĂ«rjashtimet nĂ« parim;
  • nĂ«se pĂ«rjashtimet janĂ« tĂ« nevojshme, gjithmonĂ« duhet tĂ« shtoni modifikatorĂ«t noexcept (dhe const), nĂ« mĂ«nyrĂ« qĂ« kompajleri tĂ« mund tĂ« optimizojĂ« sa mĂ« shumĂ«.

Në përgjithësi, folësi konfirmoi mendimin se përjashtimet është më mirë t'i përdorim sa më pak ose të heqim dorë nga to krejtësisht.

Prezantimi i slides është i disponueshëm në lidhje: [«Përjashtimet C++ përmes optimizimeve të kompajlerëve LLVM»]

Generatoret, korutinat dhe ëndrrat e tjera, Adi Shavit

C++ Rusia: si ndodhi
Slide nga prezentimi

Një nga shumë prezantimet e kësaj konference, e cila ishte e dedikuar novatoreve C++20, mbeti në kujtesë jo vetëm për prezantimin e saj të mirë, por edhe për shpjegimin e qartë të problemeve ekzistuese me logjikën e përpunimit të koleksioneve (cikli for, callback-e).

Adi Shavit identifikon kĂ«to probleme: metodat aktuale kalojnĂ« tĂ« gjithĂ« koleksionin dhe nuk ofrojnĂ« akses nĂ« njĂ« gjendje tĂ« brendshme ndĂ«rmjetĂ«se (ose e bĂ«jnĂ« kĂ«tĂ« nĂ« rastin e callback-eve, por me shumĂ« efekte anĂ«sore tĂ« pakĂ«ndshme, si Callback Hell). Duket se ka iteratorĂ«, por as me ata nuk Ă«shtĂ« aq e lehtĂ«: nuk ka pĂ«rputhje tĂ« pĂ«rbashkĂ«t tĂ« pikĂ«s sĂ« hyrjes dhe daljes (begin → end kundrejt rbegin → rend dhe kĂ«shtu me radhĂ«), nuk kuptohet se sa shumĂ« do tĂ« iterojmĂ«? Duke filluar nga C++20, kĂ«to probleme zgjidhen!

Opcioni i parë: ranges. Duke u mbështetur në iteratorët, ne marrim një ndërfaqe të përbashkët për fillimin dhe përfundimin e iterimit, si dhe mundësinë e kompozitës. Të gjitha këto lejojnë ndërtimin e lehtë të konvejereve të plota për përpunimin e të dhënave. Por nuk është gjithçka aq e lehtë: një pjesë e logjikës së llogaritjeve ndodhet brenda realizimit të iteratorit specifik, gjë që mund ta komplikohet kodin për t'u kuptuar dhe debug-uar.

C++ Rusia: si ndodhi
Slide nga prezentimi

ÇfarĂ« mund tĂ« themi, pĂ«r kĂ«tĂ« rast nĂ« C++20 janĂ« shtuar korutinat (funksione, sjellja e tĂ« cilave Ă«shtĂ« e ngjashme me generatoret nĂ« gjuhĂ«n Python): ekzekutimi mund tĂ« shtyhet, duke kthyer njĂ« vlerĂ« aktuale me ruajtjen e gjendjes ndĂ«rmjetĂ«se. KĂ«shtu, ne arrijmĂ« jo vetĂ«m tĂ« punojmĂ« me tĂ« dhĂ«nat pas paraqitjes sĂ« tyre, por gjithashtu inkapsulojmĂ« tĂ« gjithĂ« logjikĂ«n brenda çdo korutine specifike.

Por një lugë katran: në momentin e tanishëm, ato mbështeten vetëm pjesërisht nga kompilatorët aktualë, si dhe janë implementuar jo aq me kujdes siç do të dëshironim: për shembull, aktualisht nuk duhet të përdoren në korutina referencat dhe objektet e përkohshme. Plus, ka disa kufizime se çfarë mund të jetë korutina, dhe funksionet constexpr, konstruktorët/destruktoret, si dhe main nuk janë pjesë e këtij listës.

Kështu, korutinat zgjidhin një pjesë të dukshme të problemeve me thjeshtësinë e logjikës së përpunimit të të dhënave, por implementimet e tyre aktuale kërkojnë përmirësim.

Materialet:

Truket C++ nga Yandex.Taxi, Anton Polukhyn

Në aktivitetin tim profesional, ndonjëherë duhet të realizoj gjëra krejt ndihmëse: një mbështetje midis ndërfaqes së brendshme dhe API-të e ndonjë biblioteke, regjistrimi ose analizimi. Në këtë rast, zakonisht nuk ka nevojë për ndonjë optimizim të mëtejshëm. Por çfarë ndodh nëse këto komponente përdoren në disa nga shërbimet më të njohura në Runet? Në këtë situatë do të duhet të përpunojmë terabajt në orë vetëm për regjistrimet! Atëherë çdo milisekondë është e rëndësishme dhe prandaj duhet të përdorim truke të ndryshme - për to ka folur Anton Polukhyn.

Mendoj se shembulli më interesant ishte implementimi i modelit pointer-to-implementation (pimpl). 

#include <third_party/json.hpp> //PROBLEMS! 
struct Value { 
    Value() = default; 
    Value(Value&& other) = default; 
    Value& operator=(Value&& other) = default; 
    ~Value() = default; 

    std::size_t Size() const { return data_.size(); } 

private: 
    third_party::Json data_; 
};

Në këtë shembull, fillimisht duam të heqim dorë nga skedarët e kokës së biblioteka të jashtme - kështu që do të kompilojmë më shpejt, dhe gjithashtu mund të sigurojmë veten nga konflikte të mundshme emrash dhe gabimesh të tjera të ngjashme. 

Mirë, e kemi transferuar #include në skedarin .cpp: na nevojitet një deklaratë paraprake e API-t të mbështjellë, si dhe std::unique_ptr. Tani kemi alokime dinamike dhe gjëra të tjera të pakëndshme si të dhënat të shpërndara në grumbull dhe garancitë e reduktuara. Me të gjitha këto mund të ndihmojë std::aligned_storage. 

struct Vlera { 
// ... 
private: 
    using JsonNative = third_party::Json; 
    const JsonNative* Ptr() const noexcept; 
    JsonNative* Ptr() noexcept; 

    constexpr std::size_t kImplSize = 32; 
    constexpr std::size_t kImplAlign = 8; 
    std::aligned_storage_t data_; 
};

Problemi i vetëm: duhet të tregojmë për çdo mbështjellës madhësinë dhe përputhshmërinë - do ta bëjmë pimpl-in tonë të shabllonit me parametra , do ta përdorim me disa vlera arbitrare dhe do të shtojmë në destruktorin verifikimin se e kemi gjetur gjithçka saktë: 

~FastPimpl() noexcept { 
    validate(); 
    Ptr()->~T(); 
}

template 
static void validate() noexcept { 
    static_assert(
        Size == ActualSize, 
        "Size and sizeof(T) mismatch"
    ); 
    static_assert(
        Alignment == ActualAlignment, 
        "Alignment and alignof(T) mismatch"
    ); 
}

Dukezh të mërzitshme ky kod do të trajtojë saktë duke u përpunuar në fazën e kompilimit dhe do të prodhojë vlerat e nevojshme të madhësisë dhe grumbullimit, të cilat duhet të shkruhen. Kështu, me një qëllim të vetëm shtesë të kompilimit, ne eliminojmë alokimin dinamik të klasave të mbuluara, fshehim API-në në skedarin .cpp me implementimin, si dhe fitojmë një strukturë më të përshtatshme për keƥimin nga procesori.

Regjistrimi dhe analizimi dukeshin më pak të impresionuese, dhe për këtë arsye nuk do të përmenden në këtë shqyrtim.

Prezantimi i slides është i disponueshëm në lidhje: [«C++ truket nga Taxi»]

Teknikat moderne për të mbajtur kodin tuaj DRY, Björn Fahller

Në këtë referat, Björn Fahller tregon disa mënyra të ndryshme për të luftuar një gabim stilistik siç janë kontrollet e përsëritura të kushteve:

assert(a == IDLE || a == CONNECTED || a == DISCONNECTED);

E njohur? Duke përdorur disa teknikë të fuqishme të C++, të cilat janë shfaqur në standardet e fundit, mund të realizoni funksionalitetin e njëjtë në mënyrë elegante pa ndonjë humbje të performancës. Krahasoni:   

assert(a == any_of(IDLE, CONNECTED, DISCONNECTED));

PĂ«r tĂ« trajtuar njĂ« numĂ«r tĂ« panatyrshĂ«m tĂ« kontrolleve, kĂ«rkohet pĂ«rdorimi i shablloneve variadic dhe shprehjeve tĂ« mbĂ«shtjella. Le tĂ« supozojmĂ« se duam tĂ« kontrollojmĂ« barazinĂ« e disa variablave me njĂ« element tĂ« enum’t state_type. E para qĂ« na vjen nĂ« mendje – tĂ« shkruajmĂ« njĂ« funksion ndihmĂ«s is_any_of:


enum state_type { IDLE, CONNECTED, DISCONNECTED };

template 
bool is_any_of(state_type s, const Ts& ... ts) { 
    return ((s == ts) || ...); 
}

Ky rezultat ndërmjetës shkakton zhgënjim. Përsëri, kodi nuk bëhet më i lexueshëm:

assert(is_any_of(state, IDLE, DISCONNECTING, DISCONNECTED)); 

Pak përmirësim në situatë do të ndihmojnë parametrat non-type të shablloneve. Me ndihmën e tyre, do ta transferojmë elementet e enum në listën e parametrave të shablloneve: 

template 
bool is_any_of(state_type t) { 
    return ((t == states) | ...); 
}
	
assert(is_any_of(state)); 

Me përdorimin e auto në parametrin jo-të tipit të shabllonit (C++17), qasja vetëm sa e përgjithëson atë për krahasimet, jo vetëm me elementet e state_type, por edhe me tipet primitive, të cilat mund të përdoren si parametra non-type shablloni:


template 
bool is_any_of(const T& t) {
    return ((t == alternatives) | ...);
}

Me përmirësime të tilla të njëpasnjësh, arrihet sintaksa e dëshiruar e rrjedhshme për kontrollin:


template 
struct any_of : private std::tuple { 
// le të trashëgojmë konstruktoret nga tuple 
        using std::tuple::tuple;
        template 
        bool operator ==(const T& t) const {
                return std::apply(
                    [&t](const auto& ... ts) {
                        return ((ts == t) || ...);
                    },
                    static_cast<const std::tuple&>(*this));
        }
};

template 
any_of(Ts ...) -> any_of;
 
assert(any_of(IDLE, DISCONNECTING, DISCONNECTED) == state);

Në këtë shembull, udhëzuesi i deduksionit shërben për të sugjeruar parametrat e dëshiruar të template që kompajleri të njohë llojet e argumenteve të konstruktorit. 

MĂ« pas – bĂ«het mĂ« interesante. Bjorn na mĂ«son tĂ« pĂ«rgjithĂ«sojmĂ« kodin e fituar pĂ«r operatorĂ«t e krahasimit pĂ«rveç ==, dhe mĂ« pas edhe pĂ«r operacione tĂ« rastĂ«sishme. Parallaxa pĂ«rdorimit shpjegon karakteristika si no_unique_address attribute (C++20) dhe parametrat e templates nĂ« funksionet lambda (C++20). (Po, tani sintaksa e lambdave Ă«shtĂ« shumĂ« mĂ« e lehtĂ« pĂ«r t'u mbajtur mend – ka katĂ«r çifte tĂ« radhitur tĂ« thonjve tĂ« çdo lloji.) Zgjidhja pĂ«rfundimtare me pĂ«rdorimin e funksioneve si detaje tĂ« konstruktorit personalisht mĂ« ngroh shpirtin, pĂ«r tĂ« mos pĂ«rmendur shprehjen tuple nĂ« traditat mĂ« tĂ« mira tĂ« llogaritjes lambda.

Në fund, mos harxhoni të bëni rregullime:

  • KujtojmĂ« se lambdave – constexpr falas; 
  • ShtojmĂ« perfect forwarding dhe shohim sintaksĂ«n e tij tĂ« shĂ«mtuar nĂ« lidhje me parameter pack nĂ« mbushjen e lambdave;
  • DĂ«rgojmĂ« kompajlerin mĂ« shumĂ« mundĂ«si pĂ«r optimizimin me conditional noexcept; 
  • Kujdesemi pĂ«r njĂ« dalje mĂ« tĂ« qartĂ« tĂ« gabimeve nĂ« templates falĂ« vlerave tĂ« kthimit tĂ« qarta nga lambdat. Kjo do ta detyrojĂ« kompajlerin tĂ« bĂ«jĂ« mĂ« shumĂ« kontrolle para thirrjes sĂ« vĂ«rtetĂ« tĂ« funksionit template – nĂ« fazĂ«n e kontrollit tĂ« llojeve. 

Për detaje, reference në materialet e ligjëratës: 

Përvojat tona

Pjesa jonë e parë në C++ Russia mbeti e paharrueshme për nga intensiteti. U krijua përshtypja se C++ Russia ishte një ngjarje e ngrohtë, ku ndarja mes mësimit dhe bisedave të gjalla ishte pothuajse e padukshme. Gjithçka, nga fryma e prezantuesve deri te garat nga partnerët e ngjarjes, inkurajonte diskutime të ngjashme. Pjesa përmbajtësore e konferencës, e cila përfshin prezantime, mbulon një gamë të gjerë temash, duke përfshirë risitë në C++, shembuj nga praktika e projekteve të mëdha dhe konsiderata arkitektonike ideologjike. Por do të ishte e padrejtë të mos theksohej edhe aspekti social i ngjarjes, i cili ndihmon në kalimin e barrierave gjuhësore në lidhje jo vetëm me C++.

Faleminderit organizatorëve të konferencës për mundësinë për të marrë pjesë në një ngjarje të tillë!
Postimi i organizatorëve rreth së kaluarës, së tashmes dhe së ardhmes së C++ Russia e keni parë në blogun JUG Ru.

Faleminderit që e lexuat, dhe shpresojmë se përmbledhja jonë e ngjarjeve ishte e dobishme!

Burimi: habr.com

Blini hosting tĂ« besueshĂ«m pĂ«r faqe interneti me mbrojtje nga DDoS, serverĂ« VPS VDS đŸ”„ Blini hosting tĂ« besueshĂ«m pĂ«r faqe interneti me mbrojtje nga DDoS, serverĂ« VPS VDS | ProHoster