Go : параллельное выполнение

Вопросы написания собственного программного кода (на любых языках)

Модератор: Olej

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: Go (продолжение)

Непрочитанное сообщение Olej » 11 фев 2022, 15:33

Olej писал(а):
11 фев 2022, 14:55
P.P.S. Это связано с особенностями диспетчирования потоков в системе Linux ( O(1) ), и несколько специфичным толкованием в Linux понятия приоритетов и nice... Более точно можно это посмотреть можно запуская программу с приоритетом реального времени
Вот как это может выглядеть ... + к тому же мы можем определить (афинити маской) число процессоров, на каких (и на скольких) это будет выполняться:

Код: Выделить всё

olej@R420:~/2022/Go/probes$ sudo chrt -r 50 taskset -c 0-11 ./mtime -b 10
единичное выполнение:  988222463ns | 0.988222 s
число процессоров в системе: 12
Выполнение 10 ветвей...
... потребовало: 1089920193ns | 1.089920s

Код: Выделить всё

olej@R420:~/2022/Go/probes$ sudo chrt -r 50 taskset -c 0-9 ./mtime -b 20
единичное выполнение:  977552702ns | 0.977553 s
число процессоров в системе: 10
Выполнение 20 ветвей...
... потребовало: 2413866896ns | 2.413867s

olej@R420:~/2022/Go/probes$ sudo chrt -r 50 taskset -c 0-9 ./mtime -b 20
единичное выполнение:  722341202ns | 0.722341 s
число процессоров в системе: 10
Выполнение 20 ветвей...
... потребовало: 1758223551ns | 1.758224s

Код: Выделить всё

olej@R420:~/2022/Go/probes$ sudo chrt -r 50 taskset -c 0-9 ./mtime -b 30
единичное выполнение:  683015507ns | 0.683016 s
число процессоров в системе: 10
Выполнение 30 ветвей...
... потребовало: 2417049985ns | 2.417050s

olej@R420:~/2022/Go/probes$ sudo chrt -r 50 taskset -c 0-9 ./mtime -b 30
единичное выполнение:  915232336ns | 0.915232 s
число процессоров в системе: 10
Выполнение 30 ветвей...
... потребовало: 3258652399ns | 3.258652s

Код: Выделить всё

olej@R420:~/2022/Go/probes$ sudo chrt -r 50 taskset -c 0-9 ./mtime -b 40
единичное выполнение:  825507337ns | 0.825507 s
число процессоров в системе: 10
Выполнение 40 ветвей...
... потребовало: 3703720454ns | 3.703720s

olej@R420:~/2022/Go/probes$ sudo chrt -r 50 taskset -c 0-9 ./mtime -b 40
единичное выполнение:  937893004ns | 0.937893 s
число процессоров в системе: 10
Выполнение 40 ветвей...
... потребовало: 4213729254ns | 4.213729s

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: Go (продолжение)

Непрочитанное сообщение Olej » 11 фев 2022, 15:42

Olej писал(а):
11 фев 2022, 15:33
определить (афинити маской) число процессоров, на каких (и на скольких) это будет выполняться:
Или даже выбрать конкретно номера процессоров (ядер, на выбор), на которых это будет выполняться:

Код: Выделить всё

olej@R420:~/2022/Go/probes$ sudo chrt -r 50 taskset -c 0,3,20,23 ./mtime -b 4
единичное выполнение:  638315714ns | 0.638316 s
число процессоров в системе: 4
Выполнение 4 ветвей...
... потребовало: 1005186380ns | 1.005186s

Код: Выделить всё

olej@R420:~/2022/Go/probes$ sudo chrt -r 50 taskset -c 0,3,20,23 ./mtime -b 8
единичное выполнение:  627548267ns | 0.627548 s
число процессоров в системе: 4
Выполнение 8 ветвей...
... потребовало: 2152005056ns | 2.152005s

Код: Выделить всё

