compilation - لماذا وقت الترجمة سويفت بطيء جدا؟




swift (14)

أنا أستخدم Xcode 6 Beta 6.

هذا شيء تم التنصت لي منذ بعض الوقت ، لكنه يصل إلى نقطة يصبح من الصعب استخدامها الآن.

بدأ مشروعي في الحصول على حجم لائق من 65 ملف Swift وبعض الملفات Objective-C الجسر (وهي في الحقيقة ليست سبب المشكلة).

يبدو أن أي تعديل طفيف لأي ملف Swift (مثل إضافة مسافة بيضاء بسيطة في فئة لا تستخدم بالكاد في التطبيق) سيؤدي إلى إعادة تصنيف ملفات Swift بأكملها للهدف المحدد.

بعد تحقيق أعمق ، وجدت أن ما يأخذ 100٪ من وقت المجمع هو مرحلة CompileSwift حيث يقوم Xcode بتشغيل الأمر swiftc على كل ملفات Swift الخاصة بك.

لقد أجريت بعض التحقيقات الإضافية ، وإذا كنت أحتفظ فقط بتفويض التطبيق بجهاز تحكم افتراضي ، فإن التجميع سريع جدًا ، ولكن أثناء إضافة المزيد والمزيد من ملفات مشروعي ، بدأ وقت الترجمة بطيئًا جدًا.

الآن مع ملفات المصدر 65 فقط ، يستغرق حوالي 8/10 ثانية لتجميع كل مرة. ليس سريع جدا على الإطلاق.

لم أر أي مشاركة تتحدث عن هذه المسألة باستثناء هذه الرسالة ، ولكنها كانت نسخة قديمة من Xcode 6. لذا أنا أتساءل عما إذا كنت أنا الوحيد في هذه الحالة.

تحديث

لقد تحققت من بعض مشاريع Swift على GitHub مثل Alamofire و Euler و CryptoSwift ، ولكن لم يكن لدى أي منها ملفات Swift كافية للمقارنة في الواقع. كان المشروع الوحيد الذي وجدته هو البدء بالحجم اللائق كان SwiftHN ، وعلى الرغم من أنه كان يحتوي على 12 ملفًا مصدرًا ، إلا أنني كنت لا أزال قادرًا على التحقق من نفس الشيء ، SwiftHN البسيطة والمشروع بأكمله يحتاجان إلى إعادة التوليف الذي بدأ في القليل من الوقت (2/3 ثانية).

بالمقارنة مع كود Objective-C حيث يكون كل من المحلل والتجميع متقنين بسرعة ، فإن هذا يبدو حقاً مثل Swift لن يكون قادراً على التعامل مع المشاريع الكبيرة ، لكن من فضلك أخبرني بأنني مخطئ.

تحديث مع Xcode 6 بيتا 7

لا يوجد أي تحسن على الإطلاق. هذا هو البدء في الحصول على سخيفة. مع عدم وجود #import في Swift ، لا أرى حقاً كيف ستتمكن Apple من تحسين ذلك.

تحديث مع Xcode 6.3 و سويفت 1.2

أضافت شركة Apple تصنيعات متزايدة (والعديد من تحسينات الترجمة الأخرى). يجب عليك ترحيل رمزك إلى Swift 1.2 لمشاهدة هذه الفوائد ، ولكن Apple أضافت أداة في Xcode 6.3 لمساعدتك على القيام بذلك:

ومع ذلك

لا تفرح بسرعة كما فعلت. لم يحسن بشكل كبير للغاية الرسم البياني الذي يستخدمونه لجعل البناء المتزايد بشكل جيد.

في الواقع ، لا ينظر أولاً إلى تغييرات التوقيع الوظيفي ، لذا إذا قمت بإضافة مسافة في كتلة طريقة واحدة ، فسيتم إعادة ترجمة جميع الملفات المعتمدة على هذه الفئة.

ثانيًا ، يبدو أنه ينشئ الشجرة استنادًا إلى الملفات التي تم إعادة تصنيفها حتى لو لم يؤثر التغيير عليها. على سبيل المثال ، إذا قمت بنقل هذه الفئات الثلاث إلى ملفات مختلفة

