Thanks Jake.
The logic about the slice being reused across iterations makes sense.
I will run these tests on my machine.

On Monday, 17 February 2020 08:48:12 UTC+11, Jake Montgomery wrote:
>
> Without speaking to the correctness of your algorithm, I can tell you why 
> your results are not as you expect. It is a classic benchmark mistake. You 
> are creating your slice, filled with ordered integers, then running your 
> code on the same slice again and again. So after the first iteration the 
> slice is "sorted" already. For whatever reason, QuickFind likes this 
> scenario a lot, and QuickUnion does not. What you really want is to use the 
> same initial data for each iteration. That means resetting the 'a' slice 
> inside the "for i := 0; i < b.N; i++" loop. When the code is modified like 
> this:
>
> package quickfind
>
> import (
>     "fmt"
>     "testing"
> )
>
> type pair struct {
>     X int
>     Y int
> }
>
> 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
>     }
> }
>
> 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]
>         }
>     }
> }
>
> func createSlice(n int) []int {
>     a := make([]int, n, n)
>     return a
> }
>
> func fillSlice(a []int) {
>     // initialize each element of the array to it's index value
>     for i := range a {
>         a[i] = i
>     }
> }
>
> func createPairs(bmTestSize int) []pair {
>     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}
>     }
>     return pairs
>
> }
>
> func BenchmarkQuickUnion(b *testing.B) {
>     bmTestSizes := []int{10, 100, 1000, 10000}
>     for _, bmTestSize := range bmTestSizes {
>         bmName := fmt.Sprintf("BenchmarkQuickUnion%d", bmTestSize)
>         b.Run(bmName, func(b *testing.B) {
>             b.StopTimer()
>             pairs := createPairs(bmTestSize)
>             a := createSlice(bmTestSize)
>             pLen := bmTestSize - 1
>             b.StartTimer()
>             for i := 0; i < b.N; i++ {
>                 b.StopTimer()
>                 fillSlice(a)
>                 b.StartTimer()
>                 for j := 0; j < pLen; j++ {
>                     quickUnion(a, pairs[j].X, pairs[j].Y)
>                 }
>             }
>         })
>     }
> }
>
> func BenchmarkQuickFind(b *testing.B) {
>
>     bmTestSizes := []int{10, 100, 1000, 10000}
>     for _, bmTestSize := range bmTestSizes {
>         bmName := fmt.Sprintf("BenchmarkQuickFind%d", bmTestSize)
>         b.Run(bmName, func(b *testing.B) {
>             b.StopTimer()
>             pairs := createPairs(bmTestSize)
>             pLen := bmTestSize - 1
>             a := createSlice(bmTestSize)
>             b.StartTimer()
>             for i := 0; i < b.N; i++ {
>                 b.StopTimer()
>                 fillSlice(a)
>                 b.StartTimer()
>                 for j := 0; j < pLen; j++ {
>                     qf(a, pairs[j].X, pairs[j].Y)
>                 }
>             }
>         })
>     }
> }
>
>
> I get: 
>
> BenchmarkQuickUnion/BenchmarkQuickUnion10-4              6483990         
>       158 ns/op
> BenchmarkQuickUnion/BenchmarkQuickUnion100-4             2472957         
>       531 ns/op
> BenchmarkQuickUnion/BenchmarkQuickUnion1000-4             142843         
>      7048 ns/op
> BenchmarkQuickUnion/BenchmarkQuickUnion10000-4             17023         
>     68428 ns/op
> BenchmarkQuickFind/BenchmarkQuickFind10-4                3646029         
>       328 ns/op
> BenchmarkQuickFind/BenchmarkQuickFind100-4                 88860         
>     13043 ns/op
> BenchmarkQuickFind/BenchmarkQuickFind1000-4                  774         
>   1488236 ns/op
> BenchmarkQuickFind/BenchmarkQuickFind10000-4                   7         
> 150857786 ns/op
> PASS
> ok      command-line-arguments  443.341s
>
> QuickUnion outperforms QuickFind in every case, and massively outperforms 
> on large sets. Again, I have no idea if your code is actually correct.
> Because the slice needs to be reconfigured for each run, it makes the high 
> iteration benchmarks (the ones with small sets) take a long time to run. On 
> my machine these took about 8 minutes, so be patient. 
>
>
> On Saturday, February 15, 2020 at 2:55:18 PM UTC-5, envee wrote:
>>
>> My apologies about syntax coloring.
>> Here is the createSlice function :
>>
>> func createSlice(n int) []int {
>> a := make([]int, n, n)
>> // initialize each element of the array to it's index value
>> for i := range a {
>> a[i] = i
>> }
>> return a
>> }
>>
>>
>>
>> On Sunday, 16 February 2020 06:31:49 UTC+11, Jake Montgomery wrote:
>>>
>>> On Saturday, February 15, 2020 at 6:24:02 AM UTC-5, 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...
>>>>
>>>>
>>>>
>>> As Roberts said, please post code without color please. Use plain text, 
>>> or a simple code block, or the playground.
>>> I would love to try to replicate your results, but the createSlice() 
>>> function is missing. Can you provide it?
>>>
>>

-- 
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/597965e4-20f5-4af7-8abe-fa83f3d180df%40googlegroups.com.

Reply via email to