olej@R420:~/2022/Go/probes$ sudo chrt -r 50 taskset -c 0,3,20,23 ./mtime -b 12
единичное выполнение:  604998524ns | 0.604999 s
число процессоров в системе: 4
Выполнение 12 ветвей...
... потребовало: 2998992022ns | 2.998992s

Код: Выделить всё

olej@R420:~/2022/Go/probes$ sudo chrt -r 50 taskset -c 0,3,20,23 ./mtime -b 16
единичное выполнение:  660434081ns | 0.660434 s
число процессоров в системе: 4
Выполнение 16 ветвей...
... потребовало: 4184678243ns | 4.184678s
P.S. Но вот как конкретно выбирать номера процессоров - это непростой вопрос ... в примере выше 0 и 3 - это физические ядра на разных чипах процессоров (из 2-х), а 20 и 23 - это им же соответвтующие гипертрэдинговые "половинки".
Подробно выбор номеров процессоров рассматривается здесь: нумерация ядер процессоров.

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: Go : параллельное выполнение

Непрочитанное сообщение Olej » 12 фев 2022, 03:27

Olej писал(а):
11 фев 2022, 11:55
А теперь самое время проверить и проиллюстрировать как это - в коде...
Так же, как для параллельных ветвей Go вносит новую сущность - горутину, в дополнение к имеющимся POSIX процесс и поток, так же для взаимодействия параллелизмов - канал ... который может использоваться и (помимо передачи данных) и для синхронизации ... хотя в пакетах Go есть место и основным традиционным механизмам синхронизации.

В показанном выше приложении синхронизация (ожидание завершения всех параллельных горутин) делалась специально с привлечением пакета sync. Но это можно сделать и на встроенных механизмах Go - каналах:

Код: Выделить всё

package main

import (
	"flag"
	"fmt"
	"math"
	"runtime"
	"time"
)

func main() {
	var branch int
	flag.IntVar(&branch, "b", 30, "number of parallel branches")
	useDebug := flag.Bool("v", false, "increase verbosity")
	flag.Parse()

	activDo := func(n int64) {    // активная рабочая функция
		var β, θ float64 = 0.0, math.Pi / 2
		for n != 0 {
			β += math.Sin(θ)
			θ *= 1.1
			n--
		}
	}
	const norma = 100000000
	start := time.Now()
	activDo(int64(norma))         // калибровка
	var dt time.Duration
	dt = time.Since(start)

	множитель := float64(time.Second) / float64(dt) * norma
	индекс := int64(math.Round(множитель))
	if *useDebug {
		fmt.Printf("вызов калибровки: %dns | %f s\n", dt, dt.Seconds())
		fmt.Printf("множитель=%f\n", множитель)
		fmt.Printf("индекс для 1s = %d\n", индекс)
	}

	start = time.Now()
	activDo(индекс)               // единичное выполнение
	dt = time.Since(start)
	fmt.Printf("единичное выполнение:  %dns | %f s\n", dt, dt.Seconds())

	fmt.Printf("число процессоров в системе: %v\n", runtime.NumCPU())
	канал := make(chan int)
	fmt.Printf("Выполнение %d ветвей...\n", branch)
	start = time.Now()
	for i := 0; i < branch; i++ {
		go func(n int,ch chan int) {
			if *useDebug {
				fmt.Println("Старт ", n, time.Now())
			}
			activDo(индекс)
			if *useDebug {
				fmt.Println("Финиш ", n, time.Now())
			}
			ch <- n       // сигнализация о завершении
		}(i, канал)
	}
	for i := 0; i < branch; i++ { // ожидаем всех завершений
		 _ = <-канал 
	} 
	dt = time.Since(start)
	fmt.Printf("... потребовало: %dns | %fs\n", dt, dt.Seconds())
}
P.S. Или даже просто так:

Код: Выделить всё

	for i := 0; i < branch; i++ { // ожидаем всех завершений
		<-канал 
	} 

Код: Выделить всё

olej@R420:~/2022/Go/probes$ go build mchan.go
Вложения
mchan.go
(1.8 КБ) 31 скачивание

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: Go : параллельное выполнение

