Een tijdje geleden (in de herfst van 2016) ontstond binnen ons ontwikkelingsteam de vraag over de ondersteuning van de nieuwe standaard in onze code. De overstap naar de nieuwe standaard, zoals we aannamen, zou ons in staat stellen om veel dingen eleganter, eenvoudiger en betrouwbaarder te schrijven, en het zou de ondersteuning en het onderhoud van de code vereenvoudigen. En in de vertaling lijkt er niets bijzonders te zijn, ware het niet voor de omvang van de codebase en de specifieke kenmerken van onze code.
Voor degenen die het niet weten, 1C:Enterprise is een omgeving voor snelle ontwikkeling van cross-platform bedrijfsapplicaties en runtime voor hun uitvoering op verschillende besturingssystemen en databases. In algemene termen bestaat het product uit:
- , werkt op Windows en Linux
- , werkt met de server via http(s) of een eigen binair protocol, werkt op Windows, Linux, macOS
- , draait in browsers zoals Chrome, Internet Explorer, Microsoft Edge, Firefox, Safari (geschreven in JavaScript)
- Ontwikkelomgeving (), werkt op Windows, Linux, macOS
- voor applicatieservers, werkt op Windows, Linux, macOS
- , verbindt met de server via http(s), werkt op mobiele apparaten met Android, iOS, Windows
- ā framework voor het creĆ«ren van offline mobiele applicaties met synchronisatiemogelijkheden, werkend op Android, iOS, Windows
- Ontwikkelomgeving , geschreven in Java
- Server
We proberen zoveel mogelijk ƩƩn code te schrijven voor verschillende besturingssystemen ā de codebase van de server is voor 99% gemeenschappelijk, die van de client voor ongeveer 95%. Het technologische platform 1C:Enterprise is voornamelijk geschreven in C++ en hieronder staan de geschatte kenmerken van de code:
- 10 miljoen regels C++ code,
- 14 duizend bestanden,
- 60 duizend klassen,
- een half miljoen methoden.
En dat moest allemaal worden overgezet naar C++14. Hoe we dat gedaan hebben en met welke uitdagingen we geconfronteerd werden in het proces, zullen we vandaag bespreken.

