Enige tijd geleden (in het najaar van 2016), bij het ontwikkelen van de volgende versie van het 1C:Enterprise technologieplatform, rees binnen het ontwikkelteam de vraag over het ondersteunen van de nieuwe standaard in onze code. De overgang naar de nieuwe standaard zou ons, zoals we aannamen, in staat stellen om veel dingen eleganter, eenvoudiger en betrouwbaarder te schrijven en de ondersteuning en het onderhoud van de code te vereenvoudigen. En er lijkt niets buitengewoons aan de vertaling te zijn, ware het niet dat de schaal van de codebasis en de specifieke kenmerken van onze code er niet waren.
Voor degenen die het niet weten: 1C:Enterprise is een omgeving voor snelle ontwikkeling van platformonafhankelijke bedrijfsapplicaties en runtime voor de uitvoering ervan in verschillende besturingssystemen en DBMS. In het algemeen omvat de samenstelling van het product:
- , werkt aan Windows и Linux
- , in samenwerking met de server via http(s) of via het eigen binaire protocol, werkt op Windows, Linux, macOS
- , dat werkt in Chrome, Internet Explorer, Microsoft Edge, Firefox, Safari-browsers (geschreven in JavaScript)
- Ontwikkelomgeving (), werkt aan Windows, Linux, macOS
- applicatieservers die draaien op Windows, Linux, macOS
- De verbinding met de server via http(s) werkt op mobiele apparaten die draaien op Android, IOS, Windows
- — een framework voor het creëren van offline mobiele applicaties met synchronisatiemogelijkheden, die draaien op Android, IOS, Windows
- Ontwikkelomgeving , geschreven in Java
- Server
We proberen zoveel mogelijk dezelfde code voor verschillende besturingssystemen te schrijven - de servercodebasis is voor 99% gemeenschappelijk, de client - voor ongeveer 95%. Technologisch platform 1C:Enterprise is voornamelijk geschreven in C++ en hieronder zijn de geschatte kenmerken van de code:
- 10 miljoen regels C++-code,
- 14 duizend bestanden,
- 60 duizend lessen,
- een half miljoen methodes.
En al deze economie moest worden vertaald in C ++ 14. Over hoe we het deden en wat we tegenkwamen in het proces, vertellen we vandaag.

