Skip to content

Commit f903e8d

Browse files
committed
Fix AMD64 inner loop with compiler-optimized unrolled pipeline
1 parent 5783cd7 commit f903e8d

5 files changed

Lines changed: 40 additions & 101 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ Alternatively, `VoteRanked` accepts a pre-ordered slice of ranks (`[][]C`), avoi
2727
- **Generic Ballot Ingestion**: `VoteFrom` enables voting directly from arbitrary external structures using a custom ranking function.
2828
- **Type transformations (`Map`)**: `Record`, `Result`, `Duel`, and `Voting` support Go method type parameters via `.Map(...)` to translate between choice identifiers (e.g. `UUID` $\leftrightarrow$ `string` $\leftrightarrow$ `int`).
2929
- **Standard Go iterators**: `Duels` provides a standard `iter.Seq[*Duel[C]]` iterator for range-over-func loops.
30-
- **Hardware SIMD Acceleration**: Uses ARM64 NEON and AMD64 AVX/SSE vector assembly kernels to accelerate inner matrix relaxation.
30+
- **Hardware SIMD Acceleration**: Uses ARM64 NEON vector assembly kernels and unrolled multi-core pipelines to accelerate inner matrix relaxation.
3131
- **Fast Winner Calculation**: `Winner` returns the election winner using an $O(N^2)$ Condorcet fast-path with fallback to full beatpath computation for cycles.
3232
- **Configurable numeric precision**: Pairwise preferences support generic `Number` types (`int`, `uint32`, `uint16`, etc.) for memory optimization.
3333

schulze_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1627,8 +1627,7 @@ func BenchmarkVoteRanked(b *testing.B) {
16271627
preferences := schulze.NewPreferences[int](choicesCount)
16281628
ranked := [][]string{{"0"}, {"1", "2"}, {"3"}}
16291629

1630-
b.ResetTimer()
1631-
for n := 0; n < b.N; n++ {
1630+
for b.Loop() {
16321631
if _, err := schulze.VoteRanked(preferences, choices, ranked); err != nil {
16331632
b.Fatal(err)
16341633
}

simd_amd64.go

Lines changed: 0 additions & 17 deletions
This file was deleted.

simd_amd64.s

Lines changed: 0 additions & 74 deletions
This file was deleted.

simd_generic.go

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,50 @@
33
// Use of this source code is governed by a BSD-style
44
// license that can be found in the LICENSE file.
55

6-
//go:build !arm64 && !amd64
6+
//go:build !arm64
77

88
package schulze
99

1010
import "unsafe"
1111

1212
func relaxRowInt(rowI, rowJ unsafe.Pointer, jip int, n int) {
13-
for k := 0; k < n; k++ {
14-
ikp := (*int)(unsafe.Add(rowI, uintptr(k)*8))
15-
jkp := (*int)(unsafe.Add(rowJ, uintptr(k)*8))
16-
m := min(jip, *ikp)
17-
if m > *jkp {
18-
*jkp = m
13+
rowISlice := unsafe.Slice((*int)(rowI), n)
14+
rowJSlice := unsafe.Slice((*int)(rowJ), n)
15+
16+
const step = 8
17+
mod := n % step
18+
end := n - mod
19+
20+
for k := 0; k < end; k += step {
21+
if m := min(jip, rowISlice[k]); m > rowJSlice[k] {
22+
rowJSlice[k] = m
23+
}
24+
if m := min(jip, rowISlice[k+1]); m > rowJSlice[k+1] {
25+
rowJSlice[k+1] = m
26+
}
27+
if m := min(jip, rowISlice[k+2]); m > rowJSlice[k+2] {
28+
rowJSlice[k+2] = m
29+
}
30+
if m := min(jip, rowISlice[k+3]); m > rowJSlice[k+3] {
31+
rowJSlice[k+3] = m
32+
}
33+
if m := min(jip, rowISlice[k+4]); m > rowJSlice[k+4] {
34+
rowJSlice[k+4] = m
35+
}
36+
if m := min(jip, rowISlice[k+5]); m > rowJSlice[k+5] {
37+
rowJSlice[k+5] = m
38+
}
39+
if m := min(jip, rowISlice[k+6]); m > rowJSlice[k+6] {
40+
rowJSlice[k+6] = m
41+
}
42+
if m := min(jip, rowISlice[k+7]); m > rowJSlice[k+7] {
43+
rowJSlice[k+7] = m
44+
}
45+
}
46+
47+
for k := end; k < n; k++ {
48+
if m := min(jip, rowISlice[k]); m > rowJSlice[k] {
49+
rowJSlice[k] = m
1950
}
2051
}
2152
}

0 commit comments

Comments
 (0)