Cgo и Go изцяло на Go
03.01.2017
Но преди това
- 17.01.2017 ще има първоначална мини-защита на проектите (по желание)
- Ще напишем в новина какво точно очакваме
- 18.02.2017 (събота) от 10:00ч. до 15:00ч. в зала 306 ще е реалната защита
- 19.02.2017 (неделя) от 14:00ч. до 17:00ч. в зала 326 ще дадем втория тест и ще пишем оценките
- Ще има новина и за това
Въпроси за мъфини
Въпрос #1
Кои от следните инструменти са вградени в стандартната дистрибуция на езика?
- Race condition detector
- Unit testing framework
- Test code coverage generator
- Static code analyzer
- Documentation generator
- Package/dependency manager
Отговор:
- Всички без последното...
- ... kind of
- ... за момента
Въпрос #2
Защо е нужно и какво представлява vendoring в Go?
- Възможност да се фиксират конкретни версии на външните библиотеки, от които зависи даден проект
- Става с помощта на папка
vendor
, в която се поставят библиотеките
- Това може да стане с копиране, git submodules, symlinks и т.н.
- Структурата на директориите е същата, както в
$GOPATH/src
: за да import-нем github.com/keltia/leftpad
от локалната vendor
папка, трябва да имаме /vendor/github.com/keltia/leftpad
- Има инструменти, които почти изцяло автоматизират процеса
Въпрос #3
Какво прави go generate?
- Изпълнява указани чрез коментари в кода команди
- Обикновено се използва за автоматично генериране на код или статични файлове
- Обикновено резултата се commit-ва в git, за да продължи да работят
go get
и import
Въпрос #4
Как може да укажем даден файл от нашия код да се компилира само за windows?
Чрез build tags/constraints, т.е. коментари от вида:
// +build linux,386 darwin,!cgo
Въпрос #5
Как можем да компилираме Go програма за x64 Windows на Linux или OS X машина?
Отговор:
GOOS=windows GOARCH=amd64 go build
...но само ако не ползвате нещата, за които ще си говорим днес...
Днес ще видите
- cgo
- unsafe
- Go in Go
- GODEBUG
C? Go? Cgo!
C в .go файлове
- Споменавали сме ви, че можете да използвате C библиотеки в go
- Бърз пример как се прави:
package main
/*
#include <stdlib.h>
*/
import "C"
func Random() int {
return int(C.random())
}
func Seed(i int) {
C.srandom(C.uint(i))
}
Особености
- Коментарите трябва да се точно преди импорта на "C"
- Те стават header на компилирания С сорс
- Коментари, започващи с
#cgo
са "специални"
// #cgo CFLAGS: -DPNG_DEBUG=1
// #cgo amd64 386 CFLAGS: -DX86=1
// #cgo LDFLAGS: -lpng
// #include <png.h>
import "C"
- CPPFLAGS and LDFLAGS могат да бъдат "наследявани" от други пакети:
// #cgo pkg-config: png cairo
Go в .c файлове (1)
- А вече може да използвате Go библиотеки в C
package goc
import "C"
//export GreetFromGo
func GreetFromGo(name string) {
println("Hello from Go, ", name)
}
func main() {
// Needed by cgo in order to generate a library
}
- Важното: //export <function-name>
Go в .c файлове (2)
go build -buildmode=c-archive -o goc.a goc.go
- Ще се генерират
goc.a
и goc.h
файлове
- В
goc.h
, освен малко boilerplate, ще има и:
extern void GreetFromGo(GoString p0);
GoString
е част от споменатия boilerplate
Go в .c файлове (3)
- Сега можем да го използваме
#include "goc.h"
#include <stdio.h>
int main() {
printf("Hi, I am a C program.\n");
GoString name = {"Doycho", 4};
GreetFromGo(name);
return 0;
}
- След това събираме всичко със
Никога не е толкова просто
github.com/golang/go/wiki/cgo
golang.org/src/cmd/cgo/doc.go
Стигне ли се до компилиране на C, забравете за лесно:
- Статично линкване
- Cross компилиране
Но, за това пък, има много от:
- Wrapper-и във wrapper-и
- Ръчно управление на памет
- Увещаване на linker-а да ви събере всичките частички
Ситният шрифт (2)
Споделяне на памет, алокирана от Go е възможно ако:
- споделената памет не съдържа указатели към друга алокирана от Go памет
- C не задържи указателя към паметта, след като върне
runtime проверява за това и ако види нарушение crash-ва програмата.
Още от магиите на "C"
- В .go файловете, полетата на структури са достъпни с _. Ако в структурата x има поле foo, то е достъпно с x._foo
- Стандартните типове са достъпни през C, с леки промени.
unsigned int
-> C.uint
- Директният достъп до struct, union или enum също е особен:
C.struct_foo
- В този ред на мисли go не съпортва union, а се представят като масиви от байтове
- И докато сме на темата с ограниченията, не можете да вкарвате поле със C тип в go struct
Error handling
- Няма нужда да се отказвате от адекватния error handling
- Всяка C функция може да се извиква с две стойности от ляво. Втората очевидно е
errno
Function pointers
- Лошата новина: не може да викате функция по указател
- Добрата новина: можете да я пазите в променлива
Мoля?
package main
// typedef int (*intFunc) ();
//
// int
// bridge_int_func(intFunc f)
// {
// return f();
// }
//
// int fortytwo()
// {
// return 42;
// }
import "C"
import "fmt"
func main() {
f := C.intFunc(C.fortytwo)
fmt.Println(int(C.bridge_int_func(f)))
// Output: 42
}
Друг начин за викане на Go от C
package main
func Add(a, b int) int {
return a + b
}
Друг начин за викане на Go от C (2)
#include <stdio.h>
extern int go_add(int, int) __asm__ ("example.main.Add");
int main() {
int x = go_add(2, 3);
printf("Result: %d\n", x);
}
Друг начин за викане на Go от C (3)
- Само за
gccgo
- Спомняте ли си, че има и друг компилатор?
all: main
main: foo.o bar.c
gcc foo.o bar.c -o main
foo.o: foo.go
gccgo -c foo.go -o foo.o -fgo-prefix=example
clean:
rm -f main *.o
Указатели без граници
- Ако си играете със C рано или късно ще попаднете на void*
- Навярно ще ви се наложи и да освободите паметта сочена от указател, който ви е бил върнат
- Може даже да ви се наложи да заделите памет и да подадете указател (като void*) на някоя C функция
- void* на Go-шки е unsafe.Pointer
package unsafe
Декларира невинно изглеждащите:
func Alignof(v ArbitraryType) uintptr
func Offsetof(v ArbitraryType) uintptr
func Sizeof(v ArbitraryType) uintptr
type ArbitraryType int
type Pointer *ArbitraryType
реално тези дефиниции съществуват главно за документация, имплементацията е в компилатора.
Safety third
unsafe.Pointer има четири важни харектеристики
- указател към какъв да е тип може да бъде конвертиран към unsafe.Pointer
- unsafe.Pointer може да бъде конвертиран към указател към какъв да е тип
- uintpr може да бъде конвертиран към unsafe.Pointer
- unsafe.Pointer може да бъде конвертиран към uintptr
Това е практическо заобикаляне на типовата система в Go.
Unsafe пример:
package main
/*
#include <stdlib.h>
*/
import "C"
import (
"fmt"
"unsafe"
)
func main() {
cs := C.CString("42") // alloc on C's heap
defer C.free(unsafe.Pointer(cs)) // don't leak
answer := C.atoi(cs)
fmt.Println(answer)
}
Адресна аритметика:
package main
import "unsafe"
import "fmt"
type slice struct {
array unsafe.Pointer
size, _cap int
}
func main() {
var p = []string{"Hello", " "}
p = append(p, "World!")
var s = (*slice)(unsafe.Pointer(&p))
var sizeOfString = unsafe.Sizeof("")
fmt.Printf("size=%d, cap=%d\n", s.size, s._cap)
for i := 0; s.size > i; i++ {
fmt.Printf("[%d]: `%s`\n", i,
*(*string)(unsafe.Pointer(uintptr(s.array) + uintptr(i)*sizeOfString)))
}
}
Go in Go
С? Защо?
- Вече готови инструменти в Plan9
- Познавайки тези иструменти, могат да се движат бързо
Компилатор
- Вече изцяло на Go, всичкото С го няма
- Не го правят за да се тупат в гърдите
- Коректно Go се пише по - лесно от коректно С
- ... и се дебъгва по - лесно, дори без дебъгер
- Go прави параленото изпълнение тривиално
Runtime
- До 1.5 са имали нужда от специален компилатор за runtime-а на езика
- Вече го няма. Един компилтор по - малко.
- Само един език. По - лесна интеграция, управление на стека
- Простота
Как се махат хиляди редове С?
- Транслатор от С до (лошо!) Go
- Специално написан за тази задача, далеч от генерален
- Махане на странни С идиоми (*p++)
- Statement by statement принтиране на Go код от С
- Compile, Run, Compare, Repeat
www.youtube.com/watch?v=cF1zJYkBW4A&feature=youtu.be
Производителност
- 1.5 е първа стъпка
- Компилирането на Go програми е ~2 пъти по - бавно
- Но пък, 2 * много бързо = просто бързо
- От друга страна: go profiling(!) и други подобрения
- Прави много по - лесни оптимизации в бъдеще
Също преминали в Go Land:
GODEBUG
Добре, компилира се!
- Вече програмата работи
- ... почти
- ... прави неща, които не разбирам
- Какво!? Сигурен съм, че имах повече от 100 MB свободна рам!
Повече информация
- Можете да получите повече информация за работеща програма с флагове на GODEBUG променливата на средата:
export GODEBUG="name=flag"
Пример:
export GODEBUG="gctrace=2,invalidptr=1"
Позволява:
- Проследяваме на всяка алокация и освобаждаване на памет (allocfreetrace)
- Спрете конкурентния GC. Всичко става stop-the-world (gcstoptheworld)
- Информация за всяко пускане на GC (gctrace)
...
- Показване на състоянието на scheduler-a (scheddetail и schedtrace)
- Спрете намаляването на goroutine стековете (gcshrinkstackoff)
- Всяка алокация да бъде на нова страница и да не се преизползват адреси (efence)
- Много други
GOGC
- Променлива на средата, подобна на GODEBUG
- Определя колко често да се пуска garbage collector-а
- Стойноста е колко процента трябва да стане ново алокираното пространство в heap-a спрямо "живия" heap. След стигането на тази стойност се пуска чистенето на боклук.
- По подразбиране
GOGC=100
- Възможни са всякакви числа, 200, 300
GOGC=off
спира събирането на боклук изцяло
GOMAXPROCS
- Променлива на средата
- Функция
GOMAXPROCS(n int)
от пакета runtime
- Определя на колко истински нишки от операционната система ще се изпълнява вашия Go код
Въпроси и подсещания
- Последно домашно - feedback
- Следваща лекция - runtime? databases? нещо друго?
- Reminder 1: 17.01.2017 - първоначална мини-защита на проектите
- Reminder 2: 18.02.2017 и 19.02.2017 - истинска защита на проектите и втори тест