class FileA: NSObject {
    var foo:String?
}
class FileB: NSObject {
    var bar:FileA?
}
class FileC: NSObject {
    var baz:FileB?
}

الآن إذا قمت بتعديل FileA ، سيقوم المترجم بوضوح بتمييز FileA ليتم recompiled. سيتم أيضاً إعادة ترجمة FileB (التي يمكن أن تكون موافق استناداً إلى التغييرات إلى FileA ) ، ولكن أيضاً FileC لأن FileB هو recompiled ، وهذا سيء جداً لأن FileC يستخدم أبداً FileC هنا.

لذا آمل أن يحسنوا هذه الشجرة التبعية... لقد فتحت radar مع هذا الكود.

تحديث مع Xcode 7 beta 5 و Swift 2.0

أصدرت شركة Apple الإصدار التجريبي 5 وداخل ملاحظات الإصدار التي يمكن أن نراها:

اللغة والمترجم السويفت • البنيات الزائدة: يجب ألا يتسبب تغيير هيئة دالة ما في إعادة إنشاء الملفات التابعة. (15352929)

لقد قدمت لها محاولة ويجب أن أقول أنها تعمل حقا (حقا!) جيدا الآن. أنها إلى حد كبير الأمثل للبناء المتزايد في سريع.

أنا أوصي بإنشاء فرع swift2.0 وإبقاء الكود محدثًا باستخدام XCode 7 beta 5. سيكون من دواعي swift2.0 تحسينات المترجم (ومع ذلك ، أود أن أقول أن الحالة العالمية لـ XCode 7 لا تزال بطيئة وعربات التي تجرها الدواب )

تحديث مع Xcode 8.2

لقد مرّ بعض الوقت منذ آخر تحديث في هذه القضية لذا ها هي.

لدينا التطبيق الآن حوالي 20 كيلو خطوط من رمز سويفت بشكل حصري تقريبًا ، وهو أمر لائق ولكن ليس ممتازًا. انها خضعت سريعة 2 و 3 من الترحيل السريع. يستغرق حوالي 5 / 6m لتجميع في منتصف عام 2014 ماك بوك برو (2.5 غيغاهرتز إنتل كور i7) وهو ما يرام على بناء نظيفة.

ومع ذلك ، فإن البناء الإضافي لا يزال مزحة رغم مطالبة شركة Apple بما يلي:

لن يعيد Xcode إنشاء هدف بأكمله عند حدوث تغييرات صغيرة فقط. (28892475)

من الواضح أنني أعتقد أن الكثيرين منا ضحكوا فقط بعد التحقق من هذا الهراء (إضافة خاصية خاصة (خاصة!) إلى أي ملف من مشروعي سيعيد تجميع كل شيء ...)

أود أن أشير يا رفاق إلى هذا الموضوع على منتديات مطوري أبل التي لديها بعض المعلومات حول هذه المسألة (بالإضافة إلى تقدير Apple dev communication حول هذه المسألة من حين لآخر)

لقد توصل الناس في الأساس إلى بعض الأشياء في محاولة لتحسين البنية المتزايدة:

  1. إضافة إعداد مشروع HEADER_MAP_USES_VFS إلى true
  2. تعطيل Find implicit dependencies من نظامك
  3. قم بإنشاء مشروع جديد ونقل تسلسلات ملفاتك إلى الهيكل الجديد.

سأحاول الحل 3 ولكن لم يعمل الحل 1/2 بالنسبة لنا.

ما هو مثير للسخرية في هذا الوضع برمته هو أن النظر إلى أول مشاركة في هذه القضية كنا نستخدم Xcode 6 مع إعتقادي بسرعة 1 أو رمز سريع 1،1 عندما وصلنا إلى أول عمليات تكاسل التجميع والآن بعد حوالي عامين على الرغم من التحسينات الفعلية من أبل الوضع هو بنفس السوء كما كان مع Xcode 6. كيف السخرية.

