Skip to content

Improve BitSet.count performance by ~3x - #702

Merged
lorentey merged 1 commit into
mainfrom
dn/bit-set-count
Aug 18, 2026
Merged

Improve BitSet.count performance by ~3x#702
lorentey merged 1 commit into
mainfrom
dn/bit-set-count

Conversation

@dnadoba

@dnadoba dnadoba commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Uncheck arithmetic unlocks loop unrolling and vectorization which gives us a good ~3x performance boost, already starting at ~2K bits in the BitSet.

PNG image

Small BitSet's <512 bits got a tiny bit slower but the error bands overlap and it is at ~3ns (total runtime, not per element) so not really anything I'm worried about.
PNG image

The assembly is now quite a bit larger (loop unrolling for 4 UInt's AFAICT) so I have removed the @inline(__always) annotation.

Assembly

Before:

merged BitCollections.BitSet.count.getter : Swift.Int: ; @"merged BitCollections.BitSet.count.getter : Swift.Int"
; %bb.0:
	ldr	x9, [x0, #16]
	cbz	x9, LBB109_5
; %bb.1:
	mov	x8, x0
	mov	x0, #0                          ; =0x0
	lsl	x9, x9, #3
	add	x8, x8, #32
LBB109_2:                               ; =>This Inner Loop Header: Depth=1
	ldr	d0, [x8], #8
	cnt.8b	v0, v0
	addv.8b	b0, v0
	fmov	x10, d0
	adds	x0, x0, x10
	b.vs	LBB109_6
; %bb.3:                                ;   in Loop: Header=BB109_2 Depth=1
	subs	x9, x9, #8
	b.ne	LBB109_2
; %bb.4:
	ret
LBB109_5:
	mov	x0, #0                          ; =0x0
	ret
LBB109_6:
	brk	#544 

After:

merged BitCollections.BitSet.count.getter : Swift.Int: ; @"merged BitCollections.BitSet.count.getter : Swift.Int"
; %bb.0:
	ldr	x8, [x0, #16]
	cbz	x8, LBB109_8
; %bb.1:
	lsl	x10, x8, #3
	add	x9, x0, #32
	sub	x8, x10, #8
	cmp	x8, #56
	b.hs	LBB109_3
; %bb.2:
	mov	x8, #0                          ; =0x0
	b	LBB109_6
LBB109_3:
	lsr	x8, x8, #3
	add	x11, x8, #1
	and	x12, x11, #0x3ffffffffffffff8
	add	x9, x9, x12, lsl #3
	add	x8, x0, #80
	movi.2d	v0, #0000000000000000
	movi.16b	v1, #1
	mov	x13, x12
	movi.2d	v2, #0000000000000000
	movi.2d	v3, #0000000000000000
	movi.2d	v4, #0000000000000000
LBB109_4:                               ; =>This Inner Loop Header: Depth=1
	ldp	q5, q6, [x8, #-48]
	ldp	q7, q16, [x8, #-16]
	cnt.16b	v5, v5
	movi.2d	v17, #0000000000000000
	udot.4s	v17, v1, v5
	cnt.16b	v5, v6
	movi.2d	v6, #0000000000000000
	udot.4s	v6, v1, v5
	cnt.16b	v5, v7
	movi.2d	v7, #0000000000000000
	udot.4s	v7, v1, v5
	cnt.16b	v5, v16
	movi.2d	v16, #0000000000000000
	udot.4s	v16, v1, v5
	uadalp.2d	v0, v17
	uadalp.2d	v2, v6
	uadalp.2d	v3, v7
	uadalp.2d	v4, v16
	add	x8, x8, #64
	subs	x13, x13, #8
	b.ne	LBB109_4
; %bb.5:
	add.2d	v0, v2, v0
	add.2d	v1, v4, v3
	add.2d	v0, v1, v0
	addp.2d	d0, v0
	fmov	x8, d0
	cmp	x11, x12
	b.eq	LBB109_8
LBB109_6:
	add	x10, x0, x10
	add	x10, x10, #32
LBB109_7:                               ; =>This Inner Loop Header: Depth=1
	ldr	d0, [x9], #8
	cnt.8b	v0, v0
	addv.8b	b0, v0
	fmov	x11, d0
	add	x8, x11, x8
	cmp	x9, x10
	b.ne	LBB109_7
LBB109_8:
	mov	x0, x8
	ret

Checklist

  • I've read the Contribution Guidelines
  • My contributions are licensed under the Swift license.
  • I've followed the coding style of the rest of the project.
  • I've added tests covering all new code paths my change adds to the project (if appropriate).
  • I've added benchmarks covering new functionality (if appropriate).
  • I've verified that my change does not break any existing tests or introduce unexplained benchmark regressions.
  • I've updated the documentation if necessary.

@dnadoba
dnadoba requested a review from lorentey as a code owner August 13, 2026 21:11
@inline(__always)
package var count: Int {
_words.reduce(0) { $0 + $1.count }
assert(_words.count <= Int.max / _Word.capacity)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIUC BitSet only support Int.max bit's and it is not possible to insert more bits and therefore we can't overflow Int.
Might just remove that assert here again.

@dnadoba

dnadoba commented Aug 14, 2026

Copy link
Copy Markdown
Contributor Author

Oh and BitSet.Counted in general improves as essentially every operations calls into BitSet.count to update its cached .count.

@lorentey lorentey left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

@lorentey lorentey added this to the 1.7.0 milestone Aug 18, 2026
@lorentey
lorentey merged commit f425dff into main Aug 18, 2026
75 checks passed
@lorentey
lorentey deleted the dn/bit-set-count branch August 18, 2026 20:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants