Golang Sort package - Fuzzy sorting error - sorting

I tried to modify standard sorting approach and add certain randomness to sorting Less interface.
when
if (u[i] - u[j]) <= 0
or
if u[i] < u[j]
it works as expected
But
if (u[i] - u[j]) <= rv
condition produces panic after several executions
package main
import (
"crypto/rand"
"fmt"
"math/big"
"sort"
)
type FuzzySorter []float64
func (u FuzzySorter) Len() int {
return len(u)
}
func (u FuzzySorter) Swap(i, j int) {
u[i], u[j] = u[j], u[i]
}
func (u FuzzySorter) Less(i, j int) bool {
pom, _ := rand.Int(rand.Reader, big.NewInt(int64(2)))
rv := float64(pom.Int64())
if (u[i] - u[j]) <= rv {
return true
} else {
return false
}
}
func (u FuzzySorter) Sort() FuzzySorter {
sort.Sort(u)
return u
}
func main() {
unsorted := FuzzySorter{
0,
1,
1,
1,
1,
6,
0,
4,
6,
1,
1,
1,
0,
2,
8,
1,
5,
4,
6,
6,
6,
16,
12,
6,
1,
1,
1,
0,
0,
11,
2,
14,
16,
6,
12,
0,
4,
1,
0,
16,
2,
6,
0,
0,
0,
0,
1,
11,
1,
0,
2,
1,
1,
1,
1,
0,
1,
12,
10,
1,
5,
2,
6,
4,
1,
0,
0,
11,
1,
1,
2,
2,
1,
0,
0,
1,
0,
1,
17,
2,
1,
1,
2,
0,
3,
7,
1,
5,
1,
0,
1,
0,
0,
0,
1,
3,
1,
1,
1,
2,
1,
0,
3,
1,
6,
1,
1,
0,
1,
12,
0,
1,
1,
0,
1,
0,
0,
6,
1,
2,
2,
0,
0,
2,
1,
1,
0,
4,
4,
1,
1,
1,
0,
1,
1,
1,
2,
0,
0,
1,
0,
1,
2,
1,
2,
1,
1,
0,
0,
4,
1,
0,
1,
0,
1,
1,
3,
1,
0,
}
unsorted.Sort()
fmt.Println(unsorted)
}
https://play.golang.org/p/4AxNRN4VD7
panic message
panic: runtime error: index out of range
goroutine 1 [running]:
panic(0x176ba0, 0x1040a010)
/usr/local/go/src/runtime/panic.go:464 +0x700
main.FuzzySorter.Less(0x10456000, 0x9f, 0x9f, 0x19, 0xffffffff, 0x4, 0x1, 0xd)
/tmp/sandbox201242525/main.go:21 +0x140
main.(*FuzzySorter).Less(0x10434140, 0x19, 0xffffffff, 0x5c, 0x1, 0x10434140)
<autogenerated>:3 +0xc0
sort.doPivot(0xfef741b0, 0x10434140, 0x19, 0x9f, 0x7, 0x19)
/usr/local/go/src/sort/sort.go:128 +0x280
sort.quickSort(0xfef741b0, 0x10434140, 0x19, 0x9f, 0xe, 0xfef741b0)
/usr/local/go/src/sort/sort.go:195 +0xa0
sort.Sort(0xfef741b0, 0x10434140)
/usr/local/go/src/sort/sort.go:229 +0x80
main.FuzzySorter.Sort(0x10456000, 0x9f, 0x9f, 0x1, 0x0, 0x0, 0x0, 0x1777a0)
/tmp/sandbox201242525/main.go:29 +0xa0
main.main()
/tmp/sandbox201242525/main.go:195 +0xc0

As far as I can understand Go sort implementation it expect two negative comparisons eg. Less(i, j) and Less(j, i) both return false, which it treats as equality, but not positive. E.g Less(i, j) and Less(j, i) can't both return true. So you can easily achieve desired result logically correct and deterministic way, just
if (u[i] - u[j]) < -1 {
return true
} else {
return false
}
https://play.golang.org/p/VcKI9uzcM9

