Thanks Jake. That makes sense. Because the slice was being reused as-is for each iteration, the QuickFind was finishing much faster than the QuickUnion. That is because the find part in the QuickFind function returned immediately as all pairs got connected in a previous iteration. I am confident that the functions for QuickUnion and QuickFind are correctly implemented. (They are taken straight from Robert Sedgewick's book, but just converted to go).
But, if I look at the CPU profile of these benchmarking functions, the assignment to a slice element or a comparision of 2 slice elements takes about 200 milliseconds and is one of the longest time-taking instruction. On Saturday, 15 February 2020 22:24:02 UTC+11, envee wrote: > > Hi, > > I am using the following version of go : > PS C:\Temp> go version > go version go1.13.5 windows/amd64 > > I am trying to compare the performance of QuickUnion (just the basic one > with no weighting/ranking/path compression) and QuickFind algorithms. > My benchmarking test cases basically create a (N-1) element array of pairs > starting with (0,1), (1,2).....all the way upto (N-2,N-1). N is the number > of data points. The benchmarking is run for N=10,100,1000. > The benchmark functions then test the union-find functions by creating all > the connections from the above array of pairs. > > From the Benchmarking results, it appears that the QuickUnion function is > taking longer than the QuickFind function for the same data size and same > set of input pairs. > > QuickFind benchmarking results. > Running tool: C:\Go\bin\go.exe test -benchmem -run=^$ github.com\en-vee\ds > -bench ^(BenchmarkQuickFind)$ > > goos: windows > goarch: amd64 > pkg: github.com/en-vee/ds > BenchmarkQuickFind/BenchmarkQuickFind10-8 36392860 > 33.2 ns/op 0 B/op 0 allocs/op > BenchmarkQuickFind/BenchmarkQuickFind100-8 3199598 388 > ns/op 0 B/op 0 allocs/op > BenchmarkQuickFind/BenchmarkQuickFind1000-8 300667 3852 > ns/op 0 B/op 0 allocs/op > PASS > ok github.com/en-vee/ds 5.064s > Success: Benchmarks passed. > > > QuickUnion benchmarking results (See how much poorly they perform as > compared to the QuickFind. The times per op are almost double the times for > QuickFind). > > Running tool: C:\Go\bin\go.exe test -benchmem -run=^$ github.com\en-vee\ds > -bench ^(BenchmarkQuickUnion)$ > > goos: windows > goarch: amd64 > pkg: github.com/en-vee/ds > BenchmarkQuickUnion/BenchmarkQuickUnion10-8 17946232 > 65.4 ns/op 0 B/op 0 allocs/op > BenchmarkQuickUnion/BenchmarkQuickUnion100-8 88495 > 13516 ns/op 0 B/op 0 allocs/op > BenchmarkQuickUnion/BenchmarkQuickUnion1000-8 907 > 1328386 ns/op 0 B/op 0 allocs/op > PASS > ok github.com/en-vee/ds 4.691s > Success: Benchmarks passed. > > > Upon CPU profiling the BenchmarkQuickUnion test case, the two for loops > related to the Find operation take the maximum time, but I am surprised > that this function is taking longer because the loop does not iterate > through the entire Array for any pair. Infact, I guess the loops must be > performing only 2 iterations per pair. (Also see the pprof CPU profile > attachment which shows the QuickUnion CPU profile for my function > QuickUnion). > > Is the array lookup being performed by the Find operations in QuickUnion > function, causing this performance bottleneck ? > > I guess the most surprising bit is : Why is QuickUnion slower than > QuickFind for the same input data ? Or am I missing something ? > > (BTW, the implementations are based on Robert Sedgwick's equivalent > implementations in C as given in his excellent book) > > The QuickUnion and Quick Find functions are as below : > > QuickUnion > > func quickUnion(a []int, p, q int) { > var i, j int > > for i = p; i != a[i]; i = a[i] { > } > > for j = q; j != a[j]; j = a[j] { > } > > if i != j { > a[i] = j > } > } > > QuickFind > > func qf(a []int, p, q int) { > // Are p and q already connected ? > if a[p] == a[q] { > return > } > > n := len(a) > > for i := 0; i < n; i++ { > if a[i] == a[p] { > a[i] = a[q] > } > } > } > > My benchmarking test cases : > > BenchmarkQuickUnion > > func BenchmarkQuickUnion(b *testing.B) { > type pair struct { > X int > Y int > } > bmTestSizes := []int{10, 100, 1000} > for _, bmTestSize := range bmTestSizes { > a := createSlice(bmTestSize) > pairs := make([]pair, bmTestSize-1, bmTestSize-1) > // Create a slice of pairs > for j := 0; j < bmTestSize-1; j++ { > pairs[j] = pair{j, j + 1} > } > pLen := bmTestSize - 1 > //b.ResetTimer() > bmName := fmt.Sprintf("BenchmarkQuickUnion%d", bmTestSize) > b.Run(bmName, func(b *testing.B) { > for i := 0; i < b.N; i++ { > for j := 0; j < pLen; j++ { > quickUnion(a, pairs[j].X, pairs[j].Y) > } > //qf(a, (len(a)-1)/2, len(a)-1) > } > }) > } > } > > > BenchmarkQuickFind > func BenchmarkQuickFind(b *testing.B) { > > type pair struct { > X int > Y int > } > bmTestSizes := []int{10, 100, 1000, 10000, 100000} > for _, bmTestSize := range bmTestSizes { > a := createSlice(bmTestSize) > pairs := make([]pair, bmTestSize-1, bmTestSize-1) > // Create a slice of pairs > for j := 0; j < bmTestSize-1; j++ { > pairs[j] = pair{j, j + 1} > } > pLen := bmTestSize - 1 > > bmName := fmt.Sprintf("BenchmarkQuickFind%d", bmTestSize) > b.Run(bmName, func(b *testing.B) { > for i := 0; i < b.N; i++ { > for j := 0; j < pLen; j++ { > qf(a, pairs[j].X, pairs[j].Y) > } > } > }) > } > } > Enter code here... > > > -- You received this message because you are subscribed to the Google Groups "golang-nuts" group. To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/d3a10abd-3463-47e4-8569-74bc11ef1ba1%40googlegroups.com.