Disclaimer
Alles wat hieronder staat over langzame/snelle prestaties, (niet) veel geheugenverbruik door standaard klasimplementaties in verschillende bibliotheken betekent ƩƩn ding: dit geldt VOOR ONS. Het is goed mogelijk dat de standaardimplementaties het beste passen bij uw taken. Wij zijn echter uitgegaan van onze eigen taken: we hebben typische gegevens van onze klanten genomen, draaien typische scenario's ermee, bekeken de prestaties, het geheugenverbruik, enzovoort, en hebben geanalyseerd of ons en onze klanten deze resultaten bevallen of niet. En we hebben gehandeld afhankelijk van.
Wat we hadden
Aanvankelijk schreven we de code voor het 1C:Enterprise 8-platform in Microsoft Visual Studio. Het project begon begin jaren 2000 en we hadden alleen een versie voor Windows. Natuurlijk is de code sinds die tijd actief ontwikkeld; veel mechanismen zijn volledig herschreven. Maar de code was geschreven volgens de standaard van 1998, en bijvoorbeeld, de rechterhoekige haakjes waren gescheiden door spaties, zodat de compilatie succesvol kon plaatsvinden, zo:
vector<vector> IntV;In 2006, met de release van versie 8.1 van het platform, begonnen we ook Linux te ondersteunen en overstegen we naar een externe standaardbibliotheek . Een van de redenen voor de overstap was het werken met brede strings. In onze code gebruiken we overal std::wstring, dat is gebaseerd op het type wchar_t. De grootte ervan in Windows is 2 bytes, terwijl het in Linux standaard 4 bytes is. Dit leidde tot incompatibiliteit van onze binaire protocollen tussen client en server, evenals verschillende persistente gegevens. Met gcc-opties kan worden aangegeven dat de grootte van wchar_t bij compilatie ook 2 bytes moet zijn, maar dan kun je de standaardbibliotheek van de compiler vergeten, omdat deze glibc gebruikt, dat op zijn beurt is gecompileerd voor 4-byte wchar_t. Andere redenen waren een betere implementatie van de standaardklassen, ondersteuning voor hashtabellen en zelfs simulatie van verplaatsingssemantiek binnen containers, waarvan we veel gebruik hebben gemaakt. En nog een reden, zoals men zegt last but not least, was de prestaties van strings. We hadden onze eigen klasse voor strings, omdat string-operaties door de aard van onze software zeer breed worden gebruikt en voor ons cruciaal zijn.
Onze string is gebaseerd op ideeƫn voor stringoptimalisatie, die al aan het begin van de jaren 2000 zijn geuit . Later, when Alexandrescu was working at Facebook, a string operating on similar principles was implemented in the Facebook engine (see the library ).
In our string, we used two main optimization technologies:
- For short values, an internal buffer is utilized in the string object itself (not requiring additional memory allocation).
- For all other cases, the mechanism is . The string value is stored in one place, and when assigned/modified, a reference count is used.
To speed up the platform compilation, we excluded the stream implementation from our version of STLPort (which we did not use), resulting in approximately a 20% increase in compilation speed. Subsequently, we had to very limitedly use . Boost actively uses stream, particularly in its service APIs (for example, for logging), so we had to modify it by excluding the usage of stream. This, in turn, complicated our transition to newer versions of Boost.
The third option
When transitioning to the C++14 standard, we considered the following options:
- Raising our modified STLPort to the C++14 standard. This option is very complicated, as support for STLPort was discontinued in 2010, and we would have to raise all its code ourselves.
- Switching to another STL implementation compatible with C++14. It is highly desirable that this implementation works on both Windows and Linux.
- Using the library built into the respective compiler for compilation under each OS.
The first option was immediately rejected due to the excessive amount of work involved.
We considered the second option for some time; we looked at candidates like , but at that time it did not work on Windows. Porting libc++ to Windows would involve quite a bit of work ā for example, having to write everything related to threads, thread synchronization, and atomicity ourselves, since libc++ used .
And we chose the third option.
Transition
So, we needed to replace the use of STLPort with the libraries of the respective compilers (Visual Studio 2015 for Windows, gcc 7 for Linux, clang 8 for macOS).
Gelukkig werd onze code voornamelijk volgens de richtlijnen geschreven en maakte het gebruik van geen ingewikkelde trucs, waardoor de migratie naar de nieuwe bibliotheken relatief soepel verliep met behulp van scripts die in de oorspronkelijke bestanden typenamen, klassen, namespaces en includes vervingen. De migratie betrof 10.000 oorspronkelijke bestanden (van de 14.000). wchar_t werd vervangen door char16_t; we besloten om wchar_t niet meer te gebruiken, omdat char16_t op alle besturingssystemen 2 bytes in beslag neemt en de compatibiliteit van de code tussen Windows en Linux niet aantast.
Er waren echter enige kleine avonturen. Bijvoorbeeld, in STLPort kon een iterator impliciet worden gecast naar een pointer naar een element, en op sommige plaatsen in onze code werd dit gebruikt. In de nieuwe bibliotheken was dit niet meer mogelijk, en deze plaatsen moesten handmatig worden geanalyseerd en herschreven.
Dus, de code-migratie is voltooid, de code compileert nu voor alle besturingssystemen. Het is tijd voor tests.
De tests na de overgang toonden een daling van de prestaties (op sommige plaatsen tot 20-30%) en een toename van het geheugengebruik (tot 10-15%) in vergelijking met de oude versie van de code. Dit was onder andere gerelateerd aan de suboptimale werking van de standaard strings. Daarom moesten we onze eigen, iets aangepaste string weer gebruiken.
Ook kwam er een interessante eigenschap van de implementatie van containers in de ingebedde bibliotheken naar voren: lege (zonder elementen) std::map en std::set uit de ingebedde bibliotheken alloceerden geheugen. En door de specifieke implementatie werden er op bepaalde plaatsen in de code behoorlijk veel lege containers van dit type aangemaakt. Standaardcontainers alloceren iets geheugen, voor ƩƩn wortelelement, maar voor ons bleek dit kritisch te zijn - in een aantal scenario's daalde onze prestatie merkbaar en nam het geheugengebruik toe (in vergelijking met STLPort). Daarom vervingen we in onze code deze twee soorten containers uit de ingebedde bibliotheken door hun implementatie van Boost, waar deze containers deze eigenschap niet hadden, en dit loste het probleem met vertraging en verhoogd geheugengebruik op.
Zoals vaak het geval is na omvangrijke wijzigingen in grote projecten, werkte de eerste iteratie van de source code niet zonder problemen, en hierbij was de ondersteuning voor debugging iterators in de Windows-implementatie bijzonder nuttig. Stap voor stap gingen we vooruit, en tegen de lente van 2017 (versie 8.3.11 1C:Enterprise) was de migratie voltooid.
Conclusies
De overstap naar de standaard C++14 heeft ons ongeveer 6 maanden gekost. Het grootste deel van de tijd werkte ƩƩn (maar zeer hooggekwalificeerde) ontwikkelaar alleen aan het project, terwijl in de finale fase vertegenwoordigers van de teams die verantwoordelijk zijn voor specifieke gebieden ā UI, cluster servers, ontwikkelings- en beheertools, enz. ā zich aansloten.
De overstap heeft ons werk aan de migratie naar de nieuwste versies van de standaard sterk vereenvoudigd. Zo is versie 1C:Enterprise 8.3.14 (in ontwikkeling, de release is gepland voor begin volgend jaar) al omgezet naar de standaard. .
Na de migratie hebben ontwikkelaars meer mogelijkheden. Vroeger hadden we onze aangepaste versie van STL en ƩƩn namespace std, maar nu bevinden de standaard klassen uit de ingebouwde bibliotheken van de compiler zich in namespace std, onze geoptimaliseerde strings en containers voor onze taken in namespace stdx, en de nieuwste versie van boost in boost. De ontwikkelaar gebruikt die klassen die het beste passen bij zijn taken.
Ook de "native" implementatie van move constructors helpt bij de ontwikkeling () voor een aantal klassen. Als een klasse een move constructor heeft en deze klasse in een container geplaatst wordt, optimaliseert STL het kopiƫren van elementen binnen de container (bijvoorbeeld wanneer de container wordt vergroot en de capaciteit moet worden gewijzigd en er geheugen opnieuw moet worden toegewezen).
De lepel teer
Het misschien meest vervelende (maar niet kritieke) gevolg van de migratie is dat we te maken kregen met een toename van de omvang van , en het volledige resultaat van de build met alle tussenbestanden is nu 60 ā 70 Gb groot. Dit gedrag hangt samen met de kenmerken van moderne standaardbibliotheken die minder kritisch zijn geworden over de hoeveelheid gegenereerde hulpprogrammabestanden. Dit heeft geen invloed op de werking van de gecompileerde applicatie, maar zorgt voor een aantal ongemakken in de ontwikkeling, vooral door de langere compilatietijd. Ook de eisen voor vrije schijfruimte op buildservers en op de machines van ontwikkelaars nemen toe. Onze ontwikkelaars werken parallel aan meerdere versies van het platform, en honderden gigabytes aan tussenbestanden kunnen soms problemen opleveren. Het probleem is vervelend, maar niet kritisch, we hebben de oplossing vooralsnog uitgesteld. Als een van de mogelijke oplossingen bekijken we de techniek van (het wordt onder andere door Google gebruikt bij de ontwikkeling van de Chrome-browser).
Bron: habr.com