أنا في الواقع نأسف حقا لاختيار سويفت على Obj / C لمشروعنا بسبب الإحباط اليومي الذي ينطوي عليه. (حتى أتحول إلى AppCode ولكن هذه قصة أخرى)

على أي حال أرى هذا المنشور حتى 32k + وجهات النظر و 143 من هذه الكتابة حتى أظن أنني لست الوحيد. شنق هناك الرجال على الرغم من كونهم متشائمين بشأن هذا الوضع قد يكون هناك بعض الضوء في نهاية النفق.

إذا كان لديك الوقت (والشجاعة!) أعتقد أن أبل ترحب بالرادار حول هذا الأمر.

سمسم في المرة القادمة! في صحتك

تحديث مع Xcode 9

تعثر على this اليوم. قدمت Xcode نظام بناء جديدًا بهدوء لتحسين الأداء المرعب الحالي. يجب عليك تمكينه من خلال إعدادات مساحة العمل.

لقد أجريت تجربة حتى الآن ، ولكن سيتم تحديث هذه المشاركة بعد انتهائها. تبدو واعدة رغم ذلك.


ربما لا يمكننا إصلاح برنامج Quick Swift ، ولكن هناك شيء يمكننا إصلاحه هو رمزنا!

يوجد خيار مخفي في برنامج التحويل البرمجي Swift يقوم بطباعة الفترات الزمنية الدقيقة التي يأخذها المترجم لتجميع كل وظيفة مفردة: - -Xfrontend -debug-time-function-bodies . يتيح لنا العثور على اختناقات في التعليمات البرمجية وتحسين وقت الترجمة بشكل كبير.

تشغيل بسيط التالية في المحطة وتحليل النتائج:

xcodebuild -workspace App.xcworkspace -scheme App clean build OTHER_SWIFT_FLAGS="-Xfrontend -debug-time-function-bodies" | grep [1-9].[0-9]ms | sort -nr > culprits.txt

رهيبة بريان Irace كتب مقالة رائعة حول ذلك.


إذا كنت تحاول تحديد ملفات معينة تعمل على إبطاء وقت التحويل البرمجي الخاص بك ، فيمكنك محاولة تجميعها من سطر الأوامر الخاص بك عبر xctool والذي سيعطيك أوقاتاً للملفات حسب الملف.

والشيء الذي يجب ملاحظته هو أنه ، بشكل افتراضي ، يبني ملفين بشكل متزامن لكل وحدة معالجة مركزية مركزية ، ولن يعطيك الوقت المنقضي "net" ، ولكن الوقت "المطلق" المطلق. بهذه الطريقة كل الوقت حتى خارج بين ملفات متوازية وتبدو متشابهة جدا.

للتغلب على هذا ، قم بتعيين علامة -jobs على 1 ، بحيث لا يقوم بموازاة إنشاء الملف. سوف يستغرق الأمر وقتاً أطول ، ولكن في النهاية سيكون لديك أوقات تجميع "net" بحيث يمكنك مقارنة الملف حسب الملف.

هذا أمر مثال يجب أن يفعل الحيلة:

xctool -workspace <your_workspace> -scheme <your_scheme> -jobs 1 build

سيكون ناتج مرحلة "Compile Swift files" مثل:

...
   ✓ Compile EntityObserver.swift (1623 ms)
   ✓ Compile Session.swift (1526 ms)
   ✓ Compile SearchComposer.swift (1556 ms)
...

من خلال هذا الإخراج ، يمكنك تحديد الملفات التي تستغرق وقتًا أطول من غيرها. علاوة على ذلك ، يمكنك تحديد بدقة عالية ما إذا كانت إعادة الإنتاج الخاصة بك (الصيحات الظاهرة ، تلميحات الكتابة ، إلخ ...) تخفض أوقات التجميع لملفات محددة أم لا.

ملاحظة: من الناحية الفنية يمكن أن تفعل ذلك أيضا مع xcodebuild ولكن الإخراج هو مطول لا يصدق ويصعب استهلاكها.


الحل هو الصب.

