بحث در مورد این کد و Optimization عجیب کامپایلر GCC . - هفت خط کد انجمن پرسش و پاسخ برنامه نویسی

بحث در مورد این کد و Optimization عجیب کامپایلر GCC .

+1 امتیاز

سلام.

داشتم این پست و نتایج عجیبش رو نگاه میکردم. و واقعا واسم جالب بود که توی تست Performance ها Java رتبه ی ۱ تا ۴ رو داشت Go رتبه ی ۵ تا ۶ رو داشت LuaJit رتبه ی ۷ و C++/C رتبه ی ۹ و ۸ و Assembly رتبه ی ۱۰ رو داشت. واقعا همچین چیزی با عقل جور در نیومد واسم. واسه همینم تصمیم گرفتم کد ++C و LuaJit رو خودم تست کنم ببینم که نکنه این یارو از خودش در آورده اینارو.

نتیجه ی تستم خیلی عجیب بود. برنامه کامپایل شده با ++C توی ۲۸ ثانیه اجرا شد. و در همین حال برنامه ی اجرا شده با LuaJit توی ۱۲ ثانیه اجرا شد. و واقعا تعجب کردم !! خلاصه به این نتیجه رسیدم که JIT Compiler ها کد ها رو Optimize میکنن و به خاطر همینه که همچین نتیجه ای حاصل شده.

واسه همین تصمیم گرفتم کد ++C رو با فلگ O3- بصورت بهینه کامپایل کنم. و دوباره نتیجه ی خیلی عجیب تری گرفتم . توی این تست، زمان اجرای کد رو با تابع ()clock اندازه گیری کردم. و این نتیجه ی این تست بود :

زمان اجرای کد کامپایل شده بدون O3- شد :  28864174 واحد زمانی clock_t

زمان احرای کد کامپایل شده با O3- شد       : 2 واحد زمانی clock_t

این اختلاف خیلی خیلی زیادیه !! این نتایج قدرت فلگ های O1- و O2- و O3- کامپایلر GCC رو واقعا نشون میدن .به نظرتون چیا این وسط تغییر کردن که همچین اختلاف زمانی بزرگی رو ایجاد کردن ؟؟ آیا مشکلی هم پیش اومده ؟؟ فکر میکنم خوب باشه درموردش بحثی بشه smiley

 

این کد ++C :

#include <iostream>
#include <time.h>
#include <sys/time.h>

using namespace std;

int main(int argc, char** argv)
{
    clock_t s,e;
    s = clock();

    size_t i_loop1 = 0;
    size_t i_loop2 = 0;
    size_t i_loop3 = 0;

    size_t i_counter = 0;

    for (i_loop1 = 0; i_loop1 < 10; i_loop1++) {
        for (i_loop2 = 0; i_loop2 < 32000; i_loop2++) {
            for (i_loop3 = 0; i_loop3 < 32000; i_loop3++) {
                i_counter++;
            }
         }

         if(i_counter > 50)
            i_counter = 0;
     }

    e = clock();
    cout<<"End time : "<< e-s<<endl;;
    cout<<"i_counter : "<< i_counter<<endl;;

    return 0;
}

 

این کد Lua :

#!/bin/env lua

i_counter = 0
local i_time_start = os.clock()

for i_loop1=1,10 do
    for i_loop2=1,32000 do
        for i_loop3=1,32000 do
            i_counter = i_counter + 1
        end
    end
end

local i_time_end = os.clock()

print(i_counter)
print(string.format("Total seconds: %.2f\n", i_time_end - i_time_start))

local s = 0
for s = 1,10 do
    print(s)
    s = s + 1
end

 

سوال شده تیر 8, 1394  بوسیله ی Ali Rahbar (امتیاز 4,240)   6 16 46
ویرایش شده تیر 8, 1394 بوسیله ی Ali Rahbar

3 پاسخ

0 امتیاز

من همین کد رو با Qt 5.4.2 mingw تست کردم در دوحالت Debug و Release.نتایج اینجوری شد:

 

debug:

End time : 26140
i_counter : 0

release:

End time : 0
i_counter : 0

یعنی کامپایلر چه جوری تشخیص میده که تو حالت release چه چیزایی رو میتونه انجام نده تا در حالی که تاثیری توی روند اجرای کد نداشته باشه ولی زمان اجرا رو کم کنه؟