Непрочитанное сообщение Olej » 12 фев 2022, 03:32

Olej писал(а):
12 фев 2022, 03:27
можно сделать и на встроенных механизмах Go - каналах:

Код: Выделить всё

olej@R420:~/2022/Go/probes$ ./mchan -b 5 -v
вызов калибровки: 614900239ns | 0.614900 s
множитель=162628006.394384
индекс для 1s = 162628006
единичное выполнение:  992041762ns | 0.992042 s
число процессоров в системе: 40
Выполнение 5 ветвей...
Старт  4 2022-02-12 02:15:51.394416528 +0200 EET m=+1.607273645
Старт  1 2022-02-12 02:15:51.394422797 +0200 EET m=+1.607279964
Старт  0 2022-02-12 02:15:51.394479827 +0200 EET m=+1.607336925
Старт  2 2022-02-12 02:15:51.394451712 +0200 EET m=+1.607308880
Старт  3 2022-02-12 02:15:51.394580452 +0200 EET m=+1.607437718
Финиш  2 2022-02-12 02:15:52.403979903 +0200 EET m=+2.616837182
Финиш  3 2022-02-12 02:15:52.407044894 +0200 EET m=+2.619902000
Финиш  0 2022-02-12 02:15:52.411916928 +0200 EET m=+2.624774078
Финиш  4 2022-02-12 02:15:52.414293527 +0200 EET m=+2.627150670
Финиш  1 2022-02-12 02:15:52.423184895 +0200 EET m=+2.636042066
... потребовало: 1028946708ns | 1.028947s
Здесь очень интересно порядок начала, и порядок завершения...
И так же как в предыдущем примере:

Код: Выделить всё

olej@R420:~/2022/Go/probes$ ./mchan -b 10
единичное выполнение:  980010503ns | 0.980011 s
число процессоров в системе: 40
Выполнение 10 ветвей...
... потребовало: 1111848983ns | 1.111849s

Код: Выделить всё

olej@R420:~/2022/Go/probes$ ./mchan -b 20
единичное выполнение:  936787294ns | 0.936787 s
число процессоров в системе: 40
Выполнение 20 ветвей...
... потребовало: 1045815757ns | 1.045816s

Код: Выделить всё

olej@R420:~/2022/Go/probes$ ./mchan -b 30
единичное выполнение:  914682345ns | 0.914682 s
число процессоров в системе: 40
Выполнение 30 ветвей...
... потребовало: 1305936744ns | 1.305937s

Код: Выделить всё

olej@R420:~/2022/Go/probes$ ./mchan -b 40
единичное выполнение:  983986837ns | 0.983987 s
число процессоров в системе: 40
Выполнение 40 ветвей...
... потребовало: 1709970853ns | 1.709971s
И: :lol:

Код: Выделить всё

olej@R420:~/2022/Go/probes$ ./mchan -b 200
единичное выполнение:  1066047880ns | 1.066048 s
число процессоров в системе: 40
Выполнение 200 ветвей...
... потребовало: 8124903590ns | 8.124904s

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: Go : параллельное выполнение

Непрочитанное сообщение Olej » 13 фев 2022, 13:12

Olej писал(а):
12 фев 2022, 03:27
канал ... который может использоваться и (помимо передачи данных) и для синхронизации
Параллелизм горутин в Go просто невозможно рассматривать без каналов...
Связными понятиями в этой цепи, являются, наверное:
- Ключевые слова (в Go зарезервированы и не могут быть использованы в качестве имён переменных и любых других объектов): go, chan, select ...
- Операция <-, чтение и запись в канал
- Асинхронные таймеры из пакета time: time.Tick(), time.After() ...
Эти вещи нужно рассматривать вместе.

Вверну сюда код, который я писал когда-то для конспекта Go:

Код: Выделить всё

package main

import (
	"fmt"
	"os"
	"time"
)

