2015-02-02 15 views
10

Wykonuję test: porównaj czasy ekstrakcji cgo i czystych funkcji Go uruchom 100 milionów razy. Funkcja cgo zabiera więcej czasu w porównaniu z funkcją Golang i jestem zdezorientowany z tego wyniku. Mój kod jest testowanie:Dlaczego wydajność Cgo jest tak powolna? czy coś jest nie tak z moim kodem testowym?

package main 

import (
    "fmt" 
    "time" 
) 

/* 
#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 

void show() { 

} 

*/ 
// #cgo LDFLAGS: -lstdc++ 
import "C" 

//import "fmt" 

func show() { 

} 

func main() { 
    now := time.Now() 
    for i := 0; i < 100000000; i = i + 1 { 
     C.show() 
    } 
    end_time := time.Now() 

    var dur_time time.Duration = end_time.Sub(now) 
    var elapsed_min float64 = dur_time.Minutes() 
    var elapsed_sec float64 = dur_time.Seconds() 
    var elapsed_nano int64 = dur_time.Nanoseconds() 
    fmt.Printf("cgo show function elasped %f minutes or \nelapsed %f seconds or \nelapsed %d nanoseconds\n", 
     elapsed_min, elapsed_sec, elapsed_nano) 

    now = time.Now() 
    for i := 0; i < 100000000; i = i + 1 { 
     show() 
    } 
    end_time = time.Now() 

    dur_time = end_time.Sub(now) 
    elapsed_min = dur_time.Minutes() 
    elapsed_sec = dur_time.Seconds() 
    elapsed_nano = dur_time.Nanoseconds() 
    fmt.Printf("go show function elasped %f minutes or \nelapsed %f seconds or \nelapsed %d nanoseconds\n", 
     elapsed_min, elapsed_sec, elapsed_nano) 

    var input string 
    fmt.Scanln(&input) 
} 

i wynik brzmi:

cgo show function elasped 0.368096 minutes or 
elapsed 22.085756 seconds or 
elapsed 22085755775 nanoseconds 

go show function elasped 0.000654 minutes or 
elapsed 0.039257 seconds or 
elapsed 39257120 nanoseconds 

Wyniki pokazują, że wywoływanie funkcji C jest mniejsza niż funkcja Go. Czy coś jest nie tak z moim kodem testowym?

Mój system: Mac OS X 10.9.4 (13E28)

+0

Dlaczego według ciebie wywołanie funkcji C z Go powinno być szybsze niż wywołanie funkcji Go z Go? – Volker

+0

Spodziewałbym się, że kod go wprowadzi do wersji go 'show()', co stanowi dodatkową zaletę dla kodu go w stosunku do 'C.show()'. –

Odpowiedz

25

Jak już odkryłeś, wywołanie kodu C/C++ za pośrednictwem CGo jest dość wysokie. Zasadniczo najlepiej jest zminimalizować liczbę połączeń CGo, które wykonujesz. W powyższym przykładzie zamiast wywoływania funkcji CGo wielokrotnie w pętli może być sens przeniesienie pętli do C.

Istnieje wiele aspektów, w jaki sposób środowisko wykonawcze Go ustawia wątki, które mogą przerwać Oczekiwania wielu fragmentów kodu C:

  1. Gorulenki działają na relatywnie niewielkim stosie, obsługując wzrost stosów poprzez segmentowane stosy (stare wersje) lub kopiując (nowe wersje).
  2. Wątki utworzone przez środowisko wykonawcze Go mogą nie współdziałać poprawnie z implementacją lokalnego magazynu wątków libpthread.
  3. Moduł obsługi sygnału UNIX Go runtime może zakłócać tradycyjny kod C lub C++.
  4. Przejdź ponownie używa wątków systemu operacyjnego, aby uruchomić wiele Goroutines. Jeśli kod C nazywany systemem blokującym wywoła lub w inny sposób zmonopolizuje wątek, może to być szkodliwe dla innych goroutines.

Z tych powodów CGo wybiera bezpieczne podejście do uruchamiania kodu C w osobnym wątku skonfigurowanym za pomocą tradycyjnego stosu.

Jeśli pochodzisz z języków takich jak Python, gdzie nie jest to rzadkie przepisywanie hotspotów kodu w C jako sposób na przyspieszenie programu, będziesz rozczarowany. Ale jednocześnie występuje znacznie mniejsza luka w wydajności między odpowiednimi kodami C i Go.

Generalnie rezerwuję CGo do łączenia się z istniejącymi bibliotekami, ewentualnie z małymi funkcjami opakowania C, które mogą zmniejszyć liczbę połączeń, które muszę wykonać z Go.

+0

Dzięki, bardzo mi to pomaga! –

+0

To jest prawdopodobnie nieaktualne: https://groups.google.com/forum/#!topic/golang-nuts/RTtMsgZi88Q – gavv

-1

Jest trochę głową w wywołanie funkcji C Go. Tego nie można zmienić.

8

Aktualizacja dla Jamesa answer: wygląda na to, że w obecnej implementacji nie ma przełącznika wątku.

Zobacz this thread na golang orzechów:

tam zawsze będzie jakiś napowietrznych. To droższe niż zwykłe wywołanie funkcji, ale znacznie tańsze niż przełącznik kontekstowy (agl pamięta wcześniejszą implementację, wycinamy przełącznik wątku przed publicznym wydaniem). W tej chwili wydatek to po prostu konieczność wykonania pełnego zestawu przełączników rejestru (bez udziału jądra). Przypuszczam, że jest to porównywalne do dziesięciu wywołań funkcji.

Zobacz także this answer, która łączy "cgo is not Go" wpis na blogu.

C nie wie nic o GO konwencji wywołującego lub growable stosy wiedzieć, więc wezwanie do kodu C musi rejestrować wszystkie szczegóły stosu goroutine, przełącznik do C stosu i run C kod, który nie ma znajomość sposobu, w jaki została wywołana, lub większe środowisko wykonawcze Go odpowiedzialne za program.

Zatem CGO ma obciążenie, ponieważ wykonuje on przełącznik stosu nie gwint przełącznika.

Zapisuje i odtwarza rejestry wszystkie, gdy wywoływana jest funkcja C, natomiast nie jest wymagana, gdy wywoływana jest funkcja Go lub funkcja montażu.


Poza tym, konwencje telefoniczne CGO za zabraniają przechodzenia wskazówek przejść bezpośrednio do kodu C, a wspólnym obejście jest użycie C.malloc, a więc wprowadzić dodatkowe alokacje. Szczegóły: this question.

Powiązane problemy