پاسخ داده شده تیر 9, 1394 بوسیله ی ehsan_faal (امتیاز 41)   1 4 7
البته این optimize ها نمیتونه توی برنامه های پیچیده انجام بشه
مثلا مثال شما خیلی ساده بوده و به راحتی میشه به رابطه بینشون پیدا کرد
و شایدم مثلا متغییر های پرکاربرد رو register کنه (البته متمایین نیستم register ها پایا هستن یا غیر پایا)

در مورد قسمت debug و release
توی مد دیباگ به خاطر اینکه کدهای دیباگ حذف میشن طبیعیه که سرعت بره بالا
و اینکه دیگه به سختی میشه اونرو دیکامپایل کرد
خب پس الان تکلیف چیه؟

اصلا نتایج این سایته قابل اعتماد هست؟

اصلا مگه میشه 4 تا رتبه اول واسه جاوا باشه؟
آره فکر میکنی جاوا زبونه کمیه
پس چرا برنامه های اینترپرایس رو با جاوا مینویسن؟
نتایج اون سایت کلی نیستن. یعنی مطمئنا سطح پایین ترین زبان ها سرعت بیشتری رو خواهند داشت (البته به شرط اینکه کد نویسشون هم خوب باشه). من خودم ۲ تا از نتایج سایت رو تست کردم (LuaJit و ++C ) . نتیجه تست خود سایت این بود که LuaJit سریع تر بود. و نتیجه ی تست من هم همین بود و به خاطر همین هست که این پست رو گذاشتم تا موشکافیش کنیم .
+1 امتیاز
کدهای assembly شو مقایسه کنید
فکر میکنم به نتیجه خوبی برسید

برای opttimize کردن هم فک کنم مثل این میمونه که مثلا برای جمع کردن اعداد 1 تا 1000 هم میشه
دونه دونه جمع کرد که زمان خیلی زیادی میگیره هم میشه 500*1001 رو حساب کرد

 

مثلا در مثال بالا میشه یبار داخلی ترین حلقه رو حساب کرد بعد در دو ضربش کرد
پاسخ داده شده تیر 9, 1394 بوسیله ی Fire360Boy (امتیاز 2,524)   6 24 43
+1 امتیاز

سلام همگی. Fire360Boy یه جورایی درست حدس زدی. من یکی از دوستام بهم گفت که تعداد حلقه ها رو جمع میکنه بعد همون رو به i_counter اضافه میکنه. یعنی حلقه ها اجرا نمیشن! من خودم متغییر ها رو به صورت volatile کردم و دیدم که همینطوره. یعنی دوباره زمان اجرای برنامه برگشت به ۲۸ ثانیه.

درست باید کد Assembly بررسی بشه ولی فکر کنم دیگه نیازی نیست چون معلوم شد که حلقه ها اجرا نمیشن.

از طرف دیگه باید مطمئن بشم که LuaJit هم حلقه هاش رو اجرا میکنه. یعنی LuaJit هم حلقه ها رو اجرا نمیکنه ؟ اگه نکنه فکر نمیکنم زمان اجرا ی یه اسکریپتی که فقط یه متغییر رو با یک مقدار جمع میکنه بشه ۱۲ ثانبه.

پاسخ داده شده تیر 10, 1394 بوسیله ی Ali Rahbar (امتیاز 4,240)   6 16 46
نه  lua اون for ها رو هم حساب می کنه  گرنه جمع زدن که نمیشه ۱۲ ثانیه بشه!
خب پس نتیجه اخلاقی اینه که ++C بهتره !! چون میتونه کلا for ها رو حذف کنه عجیبه که lua این کارو نمیکنه
چک هم کن اون متغیر  توی lua سرریز نکنه آخه اون if که توی کد C هست تو اون نیست !
ولی در کل benchmark ای که کد تستش این باشه اصلا بدرد نمی خوره  .
Lua اون for ها رو حساب میکنه و درکل میشه ۱۲ ثانیه.و کد ++C ی که for ها رو حساب کرده درکل شده ۲۸ ثانیه. بحث سر اینه.
نتیجه ی اخلاقی رو کاری ندارم. بحث سر بهتر و بدتری نیست. این ۲ تا زبون قابل مقایسه با هم نیستن. و با اینکه چه کدی واسه benchmark خوبه کاری ندارم، من همین کد رو نظر گرفتم. این قضیه یه سری نکته توش هست که من بخاطر اون نکته ها این قضیه رو مطرح کردم.
یه دلیلش به نظرم میتونه این باشه که LuaJit نسبت به قابلیت های CPU از Instruction Set های بهتر و جدیدتری استفاده میکنه.
...