func child(num int, in, out chan string) {
	str1 := fmt.Sprintf("%v : ", num)
	for {
		str2 := <-in // строка полученная из канал
		fmt.Println(str1 + str2)
		if out != nil {
			out <- str2
		} // ретранслируется снова в канал
	}
}

func ввод(ch chan string) {
	const per = 300000000
	buf := make([]byte, 1024)
	for {
		fmt.Printf("> ")
		n, _ := os.Stdin.Read(buf)
		str := string(buf[:n-1])
		fmt.Println(str)
		ch <- str
		time.Sleep(per)
	}
}

func main() {
	канал := [...]chan string{make(chan string, 100), make(chan string),
		make(chan string), make(chan string)}
	for i := range канал {
		if i != len(канал)-1 {
			go child(i, канал[i], канал[i+1])
		} else {
			go child(i, канал[i], nil)
		}
	}
	ввод(канал[0])
}
Здесь 4 горутины через каналы ретранслируют друг-другу то, что мы введём с терминала.
Здесь характерно, что каналы - это такие же переменные Go, которые могут присваиваться, передаваться в качестве параметров в функции и т.д. :

Код: Выделить всё

olej@R420:~/2022/Go/probes$ go build multy.go 

olej@R420:~/2022/Go/probes$ ./multy 
> 123
123
0 : 123
1 : 123
2 : 123
3 : 123
> ^C
Вложения
multy.go
(863 байт) 31 скачивание

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: Go : параллельное выполнение

Непрочитанное сообщение Olej » 13 фев 2022, 13:31

Olej писал(а):
13 фев 2022, 13:12
Параллелизм горутин
Вообще то, судя по высказываниям авторов Go, они весьма неодобрительно относятся к модели параллелизма основанной на POSIX модели pthread_t с её механизмами взаимодействия (не говоря уже о её жалком подобии в потоках Windows :cry: ) - справедливо указывая на её перегруженность деталями и громоздкость.

За основу они взяли техники описанные в ранней работе (это первая книга - 1985г.) Тони Хоара: "Communicating Sequential Processes".
В академической среде сразу и высоко оценили эту книгу ... но мэйнстрим технологического развития, в суете и спешке, уехад тогда в другую сторону - книга сильно опередила в ней потребность! :lol:

К счастью, ещё в 1988г. в издательстве "Мир" вышел перевод книги, с предисловием академика Ершова (я надеюсь, что ест ещё люди, которые вспомнят кто это такой). Книга вполне доступна для скачивания:
Coitimtihicating Sequential Processes. C. A. R. Hoare
Взаимодействующие последовательные процессы
Оригинал здесь:
Communicating Sequential Processes. C. A. R. Hoare. May 18, 2015
Книга очень сложная (математически), но просмотреть её имеет прямой смысл, по крайней мере на предмет взаимодействия и каналов.

P.S. А для себя, опасаясь умельцев из Интернет, я сохраню копию прямо сюда...
Вложения
vzaimodeiistvuyushchie_posledovatelnye_processy.pdf
(4.3 МБ) 32 скачивания

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: Go : параллельное выполнение

Непрочитанное сообщение Olej » 13 фев 2022, 18:37

Olej писал(а):
13 фев 2022, 13:12
select
В отношении ключевого слова select, к которому нужно ещё "привыкнуть" для понимания - есть вот здесь хорошая глава в книге "Маленькая книга о Go", глава Select ... которая разъясняет это понятнее чем документация по Go.

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: Go : параллельное выполнение

Непрочитанное сообщение Olej » 30 мар 2022, 22:46

Olej писал(а):
11 фев 2022, 11:46
Параллельность в Go построена на том, что N ветвей на M процессорах (N>M или N>>M) распределяются равномерно в качестве сопрограмм, диспетчирующихся на кооперативной многозадачности. На Go не нужно писать код чтобы он использовал SMP, а код ... точнее даже бинарный скомпилированный код, один и тот же на разном оборудовании... будет сам выполняться так, чтобы использовать все процессоры (или указанное окружением число).
Глядя на такую интересную (эффективную) модель параллельного выполнения (не говоря уже об элегантности выражения этого в коде!) - надумал написать код, который мог бы рассмотреть как ветки Go распределяются между системными потоками (pthread_t) в системе.
Это (пока!) самая первая, но интересная редакция:
- исходный файл mlpar.go прост:

