Интересни имплементационни детайли в Go

04.12.2014

Как работи компилаторът на Go?

gc toolchain-а наследява този на plan9

Туловете в него са от типа \d[acl]:

И къде тук има go?

go build

-x го кара да принтира какво всъщност прави:

-> go build -x github.com/Vladimiroff/vec2d
WORK=/var/folders/t2/24j0lkxs1d93btzxnl71n_kw0000gn/T/go-build703616983
mkdir -p $WORK/github.com/Vladimiroff/vec2d/_obj/
mkdir -p $WORK/github.com/Vladimiroff/
cd /Users/kiril/go/src/github.com/Vladimiroff/vec2d
/usr/local/Cellar/go/1.3.3/libexec/pkg/tool/darwin_amd64/6g
    -o $WORK/github.com/Vladimiroff/vec2d.a -trimpath $WORK
    -p github.com/Vladimiroff/vec2d -complete
    -D _/Users/kiril/go/src/github.com/Vladimiroff/vec2d -I $WORK
    -pack ./utils.go ./vector.go

Стойности

var gopher int32 = 2014

CPU Cache

type Location struct {
    // float64 -> 8 bytes
    X, Y, Z float64
    // 24 bytes in total
}

var Locations [1000]Location
// 24 * 1000 bytes stored *sequnetially*

=> По-добро използване на кеша

Извикване на функция

Бе върши се доста работа по темата.

Пример

package util

func Max(a, b int) int {
    if a > b {
        return a
    }
    return b
}

---

package main

func double(a, b int) int {
    return 2 * util.Max(a, b)
}

Inlining

package main

func double(a, b int) int {
    max := b
    if a > b {
        max = a
    }
    return 2 * max
}

Dead code elimination

func LargeAndComplicatedAction() {
    if false {
        [...]
    }
    [...]
}

Debug checks

func DebugChecks() bool {
    return debugBuildTagIsOn
}

func LargeAndComplicatedAction() {
    if DebugChecks() {
        [...]
    }
    [...]
}

Escape analysis

Кое къде отива в С, примерно?

Толкова е просто. Хайде сега на Go

Stupid Sum

func SumFirst100() int {
    numbers := make([]int, 100)
    for i := range numbers {
        numbers[i] = i + 1
    }
    var sum int
    for _, i := range numbers {
        sum += i
    }
    return sum
}

Cursor

type Cursor struct {
    X, Y int
}

const width, height = 640, 480

func Center(c *Cursor) {
    c.X += width / 2
    c.Y += height / 2
}

func CenterCursor() (int, int) {
    c := new(Cursor)
    Center(c)
    return c.X, c.Y
}

Компилаторът на Go е комуникативно същество

-> go build -gcflags=-m esc.go
# command-line-arguments
./esc.go:9: can inline Center
./esc.go:16: inlining call to Center
./esc.go:9: Center c does not escape
./esc.go:15: CenterCursor new(Cursor) does not escape
./esc.go:22: SumFirst100 make([]int, 100) does not escape

Да си поговорим за горутини

Cooperatively scheduled

Cooperatively scheduled

Да си поговорим за C

Сценарий: Изпълняваме thread на C.

Unless...

int ack(int m, int n)
{
        if (m == 0) {
                return n + 1;
        } else if (m > 0 && n == 0) {
                return ack(m - 1, 1);
        } else {
                return ack(m - 1, ack(m, n - 1));
        }
}

ack(4, 5);
// segmentation fault

Рекурсия. Свърши ни паметта на стека.

Как можем да го решим това?

Да, това ще понася по-дълбока рекурсия, но пък ще заделяме повече памет за
АБСОЛЮТНО всяка функция, която тя може да не използва.

Ще знаем точно колко голям стек да заделяме за всяка функция, но този анализ ще
отнема време, което води до бавна компилация с много налучкване или бавно
изпълнение на всяка функция

Адресно пространство

Guard page

Thread stacks and guard pages

Segmented Stacks

Това е начинът, по който Go до версия 1.3 управляваше стековете.

Stack Split

+---------------+
|               |
|   unused      |
|   stack       |
|   space       |
+---------------+
|    Foobar     |
|               |
+---------------+       +---------------+
|               |   +-->|    Foobar     |
|  lessstack    |   |   |               |
+---------------+   |   +---------------+
| Stack info    |---+   | rest of stack |
|               |       |               |
+---------------+       +---------------+

lessstsack

Супер яко, нали?

Но има и проблеми

Stack copying

Channels on steroids

В началото на тази година Dmitry Vyukov предложи редица оптимизации на каналите:

Как?

Отдолу каналите са три вида:

1. Sync channels
2. Async channels
3. Async channels with zero-sized elements

Sync channels

struct Hchan {
    Lock;
    bool closed;
    SudoG* sendq;  // waiting senders
    SudoG* recvq;  // waiting receivers
};

Async channel

truct Hchan {
    uint32 cap;   // channel capacity
    Elem*  buf;   // ring buffer of size cap
    // send and receive positions,
    // low 32 bits represent position in the buffer,
    // high 32 bits represent the current “lap” over the ring buffer
    uint64 sendx;
    uint64 recvx;
};

struct Elem {
    // current lap,
    // the element is ready for writing on laps 0, 2, 4, ...
    // for reading -- on laps 1, 3, 5, ...
    uint32 lap;
    T val;  // user data
};

Async channels with zero-sized elements

Почти като обикновените async channels:

С тази разлика, че:

Close

Select

Не се опитва да хване всички mutex-и на всички канали

Въпроси?