मेरे पास निम्न कोड है:

#include <iostream>
#include <chrono>

#define ITERATIONS "10000"

int main()
{
    /*
    ======================================
    The first case: the MOV is outside the loop.
    ======================================
    */

    auto t1 = std::chrono::high_resolution_clock::now();

    asm("mov $100, %eax\n"
        "mov $200, %ebx\n"
        "mov $" ITERATIONS ", %ecx\n"
        "lp_test_time1:\n"
        "   add %eax, %ebx\n" // 1
        "   add %eax, %ebx\n" // 2
        "   add %eax, %ebx\n" // 3
        "   add %eax, %ebx\n" // 4
        "   add %eax, %ebx\n" // 5
        "loop lp_test_time1\n");

    auto t2 = std::chrono::high_resolution_clock::now();
    auto time = std::chrono::duration_cast<std::chrono::nanoseconds>(t2 - t1).count();

    std::cout << time;

    /*
    ======================================
    The second case: the MOV is inside the loop (faster).
    ======================================
    */

    t1 = std::chrono::high_resolution_clock::now();

    asm("mov $100, %eax\n"
        "mov $" ITERATIONS ", %ecx\n"
        "lp_test_time2:\n"
        "   mov $200, %ebx\n"
        "   add %eax, %ebx\n" // 1
        "   add %eax, %ebx\n" // 2
        "   add %eax, %ebx\n" // 3
        "   add %eax, %ebx\n" // 4
        "   add %eax, %ebx\n" // 5
        "loop lp_test_time2\n");

    t2 = std::chrono::high_resolution_clock::now();
    time = std::chrono::duration_cast<std::chrono::nanoseconds>(t2 - t1).count();
    std::cout << '\n' << time << '\n';
}

पहला मामला

मैंने इसे के साथ संकलित किया

gcc version 9.2.0 (GCC)
Target: x86_64-pc-linux-gnu

gcc -Wall -Wextra -pedantic -O0 -o proc proc.cpp

और इसका आउटपुट है

14474
5837

मैंने इसे उसी परिणाम के साथ क्लैंग के साथ भी संकलित किया।

तो, दूसरा मामला तेज क्यों है (लगभग 3x स्पीडअप)? क्या यह वास्तव में कुछ माइक्रोआर्किटेक्चरल विवरणों से संबंधित है? यदि यह मायने रखता है, तो मेरे पास AMD का CPU है: "AMD A9-9410 RADEON R5, 5 COMPUTE CORES 2C+3G"।

3
eanmos 15 नवम्बर 2019, 23:33

1 उत्तर

सबसे बढ़िया उत्तर

mov $200, %ebx लूप के अंदर ebx के माध्यम से लूप-कैरीड डिपेंडेंसी चेन को तोड़ता है, जिससे आउट-ऑफ-ऑर्डर निष्पादन कई पुनरावृत्तियों में 5 add निर्देशों की श्रृंखला को ओवरलैप करने की अनुमति देता है।

इसके बिना, add निर्देशों की श्रृंखला, थ्रूपुट के बजाय add (1 चक्र) महत्वपूर्ण पथ की विलंबता पर लूप को बाधित करती है (खुदाई पर 4/चक्र, से बेहतर स्टीमरोलर पर 2/चक्र)। आपका सीपीयू एक खुदाई कोर है .

AMD के बाद से बुलडोजर में एक कुशल loop निर्देश (केवल 1 uop) है, Intel CPUs के विपरीत जहां loop प्रति 7 चक्रों में 1 पुनरावृत्ति पर या तो लूप को बाधित करेगा। (https://agner.org/optimize/ निर्देश टेबल, माइक्रोआर्क गाइड, और हर चीज पर अधिक विवरण के लिए इस उत्तर में।)

loop और mov के साथ फ्रंट-एंड (और बैक-एंड निष्पादन इकाइयों) में add से दूर स्लॉट लेते हुए, 4x स्पीडअप के बजाय एक 3x सही दिखता है।

इसके लिए यह जवाब देखें। सीपीयू कैसे इंस्ट्रक्शन लेवल पैरेललिज्म (आईएलपी) को ढूंढते और उसका फायदा उठाते हैं, इसका एक परिचय।

देखें प्रभाव को समझना ओवरलैपिंग स्वतंत्र डिप चेन के बारे में कुछ गहन विवरण के लिए लंबाई बढ़ाने के लिए दो लंबी निर्भरता श्रृंखलाओं के साथ लूप पर


बीटीडब्ल्यू, 10k पुनरावृत्तियों कई नहीं हैं। हो सकता है कि आपका सीपीयू उस समय निष्क्रिय गति से बाहर न निकले। या अधिकांश दूसरे लूप के लिए अधिकतम गति पर कूद सकते हैं लेकिन पहले में से कोई भी नहीं। इसलिए इस तरह के माइक्रोबेंचमार्क से सावधान रहें।

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

6
Peter Cordes 16 नवम्बर 2019, 03:07