As of Go 1.8, there is an easier way to sort a slice that does not require you to define new types. You simply create a Less (anonymous) lambda.
a := []int{5, 3, 4, 7, 8, 9}
sort.Slice(a, func(i, j int) bool {
return a[i] < a[j]
})
for _, v := range a {
fmt.Println(v)
}
This will sort in ascending order, if you want the opposite, simply write a[i] < a[j]

Related

Is it possible to sort a slice of structs by time in Golang?

I am trying to sort the structs Session in the slice Session by each Session's start time and hall_id. Here is the code:
Sessions := []Session{
Session{
name: "superman",
hall_id: 1,
startTime: time.Date(2022, time.August, 15, 17, 35, 0, 0, time.UTC),
endTime: time.Date(2022, time.August, 15, 18, 35, 0, 0, time.UTC),
},
Session{
name: "thor",
hall_id: 2,
startTime: time.Date(2022, time.August, 15, 16, 30, 0, 0, time.UTC),
endTime: time.Date(2022, time.August, 15, 17, 30, 0, 0, time.UTC),
},
Session{
name: "joker",
hall_id: 3,
startTime: time.Date(2022, time.August, 15, 19, 40, 0, 0, time.UTC),
endTime: time.Date(2022, time.August, 15, 20, 30, 0, 0, time.UTC),
},
Session{
name: "batman",
hall_id: 1,
startTime: time.Date(2022, time.August, 15, 17, 40, 0, 0, time.UTC),
endTime: time.Date(2022, time.August, 15, 18, 20, 0, 0, time.UTC),
},
}
The point is that I am using "time" package, and in order to create a date, you need to use Date() function, which requires multiple stuff like: year int, month, day int, hour int, minute int, etc.
I have tried the AndreKR's answer which is:
slice.Sort(planets[:], func(i, j int) bool {
return planets[i].Axis < planets[j].Axis
})
but it seems that it does not work with multiple "parameters" of a struct. I tried this:
sort.Slice(Sessions[:], func(i, j int) bool {
return Sessions[i].startTime.Year() < Sessions[j].startTime.Year(),
int(Sessions[i].startTime.Month()) < int(Sessions[j].startTime.Month()),
Sessions[i].startTime.Day() < Sessions[j].startTime.Day(),
Sessions[i].startTime.Hour() < Sessions[j].startTime.Hour(),
Sessions[i].startTime.Minute() < Sessions[j].startTime.Minute()
})
I am new to the Golang, so if I have made an obvious mistake I am sorry ;(
Alright, I have got the answer:
sort.Slice(Sessions[:], func(i, j int) bool {
return Sessions[i].startTime.Before(Sessions[j].startTime)
})
this code will solve this problem

Better way to convert slice of int to hex value

I have a slice of int containing only zeros and ones ([]int{1,1,1,1,0,0,0,0})
I want to convert the string representation to hex value. I'm converting the slice of ints to a slice of strings then doing a strconv.ParseUint to convert.
package main
import (
"fmt"
"log"
"strconv"
"strings"
)
func IntToString(values []int) string {
valuesText := []string{}
for i := range values {
valuesText = append(valuesText, strconv.Itoa(values[i]))
}
return strings.Join(valuesText, "")
}
func IntSliceToHex(in []int) (string, error) {
intString := IntToString(in)
ui, err := strconv.ParseUint(intString, 2, 64)
if err != nil {
return "", err
}
return fmt.Sprintf("%X", ui), nil
}
func HexToBin(hex string) (string, error) {
ui, err := strconv.ParseUint(hex, 16, 64)
if err != nil {
return "", err
}
return fmt.Sprintf("%b", ui), nil
}
func main() {
profile := []int{1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1}
hex, err := IntSliceToHex(profile)
if err != nil {
log.Fatalln(err)
}
bin, err := HexToBin(hex)
if err != nil {
log.Fatalln(err)
}
fmt.Println(hex, bin)
}
OUTPUT: F0F 111100001111
Is there a better way to do this?
You should use bitshift operations to build up the actual number from the slice rather than converting each bit to string and parsing it.
You should also keep the built-up integer rather than converting back and forth to a string.
package main
import (
"fmt"
)
func main() {
profile := []int{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
final := uint64(profile[0])
for i := 1; i < len(profile); i++ {
final <<= 1
final += uint64(profile[i])
}
fmt.Printf("%X %b\n", final, final)
// Output: FFFFFFFFFFFF0000 1111111111111111111111111111111111111111111111110000000000000000
}
Note: final is an unsigned 64 bit integer and can handle profile slices of length up to (and including) 64. For larger sizes, use big.Int.

go routine wait for response from channel and continue

I'm learning go concurrency and I wanted to implement a simple example that takes rows from a matrix and adds an array (slice) of values to each row.
Since I am using channels I try to wait for each row to get its corresponding result from the goroutine. However, this is not better than just doing this synchronously. How can I make each row wait for their respective result and allow the other rows to calculate their results concurrently?
https://play.golang.org/p/uCOGwOBeIQL
package main
import "fmt"
/*
Array:
0 1 2 3 4 5 6 7 8 9
+
Matrix:
1 0 0 0 0 0 0 0 0 0
0 1 0 0 0 0 0 0 0 0
0 0 1 0 0 0 0 0 0 0
0 0 0 1 0 0 0 0 0 0
0 0 0 0 1 0 0 0 0 0
0 0 0 0 0 1 0 0 0 0
0 0 0 0 0 0 1 0 0 0
0 0 0 0 0 0 0 1 0 0
0 0 0 0 0 0 0 0 1 0
0 0 0 0 0 0 0 0 0 1
->
Expected result:
1 1 2 3 4 5 6 7 8 9
0 2 2 3 4 5 6 7 8 9
0 1 3 3 4 5 6 7 8 9
0 1 2 4 4 5 6 7 8 9
0 1 2 3 5 5 6 7 8 9
0 1 2 3 4 6 6 7 8 9
0 1 2 3 4 5 7 7 8 9
0 1 2 3 4 5 6 8 8 9
0 1 2 3 4 5 6 7 9 9
0 1 2 3 4 5 6 7 8 10
*/
func main() {
numbers := []int {0,1,2,3,4,5,6,7,8,9}
matrix := [][]int{
{1,0,0,0,0,0,0,0,0,0},
{0,1,0,0,0,0,0,0,0,0},
{0,0,1,0,0,0,0,0,0,0},
{0,0,0,1,0,0,0,0,0,0},
{0,0,0,0,1,0,0,0,0,0},
{0,0,0,0,0,1,0,0,0,0},
{0,0,0,0,0,0,1,0,0,0},
{0,0,0,0,0,0,0,1,0,0},
{0,0,0,0,0,0,0,0,1,0},
{0,0,0,0,0,0,0,0,0,1},
}
rmatrix := make([][]int, 10)
for i, row := range matrix {
cResult := make(chan []int)
go func(row []int, numbers []int, c chan <- []int) {
c <- addRow(row,numbers)
}(row,numbers,cResult)
//this read from the channel will block until the goroutine sends its result over the channel
rmatrix[i] = <- cResult
}
fmt.Println(rmatrix)
}
func addRow(row []int, numbers []int) []int{
result := make([]int, len(row))
for i,e := range row {
result[i] = e + numbers[i];
}
return result
}
This example spawns a lesser number of goroutines and also guarantees the correct order irrespective of which goroutine completed it's processing first.
package main
import (
"fmt"
"sync"
)
type rowRes struct {
index int
result *[]int
}
func addRow(index int, row []int, numbers []int) rowRes {
result := make([]int, len(row))
for i, e := range row {
result[i] = e + numbers[i]
}
return rowRes{
index: index,
result: &result,
}
}
func main() {
numbers := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
matrix := [][]int{
{1, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 1, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 1, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 1, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 1, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 1, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 1, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 1, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 1, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
}
rmatrix := make([][]int, 10)
// Buffered channel
rowChan := make(chan rowRes, 10)
wg := sync.WaitGroup{}
// Reciever goroutine
go recv(rowChan, rmatrix)
for i := range matrix {
wg.Add(1)
go func(index int, row []int, w *sync.WaitGroup) {
rowChan <- addRow(index, row, numbers)
w.Done()
}(i, matrix[i], &wg)
}
wg.Wait()
close(rowChan)
fmt.Println(rmatrix)
}
func recv(res chan rowRes, rmatrix [][]int) {
for {
select {
case k, ok := <-res:
if !ok {
return
}
rmatrix[k.index] = *k.result
}
}
}
I needed to use a sync.WaitGroup and assign directly the results of the call (to guarantee they go back to their indexed row). Thanks #Peter
package main
import (
"fmt"
"sync"
)
func main() {
numbers := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
matrix := [][]int{
{1, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 1, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 1, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 1, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 1, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 1, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 1, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 1, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 1, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
}
rmatrix := make([][]int, 10)
var waitGroup sync.WaitGroup
for i, row := range matrix {
waitGroup.Add(1)
go func(i int, row []int) {
rmatrix[i] = addRow(row, numbers)
waitGroup.Done()
}(i, row)
}
waitGroup.Wait()
fmt.Println(rmatrix)
}
func addRow(row []int, numbers []int) []int {
result := make([]int, len(row))
for i, e := range row {
result[i] = e + numbers[i]
}
return result
}
pipeline method
taskChannel := make(chan string,1000); // Set up the task queue
wg := sync.WaitGroup
// Task release
wg.add(1)
go func(&wg,taskChannel) {
defer wg.Down()
for i in "task list" {
taskChannel <- "Stuff the characters you want to deal with here"
}
// After the task is sent and closed
close(taskChannel)
}(wg *sync.WaitGroup,taskChannel chan string)
// Task execution
go func(&wg,taskChannel,1000) {
defer wg.Down()
limit := make(chan bool,limitNumber); // Limit the number of concurrent
tg := sync.WaitGroup
loop:
for {
select {
case task,over := <-taskChannel:
if !over { // If there are no more tasks, quit
tg.Wait() // Wait for all tasks to end
break loop
}
tg.Add(1)
limit<-true
go func(&tg,limitm) {
defer func() {
<-limit
tg.Down()
}
// Business processing logic, processing tasks
}(tg *sync.WaitGroup,limit chan bool,task string)
}
}
}(wg *sync.WaitGroup,taskChannel chan string,limitNumber int)
wg.Wait()
Hope to help you

Sudoku Recursive Backtracking in Go

I am trying to solve a sudoku puzzle in Go using a recursive backtracking algorithm. I created helper functions that check if a certain row, column, or block are valid (i.e no repeated values), as well as a function to print out the current state. I have tested all of these many times so I don't think they are causing the issue. I created the following function to test if a potential game board would be possible.
func cellValid(gameBoard *[9][9]int, value int, y int, x int) bool {
oldVal := gameBoard[y-1][x-1]
gameBoard[y-1][x-1] = value
row := getRow(gameBoard, y)
col := getCol(gameBoard, x)
block := getBlock(gameBoard, x, y)
possible := unitValid(row) && unitValid(col) && unitValid(block)
gameBoard[y-1][x-1] = oldVal
return possible
}
It makes a change to the gameboard, checks if it is possible and stores that bool in the variable possible. It changes the board back to what it was then returns the bool. This function is being called from the following solveBoard function.
func solveBoard(gameBoard *[9][9]int) {
for row := 1; row <= 9; row++ {
for col := 1; col <= 9; col++ {
if gameBoard[row-1][col-1] == 0 {
for value := 1; value <= 9; value++ {
if cellValid(gameBoard, value, row, col) {
gameBoard[row-1][col-1] = value
solveBoard(gameBoard)
gameBoard[row-1][col-1] = 0
}
}
return
}
}
}
printBoard(gameBoard)
return
}
Upon running the file I get no output.
func main() {
var gameBoard = [9][9]int{
{5, 3, 0, 0, 7, 0, 0, 0, 0},
{6, 0, 0, 1, 9, 5, 0, 0, 0},
{0, 9, 8, 0, 0, 0, 0, 6, 0},
{8, 0, 0, 0, 6, 0, 0, 0, 3},
{4, 0, 0, 8, 0, 3, 0, 0, 1},
{7, 0, 0, 0, 2, 0, 0, 0, 6},
{0, 6, 0, 0, 0, 0, 2, 8, 0},
{0, 0, 0, 4, 1, 9, 0, 0, 5},
{0, 0, 0, 0, 8, 0, 0, 7, 9}}
solveBoard(&gameBoard)
}
Here is a link to a go playground containing all my code.
Go Playground
The following video demonstrates what I am trying to accomplish in python.
Computerphile Video
Solution to puzzle:
Puzzle solution
Your program works perfectly fine. Double check the second last line of your matrix:
You have:
{0, 0, 0, 4, 1, 7, 0, 0, 5},
But it should be
{0, 0, 0, 4, 1, 9, 0, 0, 5},
The final working code is.
package main
import (
"fmt"
)
func printBoard(gameBoard *[9][9]int) {
for y := 0; y < 9; y++ {
if y == 3 || y == 6 {
fmt.Println("\n---------")
} else {
fmt.Println("")
}
for x := 0; x < 9; x++ {
if x == 3 || x == 6 {
fmt.Print("|", gameBoard[y][x])
} else {
fmt.Print("", gameBoard[y][x])
}
}
}
fmt.Println("")
}
func unitValid(unit [9]int) bool {
for value := 1; value <= 9; value++ {
count := 0
for index := 0; index < 9; index++ {
if unit[index] == value {
count++
}
}
if count > 1 {
return false
}
}
return true
}
func getRow(gameBoard *[9][9]int, row int) [9]int {
return gameBoard[row-1]
}
func getCol(gameBoard *[9][9]int, col int) [9]int {
var column [9]int
for row := 0; row < 9; row++ {
column[row] = gameBoard[row][col-1]
}
return column
}
func getBlock(gameBoard *[9][9]int, row, col int) [9]int {
i := whatBlock(col)*3 - 2
j := whatBlock(row)*3 - 2
var block [9]int
block[0] = gameBoard[j-1][i-1]
block[1] = gameBoard[j-1][i]
block[2] = gameBoard[j-1][i+1]
block[3] = gameBoard[j][i-1]
block[4] = gameBoard[j][i]
block[5] = gameBoard[j][i+1]
block[6] = gameBoard[j+1][i-1]
block[7] = gameBoard[j+1][i]
block[8] = gameBoard[j+1][i+1]
return block
}
func whatBlock(val int) int {
if val >= 1 && val <= 3 {
return 1
} else if val >= 4 && val <= 6 {
return 2
} else if val >= 7 && val <= 9 {
return 3
}
return 0
}
func cellValid(gameBoard *[9][9]int, value int, y int, x int) bool {
oldVal := gameBoard[y-1][x-1]
gameBoard[y-1][x-1] = value
row := getRow(gameBoard, y)
col := getCol(gameBoard, x)
block := getBlock(gameBoard, y, x)
possible := unitValid(row) && unitValid(col) && unitValid(block)
gameBoard[y-1][x-1] = oldVal
return possible
}
func solveBoard(gameBoard *[9][9]int) {
for row := 1; row <= 9; row++ {
for col := 1; col <= 9; col++ {
if gameBoard[row-1][col-1] == 0 {
for value := 1; value <= 9; value++ {
if cellValid(gameBoard, value, row, col) {
gameBoard[row-1][col-1] = value
solveBoard(gameBoard)
gameBoard[row-1][col-1] = 0
}
}
return
}
}
}
printBoard(gameBoard)
return
}
func main() {
var gameBoard = [9][9]int{
{5, 3, 0, 0, 7, 0, 0, 0, 0},
{6, 0, 0, 1, 9, 5, 0, 0, 0},
{0, 9, 8, 0, 0, 0, 0, 6, 0},
{8, 0, 0, 0, 6, 0, 0, 0, 3},
{4, 0, 0, 8, 0, 3, 0, 0, 1},
{7, 0, 0, 0, 2, 0, 0, 0, 6},
{0, 6, 0, 0, 0, 0, 2, 8, 0},
{0, 0, 0, 4, 1, 9, 0, 0, 5},
{0, 0, 0, 0, 8, 0, 0, 7, 9}}
solveBoard(&gameBoard)
}

Matrix multiplication with goroutine drops performance

I am optimizing matrix multiplication via goroutines in Go.
My benchmark shows, introducing concurrency per row or per element largely drops performance:
goos: darwin
goarch: amd64
BenchmarkMatrixDotNaive/A.MultNaive-8 2000000 869 ns/op 0 B/op 0 allocs/op
BenchmarkMatrixDotNaive/A.ParalMultNaivePerRow-8 100000 14467 ns/op 80 B/op 9 allocs/op
BenchmarkMatrixDotNaive/A.ParalMultNaivePerElem-8 20000 77299 ns/op 528 B/op 65 allocs/op
I know some basic prior knowledge of cache locality, it make sense that per element concurrency drops performance. However, why per row still drops the performance even in naive version?
In fact, I also wrote a block/tiling optimization, its vanilla version (without goroutine concurrency) even worse than naive version (not present here, let's focus on naive first).
What did I do wrong here? Why? How to optimize here?
Multiplication:
package naive
import (
"errors"
"sync"
)
// Errors
var (
ErrNumElements = errors.New("Error number of elements")
ErrMatrixSize = errors.New("Error size of matrix")
)
// Matrix is a 2d array
type Matrix struct {
N int
data [][]float64
}
// New a size by size matrix
func New(size int) func(...float64) (*Matrix, error) {
wg := sync.WaitGroup{}
d := make([][]float64, size)
for i := range d {
wg.Add(1)
go func(i int) {
defer wg.Done()
d[i] = make([]float64, size)
}(i)
}
wg.Wait()
m := &Matrix{N: size, data: d}
return func(es ...float64) (*Matrix, error) {
if len(es) != size*size {
return nil, ErrNumElements
}
for i := range es {
wg.Add(1)
go func(i int) {
defer wg.Done()
m.data[i/size][i%size] = es[i]
}(i)
}
wg.Wait()
return m, nil
}
}
// At access element (i, j)
func (A *Matrix) At(i, j int) float64 {
return A.data[i][j]
}
// Set set element (i, j) with val
func (A *Matrix) Set(i, j int, val float64) {
A.data[i][j] = val
}
// MultNaive matrix multiplication O(n^3)
func (A *Matrix) MultNaive(B, C *Matrix) (err error) {
var (
i, j, k int
sum float64
N = A.N
)
if N != B.N || N != C.N {
return ErrMatrixSize
}
for i = 0; i < N; i++ {
for j = 0; j < N; j++ {
sum = 0.0
for k = 0; k < N; k++ {
sum += A.At(i, k) * B.At(k, j)
}
C.Set(i, j, sum)
}
}
return
}
// ParalMultNaivePerRow matrix multiplication O(n^3) in concurrency per row
func (A *Matrix) ParalMultNaivePerRow(B, C *Matrix) (err error) {
var N = A.N
if N != B.N || N != C.N {
return ErrMatrixSize
}
wg := sync.WaitGroup{}
for i := 0; i < N; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
for j := 0; j < N; j++ {
sum := 0.0
for k := 0; k < N; k++ {
sum += A.At(i, k) * B.At(k, j)
}
C.Set(i, j, sum)
}
}(i)
}
wg.Wait()
return
}
// ParalMultNaivePerElem matrix multiplication O(n^3) in concurrency per element
func (A *Matrix) ParalMultNaivePerElem(B, C *Matrix) (err error) {
var N = A.N
if N != B.N || N != C.N {
return ErrMatrixSize
}
wg := sync.WaitGroup{}
for i := 0; i < N; i++ {
for j := 0; j < N; j++ {
wg.Add(1)
go func(i, j int) {
defer wg.Done()
sum := 0.0
for k := 0; k < N; k++ {
sum += A.At(i, k) * B.At(k, j)
}
C.Set(i, j, sum)
}(i, j)
}
}
wg.Wait()
return
}
Benchmark:
package naive
import (
"os"
"runtime/trace"
"testing"
)
type Dot func(B, C *Matrix) error
var (
A = &Matrix{
N: 8,
data: [][]float64{
[]float64{1, 2, 3, 4, 5, 6, 7, 8},
[]float64{9, 1, 2, 3, 4, 5, 6, 7},
[]float64{8, 9, 1, 2, 3, 4, 5, 6},
[]float64{7, 8, 9, 1, 2, 3, 4, 5},
[]float64{6, 7, 8, 9, 1, 2, 3, 4},
[]float64{5, 6, 7, 8, 9, 1, 2, 3},
[]float64{4, 5, 6, 7, 8, 9, 1, 2},
[]float64{3, 4, 5, 6, 7, 8, 9, 0},
},
}
B = &Matrix{
N: 8,
data: [][]float64{
[]float64{9, 8, 7, 6, 5, 4, 3, 2},
[]float64{1, 9, 8, 7, 6, 5, 4, 3},
[]float64{2, 1, 9, 8, 7, 6, 5, 4},
[]float64{3, 2, 1, 9, 8, 7, 6, 5},
[]float64{4, 3, 2, 1, 9, 8, 7, 6},
[]float64{5, 4, 3, 2, 1, 9, 8, 7},
[]float64{6, 5, 4, 3, 2, 1, 9, 8},
[]float64{7, 6, 5, 4, 3, 2, 1, 0},
},
}
C = &Matrix{
N: 8,
data: [][]float64{
[]float64{0, 0, 0, 0, 0, 0, 0, 0},
[]float64{0, 0, 0, 0, 0, 0, 0, 0},
[]float64{0, 0, 0, 0, 0, 0, 0, 0},
[]float64{0, 0, 0, 0, 0, 0, 0, 0},
[]float64{0, 0, 0, 0, 0, 0, 0, 0},
[]float64{0, 0, 0, 0, 0, 0, 0, 0},
[]float64{0, 0, 0, 0, 0, 0, 0, 0},
[]float64{0, 0, 0, 0, 0, 0, 0, 0},
},
}
)
func BenchmarkMatrixDotNaive(b *testing.B) {
f, _ := os.Create("bench.trace")
defer f.Close()
trace.Start(f)
defer trace.Stop()
tests := []struct {
name string
f Dot
}{
{
name: "A.MultNaive",
f: A.MultNaive,
},
{
name: "A.ParalMultNaivePerRow",
f: A.ParalMultNaivePerRow,
},
{
name: "A.ParalMultNaivePerElem",
f: A.ParalMultNaivePerElem,
},
}
for _, tt := range tests {
b.Run(tt.name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
tt.f(B, C)
}
})
}
}
Performing 8x8 matrix multipliciation is relatively small work.
Goroutines (although may be lightweight) do have overhead. If the work they do is "small", the overhead of launching, synchronizing and throwing them away may outweight the performance gain of utilizing multiple cores / threads, and overall you might not gain performance by executing such small tasks concurrently (hell, you may even do worse than without using goroutines). Measure.
If we increase the matrix size to 80x80, running the benchmark we already see some performance gain in case of ParalMultNaivePerRow:
BenchmarkMatrixDotNaive/A.MultNaive-4 2000 1054775 ns/op
BenchmarkMatrixDotNaive/A.ParalMultNaivePerRow-4 2000 709367 ns/op
BenchmarkMatrixDotNaive/A.ParalMultNaivePerElem-4 100 10224927 ns/op
(As you see in the results, I have 4 CPU cores, running it on your 8-core machine might show more performance gain.)
When rows are small, you are using goroutines to do minimal work, you may improve performance by not "throwing" away goroutines once they're done with their "tiny" work, but you may "reuse" them. See related question: Is this an idiomatic worker thread pool in Go?
Also see related / possible duplicate: Vectorise a function taking advantage of concurrency

Resources