10 milyon satırlık C++ kodunu C++14 standardına (ve ardından C++17'ye) nasıl çevirdik?

Bir süre önce (2016 sonbaharında), 1C:Enterprise teknoloji platformunun bir sonraki versiyonunun geliştirilmesi sırasında, geliştirme ekibinde yeni standardın desteklenmesiyle ilgili bir soru ortaya çıktı. C ++ 14 bizim kodumuzda. Yeni bir standarda geçiş, varsaydığımız gibi, birçok şeyi daha zarif, basit ve güvenilir bir şekilde yazmamıza olanak tanıyacak ve kodun desteklenmesini ve bakımını basitleştirecektir. Kod tabanının ölçeği ve kodumuzun belirli özellikleri dışında, çeviride olağanüstü hiçbir şey yok gibi görünüyor.

Bilmeyenler için 1C:Enterprise, platformlar arası iş uygulamalarının hızlı bir şekilde geliştirilmesine ve bunların farklı işletim sistemleri ve DBMS'lerde çalıştırılmasına yönelik bir ortamdır. Genel anlamda ürün şunları içerir:

  • Uygulama Sunucusu Kümesi, Windows ve Linux'ta çalışır
  • Müşteri, sunucuyla http(s) veya kendi ikili protokolü aracılığıyla çalışır, Windows, Linux, macOS'ta çalışır
  • Web istemcisi, Chrome, Internet Explorer, Microsoft Edge, Firefox, Safari tarayıcılarında çalışır (JavaScript ile yazılmıştır)
  • Geliştirme ortamı (Yapılandırıcı), Windows, Linux, macOS'ta çalışır
  • Yönetim Araçları uygulama sunucuları, Windows, Linux, macOS üzerinde çalışır
  • Mobil istemci, sunucuya http(s) aracılığıyla bağlanıyor, Android, iOS, Windows çalıştıran mobil cihazlarda çalışıyor
  • Mobil platform — Android, iOS ve Windows'ta çalışan, senkronize etme özelliğine sahip çevrimdışı mobil uygulamalar oluşturmaya yönelik bir çerçeve
  • Geliştirme ortamı 1C:Kurumsal Geliştirme Araçları, Java ile yazılmış
  • Sunucu Etkileşim Sistemleri

Mümkün olduğunca farklı işletim sistemleri için aynı kodu yazmaya çalışıyoruz - sunucu kod tabanı %99 ortaktır, istemci kod tabanı ise yaklaşık %95'tir. 1C:Enterprise teknoloji platformu öncelikle C++ ile yazılmıştır ve yaklaşık kod özellikleri aşağıda verilmiştir:

  • 10 milyon satırlık C++ kodu,
  • 14 bin dosya,
  • 60 bin derslik,
  • yarım milyon yöntem.

Ve tüm bunların C++ 14'e çevrilmesi gerekiyordu. Bugün sizlere bunu nasıl yaptığımızı ve süreçte nelerle karşılaştığımızı anlatacağız.

10 milyon satırlık C++ kodunu C++14 standardına (ve ardından C++17'ye) nasıl çevirdik?

sorumluluk reddi

Yavaş/hızlı çalışma, çeşitli kütüphanelerdeki standart sınıfların uygulamalarıyla büyük bellek tüketimi (değil) hakkında aşağıda yazılan her şey tek bir anlama gelir: bu BİZİM İÇİN doğrudur. Standart uygulamaların görevleriniz için en uygun olması oldukça mümkündür. Kendi görevlerimizden başladık: Müşterilerimiz için tipik olan verileri aldık, bunlar üzerinde tipik senaryolar yürüttük, performansa, tüketilen bellek miktarına vb. baktık ve bizim ve müşterilerimizin bu sonuçlardan memnun olup olmadığını analiz ettik. . Ve buna bağlı olarak hareket ettiler.

Neye sahiptik

Başlangıçta 1C:Enterprise 8 platformunun kodunu Microsoft Visual Studio kullanarak yazdık. Proje 2000'li yılların başında başladı ve yalnızca Windows sürümüne sahiptik. Doğal olarak, o zamandan beri kod aktif olarak geliştirildi ve birçok mekanizma tamamen yeniden yazıldı. Ancak kod 1998 standardına göre yazılmıştı ve örneğin dik açılı ayraçlarımız derlemenin başarılı olması için boşluklarla ayrılmıştı, şu şekilde:

vector<vector<int> > IntV;

2006 yılında platform 8.1 sürümünün piyasaya sürülmesiyle Linux'u desteklemeye başladık ve üçüncü taraf standart kitaplığa geçtik STL Portu. Geçişin sebeplerinden biri de geniş çizgilerle çalışmaktı. Kodumuzda wchar_t tipini temel alan std::wstring'i kullanıyoruz. Windows'ta boyutu 2 bayttır ve Linux'ta varsayılan 4 bayttır. Bu, istemci ve sunucu arasındaki ikili protokollerimizin yanı sıra çeşitli kalıcı veriler arasında uyumsuzluğa yol açtı. Gcc seçeneklerini kullanarak, derleme sırasında wchar_t boyutunun da 2 bayt olduğunu belirtebilirsiniz, ancak daha sonra derleyiciden standart kitaplığı kullanmayı unutabilirsiniz, çünkü glibc'yi kullanır ve bu da 4 baytlık bir wchar_t için derlenir. Diğer nedenler ise standart sınıfların daha iyi uygulanması, hash tablolarının desteklenmesi ve hatta aktif olarak kullandığımız kapların içinde hareket etme semantiğinin emülasyonuydu. Ve en son söyledikleri gibi bir neden daha, yaylı performansıydı. Yaylılar için kendi sınıfımız vardı çünkü... Yazılımımızın özellikleri nedeniyle string işlemleri çok yaygın olarak kullanılmaktadır ve bu bizim için kritik öneme sahiptir.

Dizimiz 2000'li yılların başında ifade edilen dizi optimizasyon fikirlerine dayanmaktadır Andrey Alexandrescu. Daha sonra Alexandrescu Facebook'ta çalışırken onun önerisi üzerine Facebook motorunda benzer prensiplerle çalışan bir hat kullanıldı (bkz. çılgınlık).

Hattımızda iki ana optimizasyon teknolojisi kullanıldı:

  1. Kısa değerler için, dize nesnesinin kendisindeki dahili bir arabellek kullanılır (ek bellek tahsisi gerektirmez).
  2. Diğerleri için mekanikler kullanılır Yazarken Kopyala. Dize değeri tek bir yerde saklanır ve atama/değiştirme sırasında bir referans sayacı kullanılır.

Platform derlemesini hızlandırmak için akış uygulamasını STLPort varyantımızdan hariç tuttuk (ki bunu kullanmadık), bu bize yaklaşık %20 daha hızlı derleme sağladı. Daha sonra sınırlı kullanmak zorunda kaldık Artırmak. Boost, özellikle hizmet API'lerinde (örneğin günlük kaydı için) akışı yoğun şekilde kullanıyor, bu nedenle akışın kullanımını kaldırmak için onu değiştirmek zorunda kaldık. Bu da Boost'un yeni versiyonlarına geçmemizi zorlaştırdı.

Üçüncü Yol

C++14 standardına geçerken aşağıdaki seçenekleri göz önünde bulundurduk:

  1. Değiştirdiğimiz STLPort'u C++14 standardına yükseltin. Seçenek çok zor çünkü... STLPort desteği 2010 yılında durduruldu ve tüm kodunu kendimiz oluşturmak zorunda kaldık.
  2. C++14 ile uyumlu başka bir STL uygulamasına geçiş. Bu uygulamanın Windows ve Linux için olması oldukça arzu edilir.
  3. Her işletim sistemi için derleme yaparken, ilgili derleyicide yerleşik olarak bulunan kitaplığı kullanın.

İlk seçenek çok fazla çalışma nedeniyle tamamen reddedildi.

Bir süre ikinci seçeneği düşündük; aday olarak değerlendiriliyor libc++, ancak o zamanlar Windows altında çalışmıyordu. Libc++'ı Windows'a taşımak için çok fazla iş yapmanız gerekir; örneğin, bu alanlarda libc++ kullanıldığından, iş parçacıkları, iş parçacığı senkronizasyonu ve atomiklik ile ilgili her şeyi kendiniz yazmanız gerekir. POSIX API'si.

Biz de üçüncü yolu seçtik.

geçiş

Bu nedenle, STLPort kullanımını ilgili derleyicilerin kitaplıklarıyla değiştirmek zorunda kaldık (Windows için Visual Studio 2015, Linux için gcc 7, macOS için clang 8).

Neyse ki, kodumuz temel olarak yönergelere göre yazılmıştı ve her türlü zekice numarayı kullanmıyordu; bu nedenle, kaynaktaki türlerin, sınıfların, ad alanlarının ve içeriklerin adlarının yerini alan komut dosyalarının yardımıyla yeni kitaplıklara geçiş nispeten sorunsuz bir şekilde ilerledi. Dosyalar. Geçiş, 10 kaynak dosyadan 000'ini etkiledi. wchar_t'nin yerini char14_t aldı; wchar_t kullanımını bırakmaya karar verdik çünkü char000_t tüm işletim sistemlerinde 16 bayt alır ve Windows ile Linux arasındaki kod uyumluluğunu bozmaz.

Küçük maceralar yaşandı. Örneğin, STLPort'ta bir yineleyici örtülü olarak bir öğeye yönelik bir işaretçiye aktarılabilir ve kodumuzun bazı yerlerinde bu kullanıldı. Yeni kütüphanelerde bunu yapmak artık mümkün değildi ve bu pasajların manuel olarak analiz edilmesi ve yeniden yazılması gerekiyordu.

Böylece kod geçişi tamamlandı, kod tüm işletim sistemleri için derlendi. Testlerin zamanı geldi.

Geçişten sonraki testler, kodun eski sürümüne kıyasla performansta bir düşüş (%20-30'a kadar) ve bellek tüketiminde (%10-15'e kadar) bir artış gösterdi. Bunun nedeni özellikle standart dizilerin optimumun altındaki performansıydı. Bu nedenle yine biraz değiştirilmiş kendi hattımızı kullanmak zorunda kaldık.

Gömülü kitaplıklarda kapların uygulanmasının ilginç bir özelliği de ortaya çıktı: boş (öğeler olmadan) std::map ve yerleşik kitaplıklardan std::set bellek ayırır. Ve uygulama özellikleri nedeniyle kodun bazı yerlerinde bu türden oldukça fazla boş kap yaratılıyor. Standart bellek kapları bir kök öğe için biraz tahsis edilmiştir, ancak bunun bizim için kritik olduğu ortaya çıktı - bazı senaryolarda performansımız önemli ölçüde düştü ve bellek tüketimi arttı (STLPort'a kıyasla). Bu nedenle kodumuzda, yerleşik kütüphanelerdeki bu iki konteyner tipini, bu konteynerlerin bu özelliğe sahip olmadığı Boost'taki uygulamalarıyla değiştirdik ve bu, yavaşlama ve artan bellek tüketimi sorununu çözdü.

Büyük projelerdeki büyük ölçekli değişikliklerden sonra sıklıkla olduğu gibi, kaynak kodunun ilk yinelemesi sorunsuz çalışmadı ve burada, özellikle Windows uygulamasındaki yineleyicilerin hata ayıklama desteği işe yaradı. Adım adım ilerledik ve 2017 baharında (sürüm 8.3.11 1C:Enterprise) geçiş tamamlandı.

sonuçlar

C++14 standardına geçişimiz yaklaşık 6 ay sürdü. Çoğu zaman projede bir (ama çok nitelikli) geliştirici çalıştı ve son aşamada kullanıcı arayüzü, sunucu kümesi, geliştirme ve yönetim araçları vb. gibi belirli alanlardan sorumlu ekiplerin temsilcileri katıldı.

Geçiş, standardın en son sürümlerine geçiş çalışmalarımızı büyük ölçüde basitleştirdi. Böylece, 1C:Enterprise 8.3.14 sürümü (geliştirilme aşamasında, gelecek yılın başında yayınlanması planlanıyor) halihazırda standarda aktarılmış durumda. C++17.

Geçişten sonra geliştiricilerin daha fazla seçeneği vardır. Daha önce kendi değiştirilmiş STL sürümümüz ve bir std ad alanımız olsaydı, şimdi std ad alanında, stdx ad alanında yerleşik derleyici kitaplıklarından standart sınıflarımız var - görevlerimiz için optimize edilmiş satırlarımız ve kapsayıcılarımız, destek olarak - Boost'un son sürümü. Ve geliştirici, problemlerini çözmek için en uygun sınıfları kullanır.

Move kurucularının “yerel” uygulanması da geliştirmeye yardımcı olur (yapıcıları taşı) birkaç sınıf için. Bir sınıfın taşıma yapıcısı varsa ve bu sınıf bir konteynere yerleştirildiyse, STL konteynerin içindeki öğelerin kopyalanmasını optimize eder (örneğin, konteyner genişletildiğinde ve kapasiteyi değiştirmek ve belleği yeniden tahsis etmek gerektiğinde).

Merhem olarak Fly

Belki de göçün en rahatsız edici (ancak kritik olmayan) sonucu, hacimde bir artışla karşı karşıya kalmamızdır. obj dosyalarıve tüm ara dosyalar ile derlemenin tam sonucu 60-70 GB yer kaplamaya başladı. Bu davranış, oluşturulan hizmet dosyalarının boyutu açısından daha az kritik hale gelen modern standart kitaplıkların özelliklerinden kaynaklanmaktadır. Bu, derlenen uygulamanın çalışmasını etkilemez, ancak geliştirmede bir takım sıkıntılara neden olur, özellikle derleme süresini artırır. Derleme sunucuları ve geliştirici makinelerindeki boş disk alanı gereksinimleri de artıyor. Geliştiricilerimiz platformun çeşitli versiyonları üzerinde paralel olarak çalışıyor ve yüzlerce gigabaytlık ara dosya bazen çalışmalarında zorluk yaratıyor. Sorun hoş değil ama kritik değil; çözümünü şimdilik erteledik. Teknolojiyi bu sorunu çözme seçeneklerinden biri olarak görüyoruz birlik inşası (özellikle Google bunu Chrome tarayıcısını geliştirirken kullanır).

Kaynak: habr.com

Yorum ekle