प्रोफ़ाइल के हिसाब से ऑप्टिमाइज़ेशन

प्रोफ़ाइल के निर्देशों के साथ ऑप्टिमाइज़ेशन (पीजीओ) एक मशहूर कंपाइलर ऑप्टिमाइज़ेशन तकनीक है. PGO में, कंपाइलर किसी प्रोग्राम के रनटाइम प्रोफ़ाइल का इस्तेमाल करता है, ताकि इनलाइनिंग और कोड लेआउट के बारे में सबसे सही विकल्प चुने जा सकें. इससे परफ़ॉर्मेंस बेहतर होती है और कोड का साइज़ कम होता है.

PGO को अपने ऐप्लिकेशन या लाइब्रेरी में डिप्लॉय करने के लिए, यह तरीका अपनाएं: 1. किसी ऐसे वर्कलोड की पहचान करें जो आपके काम का हो. 2. प्रोफ़ाइलें इकट्ठा करना. 3. रिलीज़ बिल्ड में प्रोफ़ाइलों का इस्तेमाल करें.

पहला चरण: किसी प्रतिनिधि वर्कलोड की पहचान करना

सबसे पहले, अपने ऐप्लिकेशन के लिए कोई मान्य मानदंड या वर्कलोड तय करें. यह एक अहम चरण है, क्योंकि वर्कलोड से इकट्ठा की गई प्रोफ़ाइलों से, कोड में मौजूद हॉट और कोल्ड क्षेत्रों की पहचान की जाती है. प्रोफ़ाइलों का इस्तेमाल करते समय, कंपाइलर ज़्यादा ऑप्टिमाइज़ेशन करेगा और ज़्यादा इस्तेमाल होने वाले हिस्सों में इनलाइनिंग करेगा. कंपाइलर, परफ़ॉर्मेंस को कम करते हुए, कम इस्तेमाल होने वाले हिस्सों के कोड का साइज़ भी कम कर सकता है.

सही वर्कलोड की पहचान करने से, आम तौर पर परफ़ॉर्मेंस पर नज़र बनाए रखने में भी मदद मिलती है.

दूसरा चरण: प्रोफ़ाइलें इकट्ठा करना

प्रोफ़ाइल इकट्ठा करने के लिए, ये तीन चरण पूरे करने होते हैं: - इंस्ट्रूमेंटेशन की मदद से नेटिव कोड बनाना, - डिवाइस पर इंस्ट्रूमेंट किए गए ऐप्लिकेशन को चलाना और प्रोफ़ाइलें जनरेट करना, और - होस्ट पर प्रोफ़ाइलों को मर्ज करना/पोस्ट-प्रोसेस करना.

इंस्ट्रूमेंट किया गया बिल्ड बनाना

प्रोफ़ाइलें इकट्ठा करने के लिए, ऐप्लिकेशन के इंस्ट्रूमेंट किए गए बिल्ड पर, पहले चरण से वर्कलोड चलाया जाता है. इंस्ट्रूमेंट किया गया बिल्ड जनरेट करने के लिए, कंपाइलर और लिंकर फ़्लैग में -fprofile-generate जोड़ें. इस फ़्लैग को एक अलग बिल्ड वैरिएबल से कंट्रोल किया जाना चाहिए, क्योंकि डिफ़ॉल्ट बिल्ड के दौरान फ़्लैग की ज़रूरत नहीं होती.

प्रोफ़ाइलें जनरेट करना

इसके बाद, डिवाइस पर इंस्ट्रूमेंट किए गए ऐप्लिकेशन को चलाएं और प्रोफ़ाइलें जनरेट करें. इंस्ट्रूमेंट की गई बाइनरी को चलाने पर, प्रोफ़ाइलें मेमोरी में इकट्ठा की जाती हैं और प्रोग्राम बंद होने पर, उन्हें फ़ाइल में लिखा जाता है. हालांकि, atexit के साथ रजिस्टर किए गए फ़ंक्शन, Android ऐप्लिकेशन में नहीं चलते — ऐप्लिकेशन बंद हो जाता है.