Код: Выделить всё

package main

//#include <pthread.h>
//unsigned long tid(void) { return (unsigned long)pthread_self(); }
import "C"
import (
	"fmt"
	"os"
	"runtime"
	"strconv"
	"time"
)

type msg struct { // структура сообщения
	n int          // номер ветви
	t int64        // системный pthread_t
}
func (p msg) String() string {
	return "[" + strconv.Itoa(p.n) + "," +
	       strconv.FormatInt(p.t, 16) + "]"
}

func main() {
	fmt.Printf("число процессоров в системе: %v\n", runtime.NumCPU())
	ветви := 3
	if len(os.Args) > 1 {
		ветви, _ = strconv.Atoi(os.Args[1])
	}
	fmt.Printf("число ветвей выполнения: %v\n", ветви)
	ch := make(chan msg)
	t0 := time.Now()
	for i := 1; i <= ветви; i++ {
		go func(n int64) {
			defer func() { ch <- msg{int(n), int64(C.tid())} }()
			t0, t1 := time.Now(), time.Now()
			for t1.Sub(t0).Seconds() < 1 {
				t1 = time.Now()
			}
		}(int64(i))
	}
	for i := 1; i <= ветви; i++ {
		m := <-ch
		fmt.Printf("%s\n", m.String())
	}
	fmt.Printf("итоговое время выполнения: %v\n", time.Now().Sub(t0))
}
- сборка элементарная:

Код: Выделить всё

olej@R420:~/2022/Go/probes$ go build mlpar.go 

olej@R420:~/2022/Go/probes$ ls -l mlpar
-rwxrwxr-x 1 olej olej 2166896 мар 30 22:45 mlpar

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: Go : параллельное выполнение

Непрочитанное сообщение Olej » 30 мар 2022, 23:02

Olej писал(а):
30 мар 2022, 22:46
- исходный файл mlpar.go прост:
Но тут:
1). Используется псевдомодуль Cgo уровня совместимости кода Go с нативным Linux API (кодом на C), описано здесь:Using cgo with the go command.
Вызывается системный вызов pthread_self() возвращающий идентификатор pthread_t системы Linux.

2). type msg struct - это тип (класс в терминологии C++) сообщений обмена между ветвями гопрограмм.
Для типа реализован метод String() - т.е. объектно-ориентированное программирование в Go.

3). Запускается 'ветви' (целое число) параллельных горутин, меньше или больше числа процессоров, которые отрабатывают по 1 сеекунде, а завершаются отправкой в канал сообщения типа msg, в котором содержится pthread_t того системного потока, в котором выполнялась горутина.

4). Главная ветвь программы синхронизируется на приёме числа 'ветви' сообщений из канала, после чего даёт диагностику и завершается.

Вообще то, достаточно большая функциональность в 45 строк кода! Это к вопросу о уровне языка...

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: Go : параллельное выполнение

Непрочитанное сообщение Olej » 30 мар 2022, 23:07

Olej писал(а):
30 мар 2022, 22:46
- сборка элементарная:
Но сборка интересная!
Она статическая как по дефаулту полагается в GoLang - я не указывал использовать динамические библиотеки ... но динамически прикомпоновываются расшаренные (разделяемые, *.so) библиотеки Linux:

Код: Выделить всё

olej@R420:~/2022/Go/probes$ ldd mlpar
	linux-vdso.so.1 (0x00007ffd35776000)
	libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fdf0d9d1000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fdf0d7df000)
	/lib64/ld-linux-x86-64.so.2 (0x00007fdf0da1b000)

Ответить

Вернуться в «Программирование»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 9 гостей