كان لدي مجموعة كبيرة من أطنان القواميس ، مثل:

["title" : "someTitle", "textFile" : "someTextFile"],
["title" : "someTitle", "textFile" : "someTextFile"],
["title" : "someTitle", "textFile" : "someTextFile"],
["title" : "someTitle", "textFile" : "someTextFile"],
.....

استغرق الأمر حوالي 40 دقيقة لتجميعها. حتى ألقيت القواميس كما يلي:

["title" : "someTitle", "textFile" : "someTextFile"] as [String : String],
["title" : "someTitle", "textFile" : "someTextFile"] as [String : String],
["title" : "someTitle", "textFile" : "someTextFile"] as [String : String],
....

لقد نجح هذا الأمر في حل كل مشكلة أخرى واجهتها في ما يتعلق بأنواع البيانات التي قمت بتشفيرها في تطبيقي.


بالنسبة إلى Xcode 8 ، انتقل إلى إعدادات المشروع ، ثم المحرر> إضافة إعداد إعداد> إضافة إعداد معرّف من قبل المستخدم ، ثم أضف ما يلي:

SWIFT_WHOLE_MODULE_OPTIMIZATION = YES

أدت إضافة هذه العلامة إلى تقليل أوقات الترجمة النظيفة الخاصة بنا من 7 دقائق إلى 65 ثانية لمشروع 40KLOC السريع ، بأعجوبة. كما يمكن أن يؤكد 2 أصدقاء شهدوا تحسينات مماثلة على مشاريع المشاريع.

يمكنني فقط أن أفترض أن هذا نوع من الأخطاء في Xcode 8.0

تعديل: يبدو أنه لم يعد يعمل في Xcode 8.3 لبعض الأشخاص.


بما أن كل هذه الأشياء موجودة في Beta ، وبما أن مترجم Swift (على الأقل اعتبارًا من اليوم) ليس مفتوحًا ، أعتقد أنه لا توجد إجابة حقيقية على سؤالك.

بادئ ذي بدء ، فإن مقارنة Objective-C إلى مترجم Swift هي بطريقة قاسية. لا يزال Swift في Beta ، وأنا متأكد من أن Apple تعمل على توفير الوظائف وإصلاح الأخطاء ، أكثر من توفير سرعة البرق (لا تبدأ في بناء منزل بشراء الأثاث). أعتقد أن أبل ستقوم بتحسين المترجم في الوقت المناسب.

إذا كان لابد من تجميع جميع ملفات المصدر لسبب ما ، فقد يكون خيار إنشاء وحدات / مكتبات منفصلة. لكن هذا الخيار غير ممكن بعد ، حيث لا يمكن لـ Swift السماح للمكتبات حتى تكون اللغة مستقرة.

تخميني هو أنهم سيحسنون المترجم. وللسبب نفسه الذي لا يمكننا من إنشاء وحدات تم تجميعها مسبقًا ، فقد يكون من الضروري أن يقوم المترجم بتجميع كل شيء من الصفر. ولكن بمجرد وصول اللغة إلى إصدار مستقر ولم يتغير شكل الثنائيات بعد الآن ، سنكون قادرين على إنشاء مكتباتنا ، وربما (؟) سيكون المترجم قادراً أيضًا على تحسين عمله.

مجرد التخمين ، على الرغم من ذلك ، فقط لأبل يعرف ...


تأكد أيضًا من أنه عند التحويل البرمجي لـ debug (إما Swift أو Objective-C) ، يمكنك التعيين على إنشاء البنية النشطة فقط:


شيء واحد هو أن نلاحظ أن محرك الاستدلال نوع سويفت يمكن أن تكون بطيئة جدا مع أنواع متداخلة. يمكنك الحصول على فكرة عامة حول سبب التباطؤ من خلال مشاهدة سجل الإنشاء لوحدات التجميع الفردية التي تستغرق وقتًا طويلاً ثم نسخ ولصق أمر Xcode-spawned الكامل إلى نافذة طرفية ثم الضغط على CTRL- \ للحصول على بعض التشخيص. ألق نظرة على http://blog.impathic.com/post/99647568844/debugging-slow-swift-compile-times للحصول على مثال كامل.


