چرا از Go برای محاسبات سنگین استفاده کنیم؟

زبان Go در زمینه طراحی میکرو سرویس ها و برنامه های بسیار سریع با استفاده از حداکثر توان سیستم که به خوبی برای برنامه نویسان قابل فهم هستند مطرح شده است. در ادامه سعی می کنیم نمونه برنامه ای بنویسیم که میزان تاثیر استفاده از چند هسته در سرعت انجام محاسبات را به ما نشان بدهد.

در این برنامه یک متد داریم که یک عملیات ساده ریاضی را 100 میلیون بار انجام می دهد. حالا می خواهیم با استفاده از روتین های گو، این 100 میلیون عملیات را به طور موازی 100 بار انجام دهیم. در حقیقت می خواهیم 10000000000 بار دستور ساده ریاضی را اجرا کنیم.

برای اندازه گیری زمان اجرا از مجموعه time استفاده می کنیم. همچنین برای اطمینان از اینکه تمام عملیات مورد نظر ما به طور کامل انجام شده است از sync.WaitGroup استفاده کرده ایم.

ابتدا برنامه را با استفاده از فقط یک هسته پیاده سازی می کنیم.


package main

import (
 "log"
 "runtime"
 "sync"
 "time"
)

func m(wg *sync.WaitGroup) {
 defer wg.Done()
 j := 0
 for i := 0; i < 100000000; i++ {
        j += i % 2
    }
}

func main() {// use only one core
  runtime.GOMAXPROCS(1)

 start := time.Now()
 var wg sync.WaitGroup
 for i := 0; i < 100; i++ {
        wg.Add(1)
        go m(&wg)
 }
   // wait until all calls completed
    wg.Wait()
    elapsed := time.Since(start)
    log.Printf("Binomial took %s", elapsed)
}

فرض می کنیم اسم برنامه test.go باشد. آن را اجرا می کنیم.

go run test.go

خروجی این برنامه:

Binomial took 4.0700248s

استفاده از حداکثر توان!

حالا با تغییر در برنامه، به آن اجازه می دهیم از همه هسته های سیستم استفاده کند.

 runtime.GOMAXPROCS(runtime.NumCPU())

نتیجه اجرای برنامه روی سی پی یو 4 هسته ای:

Binomial took 2.1269973s

چطوره حالا برنامه رو روی یک سرور 32 هسته ای اجرا کنیم؟ نتیجه اعجاب آور است!

Binomial took 234.2243ms

اجرای 10.000.000.000 دستور در 234 میلی ثانیه. البته با ایجاد توازن بین تعداد روتین ها و تعداد پردازش در روتین ها می توان به سرعت های بالاتر از این هم دست پیدا کرد...

از این به بعد شاید برای برنامه هایی که نیاز به محاسبات ریاضی سنگین دارند (مثل استخراج رمز ارزها) گوشه چشمی هم به Go داشته باشید...

پانوشت

  • منظور از هسته همون پردازنده است. منتهی چون در اصطلاح می گوییم cpu چهار هسته ای، ما عنوان هسته رو آوردیم. در واقع سیستمی که 4 پردازنده دارد.
  • البته شاید سیستم 32 هسته ای دم دست شما باشه ولی من شانس آوردم برای تست، از یکی دسترسی ریموت موقتی گرفتم.
  • عملیات در واقع به معنی اجرای یک دستور محاسباتی هست مثلا یک جمع، یک تفریق و ... ولی خوب ما که پشت زمینه کد کامپایل شده نیستیم. به نظر میاد همین یک عملیات مورد نظر ما هم از چند عملیات مختلف تشکیل شده باشه. مثل فراخوانی متد، ورود عدد به ثبات، عملیات جمع، انتقال به پشته و ...
    برای همین هم اگر شما مثلا به جای یک جمع ساده جمع و تفریق و ضرب های مختلف رو بزارید باز هم در سرعت اجرا خیلی تاثیری نداره چون در واقع این سرعت اجرای یک دستور نیست و دستورایی که شما می گذارید بخش کوچکی از دستوراتی هست که طی فراخوانی اجرا می شه. و این یعنی سرعت باز هم باورنکردنی تر اجرا!
  • البته دستیابی به سرعت بالا هزینه هم داره. Go برای هر گو روتین، 32 کیلوبایت حافظه اختصاص می ده پس اگر ما 100 تا اجرا کنیم 3200 کیلوبایت حافظه نیازه (البته در این مثال عددی حساب نمی شه ولی در برنامه هایی که فراخوانی های متعدد گو روتین دارند این مقدار ممکنه خیلی زیاد بشه، مثل سیستم های مدیریت صف و ...)