ऐप्लिकेशन/वर्कलोड को प्रोफ़ाइल फ़ाइल के लिए पाथ सेट करने के लिए, ज़्यादा काम करना होता है. इसके बाद, साफ़ तौर पर प्रोफ़ाइल राइटिंग ट्रिगर होती है.

  • प्रोफ़ाइल फ़ाइल का पाथ सेट करने के लिए, __llvm_profile_set_filename(PROFILE_DIR "/default-%m.profraw को कॉल करें. %m तब काम आता है, जब शेयर की गई एक से ज़्यादा लाइब्रेरी हों. इस लाइब्रेरी के लिए, %m एक यूनीक मॉड्यूल सिग्नेचर बन जाता है. इससे हर लाइब्रेरी के लिए, अलग प्रोफ़ाइल बन जाती है. पैटर्न के बारे में बताने वाले अन्य फ़ंक्शन के बारे में जानने के लिए, यहां देखें. PROFILE_DIR एक ऐसी डायरेक्ट्री है जिसमें ऐप्लिकेशन से लिखा जा सकता है. रनटाइम के दौरान इस डायरेक्ट्री का पता लगाने के लिए, डेमो देखें.
  • प्रोफ़ाइल राइटिंग को साफ़ तौर पर ट्रिगर करने के लिए, __llvm_profile_write_file फ़ंक्शन को कॉल करें.
extern "C" {
extern int __llvm_profile_set_filename(const char*);
extern int __llvm_profile_write_file(void);
}

#define PROFILE_DIR "<location-writable-from-app>"
void workload() {
  // ...
  // run workload
  // ...

  // set path and write profiles after workload execution
  __llvm_profile_set_filename(PROFILE_DIR "/default-%m.profraw");
  __llvm_profile_write_file();
  return;
}

अहम जानकारी: अगर वर्कलोड एक स्टैंडअलोन बाइनरी है, तो प्रोफ़ाइल फ़ाइल जनरेट करना आसान होता है — बाइनरी को चलाने से पहले, LLVM_PROFILE_FILE एनवायरमेंट वैरिएबल को %t/default-%m.profraw पर सेट करें.

प्रोसेस के बाद की प्रोफ़ाइलें

प्रोफ़ाइल फ़ाइलें .profraw फ़ॉर्मैट में हों. सबसे पहले, उन्हें adb pull का इस्तेमाल करके डिवाइस से फ़ेच करना होगा. फ़ेच करने के बाद, एनडीके में llvm-profdata सुविधा का इस्तेमाल करके .profraw से .profdata में बदलें. इसके बाद, इस टूल को कंपाइलर को भेजा जा सकता है.

$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-profdata \
    merge --output=pgo_profile.profdata \
    <list-of-profraw-files>

प्रोफ़ाइल फ़ाइल फ़ॉर्मैट के वर्शन के मेल न खाने से बचने के लिए, एक ही NDK रिलीज़ से llvm-profdata और clang का इस्तेमाल करें.

तीसरा चरण: ऐप्लिकेशन बनाने के लिए प्रोफ़ाइलों का इस्तेमाल करना

अपने ऐप्लिकेशन के रिलीज़ बिल्ड के दौरान, पिछले चरण की प्रोफ़ाइल का इस्तेमाल करें. इसके लिए, कंपाइलर और लिंकर को -fprofile-use=<>.profdata पास करें. कोड में बदलाव होने के बाद भी प्रोफ़ाइलों का इस्तेमाल किया जा सकता है — Clang कंपाइलर, सोर्स और प्रोफ़ाइलों के बीच थोड़े अंतर को बर्दाश्त कर सकता है.

अहम जानकारी: आम तौर पर, ज़्यादातर लाइब्रेरी के लिए, प्रोफ़ाइलें सभी आर्किटेक्चर में एक जैसी होती हैं. उदाहरण के लिए, लाइब्रेरी के arm64 बिल्ड से जनरेट की गई प्रोफ़ाइलों का इस्तेमाल, सभी आर्किटेक्चर के लिए किया जा सकता है. चेतावनी यह है कि अगर लाइब्रेरी में आर्किटेक्चर से जुड़े कोड पाथ (आर्म बनाम x86 या 32-बिट बनाम 64-बिट) हैं, तो ऐसे हर कॉन्फ़िगरेशन के लिए अलग-अलग प्रोफ़ाइल का इस्तेमाल किया जाना चाहिए.

यह रही पूरी जानकारी

https://github.com/DanAlbert/ndk-samples/tree/pgo/pgo पर, किसी ऐप्लिकेशन से PGO का इस्तेमाल करने के बारे में पूरी जानकारी दी गई है. इसमें ऐसी अतिरिक्त जानकारी भी दी गई है जो इस दस्तावेज़ में नहीं दी गई है.

  • CMake के बने बाइल्ड नियमों से, CMake वैरिएबल को सेटअप करने का तरीका पता चलता है. यह वैरिएबल, इंस्ट्रूमेंटेशन की मदद से नेटिव कोड बनाता है. जब बिल्ड वैरिएबल सेट नहीं होता है, तो पहले से जनरेट की गई PGO प्रोफ़ाइलों का इस्तेमाल करके, नेटिव कोड को ऑप्टिमाइज़ किया जाता है.
  • इंस्ट्रुमेंटेड बिल्ड में, pgodemo.cpp लिखता है कि प्रोफ़ाइल वर्कलोड एक्ज़ीक्यूशन हैं.
  • प्रोफ़ाइलों के लिए लिखने की अनुमति वाली जगह, रनटाइम के दौरान applicationContext.cacheDir.toString() का इस्तेमाल करके, MainActivity.kt में मिलती है.
  • adb root की ज़रूरत के बिना, डिवाइस से प्रोफ़ाइलें पाने के लिए, adb के लिए बनी रेसिपी का इस्तेमाल करें. यह रेसिपी यहां देखी जा सकती है.