في حالتي ، لم Xcode 7 فرقا على الإطلاق. كان لدي وظائف متعددة تتطلب عدة ثوان لتجميع.

مثال

// Build time: 5238.3ms
return CGSize(width: size.width + (rightView?.bounds.width ?? 0) + (leftView?.bounds.width ?? 0) + 22, height: bounds.height)

بعد إلغاء الاختيارات ، انخفض وقت الإنشاء بنسبة 99.4٪ .

// Build time: 32.4ms
var padding: CGFloat = 22
if let rightView = rightView {
    padding += rightView.bounds.width
}

if let leftView = leftView {
    padding += leftView.bounds.width
}
return CGSizeMake(size.width + padding, bounds.height)

انظر المزيد من الأمثلة في هذا المنصب وهذا المنصب .

محلل وقت البناء ل Xcode

لقد قمت بتطوير مكون Xcode الإضافي الذي قد يكون مفيدًا لأي شخص يواجه هذه المشكلات.

يبدو أن هناك تحسينات قادمة في Swift 3 ، لذا نأمل أن نرى كود Swift الخاص بنا يتم تجميعه بشكل أسرع.


لتصحيح الأخطاء والاختبار ، تأكد من استخدام الإعدادات التالية لقطع وقت الترجمة من حوالي 20 دقيقة إلى أقل من دقيقتين ،

  1. في إعدادات إنشاء المشروع ، ابحث عن "التحسين" قم بتشغيل Debug إلى "Fastest [-O3]" أو أعلى.
  2. Set Build for Active Architecture: YES
  3. تنسيق معلومات التصحيح: DWARF
  4. وحدة الأمثل الأمثل: لا

لقد أهدرت ساعات لا تحصى في انتظار أن يبني المشروع فقط لأدرك أنه كان علي أن أغير ذلك قليلا واضطررت إلى الانتظار لمدة 30 دقيقة أخرى لاختباره. هذه هي الإعدادات التي عملت بالنسبة لي. (ما زلت أختبر الإعدادات)

ولكن ، تأكد من تعيين "DWARF بـ dSYM" على الأقل (إذا كنت ترغب في مراقبة تطبيقك) وبناء بنية Active إلى "NO" للإصدار / الأرشفة للدفع إلى iTunes Connect (أتذكر أن تضييع بضع ساعات هنا أيضًا).


لسوء الحظ ، لا يزال المحول البرمجي لـ Swift غير محسَّن لتجميع سريع وتزايدي (بدءًا من الإصدار Xcode 6.3 التجريبي). في هذه الأثناء ، يمكنك استخدام بعض الأساليب التالية لتحسين وقت ترجمة Swift:

  • تقسيم التطبيق إلى إطارات للحد من تأثير إعادة التركيب. ولكن اعلم أنه يجب عليك تجنب الاعتمادات الدورية في تطبيقك. لمزيد من المعلومات حول هذا الموضوع ، راجع هذه المقالة: http://bits.citrusbyte.com/improving-swift-compile-time/

  • استخدم Swift لأجزاء من مشروعك مستقرة تمامًا ولا تتغير كثيرًا. بالنسبة إلى المناطق الأخرى التي تحتاج إلى تغييرها كثيرًا أو المناطق التي تحتاج إلى الكثير من الترجمات / التجميعات لتكتمل (تقريبًا أي عناصر مرتبطة بـ UI) ، يمكنك استخدام Objective-C بشكل أفضل من خلال نهج الخلط والتوافق.

  • محاولة حقن رمز وقت التشغيل مع "حقن لل Xcode"

  • استخدم طريقة roopc: http://roopc.net/posts/2014/speeding-up-swift-builds/

  • قم بتخفيف محرك الاستدلال السريع من خلال إعطاء بعض التلميحات بظهورات صريحة.