Disclaimer
Alles wat hieronder geschreven wordt over langzaam/snel werken, (niet) groot geheugengebruik door implementaties van standaardklassen in diverse bibliotheken betekent één ding: dit geldt VOOR ONS. Het is heel goed mogelijk dat standaardimplementaties het beste bij uw taken passen. We gingen uit van onze taken: we namen de typische gegevens van onze klanten, voerden er typische scenario's op uit, keken naar de snelheid, de hoeveelheid verbruikt geheugen, enz., en analyseerden of wij en onze klanten tevreden waren met dergelijke resultaten of niet. En handelde afhankelijk van.
Wat we hadden
Aanvankelijk schreven we de platformcode voor 1C:Enterprise 8 in Microsoft Visual Studio. Het project begon begin jaren 2000 en we hadden toen slechts één versie voor WindowsUiteraard is de code sindsdien snel geëvolueerd en zijn veel mechanismen volledig herschreven. Maar de code is geschreven volgens de standaard van 1998, en we scheidden bijvoorbeeld rechte haakjes met spaties om een succesvolle compilatie te garanderen, zoals dit:
vector<vector<int> > IntV;In 2006, met de release van platformversie 8.1, zijn we begonnen met de ondersteuning van Linux en overgestapt op een standaardbibliotheek van een derde partij Een van de redenen voor de overstap was het werken met brede strings. In onze code gebruiken we overal std::wstring, dat gebaseerd is op het type wchar_t. De grootte ervan is Windows 2 bytes, en in Linux De standaardwaarde is 4 bytes. Dit leidde tot incompatibiliteit tussen onze binaire protocollen voor de client en de server, evenals met diverse persistente data. Met behulp van gcc-opties kun je de grootte van wchar_t tijdens de compilatie forceren naar 2 bytes, maar dan kun je de standaardbibliotheek van de compiler wel vergeten, aangezien deze glibc gebruikt, die op zijn beurt gecompileerd is voor een wchar_t van 4 bytes. Andere redenen waren een betere implementatie van standaardklassen, ondersteuning voor hashtabellen en zelfs emulatie van de semantiek van containerverplaatsing, die we actief gebruikten. En tot slot, zoals men zegt, was er de stringprestatie. We hadden onze eigen stringklasse, omdat stringbewerkingen veelvuldig in onze software worden gebruikt en dit cruciaal voor ons is.
Onze snaar is gebaseerd op de ideeën van snaaroptimalisatie die in de vroege jaren 2000 tot uiting kwamen . Later, toen Alexandrescu bij Facebook werkte, werd op zijn suggestie een string gebruikt in de Facebook-engine die volgens vergelijkbare principes werkt (zie de bibliotheek ).
Onze lijn gebruikte twee belangrijke optimalisatietechnologieën:
- Voor korte waarden wordt een interne buffer gebruikt in het stringobject zelf (geen extra geheugentoewijzing vereist).
- Voor alle anderen wordt mechanica gebruikt . De waarde van de string wordt op één plaats opgeslagen, de referentietelling wordt gebruikt bij het toewijzen/wijzigen.
Om de compilatie van het platform te versnellen, hebben we de implementatie van stream (die we niet gebruikten) uitgesloten van onze STLPort-variant, wat ons een compilatiesnelheid van ongeveer 20% opleverde. Vervolgens moesten we beperkt gebruiken . Boost maakt uitgebreid gebruik van stream, met name in zijn service-API's (bijvoorbeeld voor logging), dus moesten we het aanpassen om het gebruik van stream ervan uit te sluiten. Dit maakte het op zijn beurt weer moeilijk voor ons om naar nieuwe versies van Boost te migreren.
derde manier
Bij de overgang naar de C++14-standaard hebben we de volgende opties overwogen:
- Verhoog de door ons aangepaste STLPort naar de C++ 14 standaard. De optie is erg moeilijk, omdat. STLPort-ondersteuning werd stopgezet in 2010 en we zouden alle code zelf moeten verwijderen.
- Overstappen naar een andere STL-implementatie die compatibel is met C++14. Het is zeer wenselijk dat deze implementatie compatibel is met Windows и Linux.
- Gebruik bij het compileren voor elk besturingssysteem de bibliotheek die is ingebouwd in de overeenkomstige compiler.
De eerste optie werd direct afgewezen wegens te veel werk.
We hebben even nagedacht over de tweede optie; beschouwd als kandidaat maar op dat moment werkte hij niet onder WindowsOm libc++ te porteren naar WindowsWe zouden veel werk moeten verzetten - bijvoorbeeld alles met betrekking tot threads, threadsynchronisatie en atomiciteit zelf schrijven, aangezien libc++ dat niet deed. .
En we kozen voor de derde weg.
Overgang
Daarom moesten we het gebruik van STLPort vervangen door de bibliotheken van de bijbehorende compilers (Visual Studio 2015 voor Windows, gcc 7 voor Linux, clang 8 voor macOS).
Gelukkig was onze code grotendeels volgens de richtlijnen geschreven en maakten we geen gebruik van slimme trucjes, waardoor de migratie naar de nieuwe bibliotheken relatief soepel verliep. We gebruikten scripts die de namen van typen, klassen, namespaces en includes in de bronbestanden vervingen. De migratie betrof 10.000 bronbestanden (van de 14.000). wchar_t werd vervangen door char16_t; we hebben besloten om het gebruik van wchar_t te laten vallen, omdat char16_t op alle besturingssystemen 2 bytes in beslag neemt en de codecompatibiliteit niet beïnvloedt. Windows и Linux.
Er waren wat kleine avonturen. In STLPort kan bijvoorbeeld een iterator impliciet worden gecast naar een elementpointer, en dit werd op sommige plaatsen in onze code gebruikt. In de nieuwe bibliotheken was dit niet meer mogelijk en moesten deze passages handmatig worden geanalyseerd en herschreven.
Dus de codemigratie is voltooid, de code is gecompileerd voor alle besturingssystemen. Het is tijd voor testen.
Tests na de overgang toonden een prestatiedaling (op sommige plaatsen tot 20-30%) en een toename van het geheugenverbruik (tot 10-15%) in vergelijking met de oude versie van de code. Dit was met name te wijten aan de suboptimale prestaties van standaard snaren. Daarom moesten we opnieuw onze eigen lijn gebruiken, enigszins aangepast.
Een interessant kenmerk van de implementatie van containers in ingebedde bibliotheken werd ook onthuld: lege (zonder elementen) std::map en std::set van ingebouwde bibliotheken wijzen geheugen toe. En in ons land worden, vanwege de eigenaardigheden van de implementatie, op sommige plaatsen van de code nogal wat lege containers van dit type gemaakt. Ze wijzen standaard geheugencontainers een beetje toe, voor één root-element, maar voor ons bleek het van cruciaal belang te zijn - in een aantal scenario's daalden onze prestaties aanzienlijk en nam het geheugenverbruik toe (vergeleken met STLPort). Daarom hebben we in onze code deze twee soorten containers uit de ingebouwde bibliotheken vervangen door hun implementatie van Boost, waar deze containers deze functie niet hadden, en dit loste het probleem met vertraging en verhoogd geheugenverbruik op.
Zoals vaak gebeurt na grootschalige wijzigingen in grote projecten, verliep de eerste versie van de broncode niet zonder problemen, en hierbij met name de ondersteuning voor debug-iterators in Windows-implementatie. We gingen stap voor stap te werk en in het voorjaar van 2017 (versie 8.3.11 van 1C:Enterprise) was de migratie voltooid.
Resultaten van
De overgang naar de C++14-standaard kostte ons ongeveer 6 maanden. Meestal werkte één (maar zeer hooggekwalificeerde) ontwikkelaar aan het project en in de laatste fase voegden vertegenwoordigers van teams die verantwoordelijk waren voor specifieke gebieden zich bij - UI, servercluster, ontwikkelings- en beheertools, enz.
De overgang heeft ons werk bij het migreren naar de nieuwste versies van de standaard aanzienlijk vereenvoudigd. Versie 1C:Enterprise 8.3.14 (in ontwikkeling, de release staat gepland voor begin volgend jaar) is dus al overgezet naar de standaard .
Na de migratie hebben ontwikkelaars meer opties. Als we eerder onze eigen aangepaste versie van STL en één std-naamruimte hadden, hebben we nu in de std-naamruimte standaardklassen van de ingebouwde bibliotheken van de compiler, in de stdx-naamruimte - onze regels en containers geoptimaliseerd voor onze taken, in boost - een frisse versie van boost. En de ontwikkelaar gebruikt die klassen die het meest geschikt zijn om zijn problemen op te lossen.
De "native" implementatie van move constructors helpt ook bij de ontwikkeling () voor een aantal klassen. Als een klasse een verplaatsingsconstructor heeft en deze klasse in een container wordt geplaatst, optimaliseert de STL het kopiëren van elementen in de container (bijvoorbeeld wanneer de container uitbreidt en u de capaciteit moet wijzigen en geheugen opnieuw moet toewijzen).
Vlieg in de zalf
Misschien wel het meest onaangename (maar niet kritieke) gevolg van migratie is dat we te maken krijgen met een toename van het volume , en het volledige resultaat van de build met alle tussenliggende bestanden begon 60 - 70 GB in beslag te nemen. Dit gedrag is te wijten aan de eigenaardigheden van moderne standaardbibliotheken, die minder kritisch zijn geworden over het volume van gegenereerde servicebestanden. Dit heeft geen invloed op de werking van de gecompileerde applicatie, maar het veroorzaakt een aantal ongemakken bij de ontwikkeling, met name het verhoogt de compilatietijd. Er zijn ook verhoogde vereisten voor vrije schijfruimte op buildservers en ontwikkelmachines. Onze ontwikkelaars werken parallel aan verschillende versies van het platform en honderden gigabytes aan tussenliggende bestanden zorgen soms voor problemen bij het werk. Het probleem is onaangenaam, maar niet kritiek, we hebben de oplossing ervan voorlopig uitgesteld. Als een van de opties om het op te lossen, beschouwen we de techniek (het wordt met name door Google gebruikt bij de ontwikkeling van de Chrome-browser).
Bron: www.habr.com