من المرجح أن لا علاقة له بحجم مشروعك. من المحتمل أنها جزء معين من الكود ، ربما حتى سطر واحد فقط. يمكنك اختبار ذلك عن طريق محاولة تجميع ملف واحد في كل مرة بدلاً من المشروع بأكمله. أو حاول مشاهدة سجلات الإنشاء لمعرفة الملف الذي يستغرق وقتًا طويلاً.

كمثال على أنواع التعليمات البرمجية التي يمكن أن تتسبب في مشكلة ، يستغرق هذا النظام المكون من 38 سطرًا أكثر من دقيقة لتجميعه في beta7. كل هذا يحدث بسبب هذه الكتلة الواحدة:

let pipeResult =
seq |> filter~~ { $0 % 2 == 0 }
  |> sorted~~ { $1 < $0 }
  |> map~~ { $0.description }
  |> joinedWithCommas

بسّط ذلك من خلال سطر واحد أو اثنين فقط ويتم تجميعها على الفور. والمشكلة هي أن هذا الأمر يسبب نموًا أسيًا (ربما نموًا مضرًا) في المجمع. من الواضح أن هذا ليس مثاليًا ، وإذا تمكنت من عزل مثل هذه المواقف ، فعليك فتح الرادارات للمساعدة في تنظيف هذه المشكلات.


هذا كان يعمل مثل السحر بالنسبة لي - تسريع تجميع سويفت . تقليل وقت التحويل إلى 3 دقائق من 10 دقائق.

تقول أنه يجب عليك تشغيل " Whole Module Optimization أثناء إضافة -Onone في " Other Swift Flags .

أنا باستخدام Swift 3 على Xcode 8.3 / Xcode 8.2 .


يبدو أن المصفوفات السريعة وبناء القواميس سبب شائع جدًا (خصوصًا بالنسبة لك الذين يأتون من خلفية Ruby ) ،

var a = ["a": "b",
         "c": "d",
         "e": "f",
         "g": "h",
         "i": "j",
         "k": "l",
         "m": "n",
         "o": "p",
         "q": "r",
         "s": "t",
         "u": "v",
         "x": "z"]

من المحتمل أن يكون السبب في ذلك يجب إصلاح ذلك:

var a = NSMutableDictionary()
a["a"] = "b"
a["c"] = "d"
... and so on

يقضي المجمع الكثير من الوقت في الاستدلال والتحقق من الأنواع. لذا ، فإن إضافة التعليقات التوضيحية للنوع يساعد المترجم كثيرًا.

إذا كان لديك الكثير من المكالمات الدالة متسلسل مثل

let sum = [1,2,3].map({String($0)}).flatMap({Float($0)}).reduce(0, combine: +)

ثم يأخذ المجمع بعض الوقت لمعرفة ما يجب أن يكون نوع sum . إضافة نوع يساعد. ما يساعد أيضًا في سحب الخطوات المتقطعة إلى متغيرات منفصلة.

let numbers: [Int] = [1,2,3]
let strings: [String] = sum.map({String($0)})
let floats: [Float] = strings.flatMap({Float($0)})
let sum: Float = floats.reduce(0, combine: +)

خاصة لأنواع رقمية CGFloat ، Int يمكن أن يساعد كثيرا. يمكن أن يمثل العدد الحرفي مثل 2 أنواعًا رقمية مختلفة. لذلك يحتاج المحول إلى معرفة من السياق الذي هو عليه.

يجب أيضًا تجنب الوظائف التي تستغرق الكثير من الوقت للبحث عنها مثل + . يعد استخدام عدة + لسَلسَط صفائف متعددة بطيئًا لأن المحول البرمجي يحتاج إلى معرفة أي تطبيق + يجب أن يسمى + لكل. لذا استخدم a var a: [Foo] مع append() بدلاً من ذلك إن أمكن.

يمكنك إضافة تحذير للكشف عن الوظائف بطيئة في التحويل البرمجي في Xcode .

في " إعدادات البناء" لبحثك الهدف عن " علامات سويفت أخرى" وإضافتها

-Xfrontend -warn-long-function-bodies=100

للتحذير لكل وظيفة تستغرق أكثر من 100 مللي ثانية لتجميعها.