Skip to content
This repository was archived by the owner on Dec 28, 2022. It is now read-only.

Commit 84111d8

Browse files
authored
Improve bitfield.setRange() (#113)
* Improve `bitfield.setRange()` * Clean up remote bitfield as well * Use `bits.fill()` * Typo * Add `remoteBitfield.setRange()`
1 parent 66a644a commit 84111d8

File tree

4 files changed

+81
-43
lines changed

4 files changed

+81
-43
lines changed

lib/bitfield.js

Lines changed: 51 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
// TODO: needs massive improvements obvs
2-
31
const BigSparseArray = require('big-sparse-array')
42
const b4a = require('b4a')
3+
const bits = require('bits-to-bytes')
54

65
class FixedBitfield {
76
constructor (index, bitfield) {
@@ -11,26 +10,17 @@ class FixedBitfield {
1110
}
1211

1312
get (index) {
14-
const j = index & 31
15-
const i = (index - j) / 32
16-
17-
return i < this.bitfield.length && (this.bitfield[i] & (1 << j)) !== 0
13+
return bits.get(this.bitfield, index)
1814
}
1915

2016
set (index, val) {
21-
const j = index & 31
22-
const i = (index - j) / 32
23-
const v = this.bitfield[i]
24-
25-
if (val === ((v & (1 << j)) !== 0)) return false
26-
27-
const u = val
28-
? v | (1 << j)
29-
: v ^ (1 << j)
30-
31-
if (u === v) return false
17+
return bits.set(this.bitfield, index, val)
18+
}
3219

33-
this.bitfield[i] = u
20+
setRange (start, length, val) {
21+
// Using fill instead of setRange is ~2 orders of magnitude faster, but does
22+
// have the downside of not being able to tell if any bits actually changed.
23+
bits.fill(this.bitfield, val, start, start + length)
3424
return true
3525
}
3626
}
@@ -44,7 +34,11 @@ module.exports = class Bitfield {
4434
this.resumed = !!(buf && buf.byteLength >= 4)
4535

4636
const all = this.resumed
47-
? new Uint32Array(buf.buffer, buf.byteOffset, Math.floor(buf.byteLength / 4))
37+
? new Uint32Array(
38+
buf.buffer,
39+
buf.byteOffset,
40+
Math.floor(buf.byteLength / 4)
41+
)
4842
: new Uint32Array(1024)
4943

5044
for (let i = 0; i < all.length; i += 1024) {
@@ -55,33 +49,52 @@ module.exports = class Bitfield {
5549
}
5650

5751
get (index) {
58-
const j = index & 32767
59-
const i = (index - j) / 32768
52+
const j = index & (this.pageSize - 1)
53+
const i = (index - j) / this.pageSize
54+
6055
const p = this.pages.get(i)
6156

6257
return p ? p.get(j) : false
6358
}
6459

6560
set (index, val) {
66-
const j = index & 32767
67-
const i = (index - j) / 32768
61+
const j = index & (this.pageSize - 1)
62+
const i = (index - j) / this.pageSize
6863

6964
let p = this.pages.get(i)
7065

71-
if (!p) {
72-
if (!val) return
66+
if (!p && val) {
7367
p = this.pages.set(i, new FixedBitfield(i, new Uint32Array(1024)))
7468
}
7569

76-
if (!p.set(j, val) || p.dirty) return
77-
78-
p.dirty = true
79-
this.unflushed.push(p)
70+
if (p && p.set(j, val) && !p.dirty) {
71+
p.dirty = true
72+
this.unflushed.push(p)
73+
}
8074
}
8175

8276
setRange (start, length, val) {
83-
for (let i = 0; i < length; i++) {
84-
this.set(start + i, val)
77+
let j = start & (this.pageSize - 1)
78+
let i = (start - j) / this.pageSize
79+
80+
while (length > 0) {
81+
let p = this.pages.get(i)
82+
83+
if (!p && val) {
84+
p = this.pages.set(i, new FixedBitfield(i, new Uint32Array(1024)))
85+
}
86+
87+
const end = Math.min(j + length, this.pageSize)
88+
const range = end - j
89+
90+
if (p && p.setRange(j, range, val) && !p.dirty) {
91+
p.dirty = true
92+
this.unflushed.push(p)
93+
}
94+
95+
j = 0
96+
i++
97+
length -= range
8598
}
8699
}
87100

@@ -120,7 +133,12 @@ module.exports = class Bitfield {
120133
let error = null
121134

122135
for (const page of this.unflushed) {
123-
const buf = b4a.from(page.bitfield.buffer, page.bitfield.byteOffset, page.bitfield.byteLength)
136+
const buf = b4a.from(
137+
page.bitfield.buffer,
138+
page.bitfield.byteOffset,
139+
page.bitfield.byteLength
140+
)
141+
124142
page.dirty = false
125143
this.storage.write(page.index * 4096, buf, done)
126144
}
@@ -151,7 +169,7 @@ module.exports = class Bitfield {
151169
}
152170

153171
function ensureSize (uint32, size) {
154-
if (uint32.length === size) return uint32
172+
if (uint32.byteLength === size) return uint32
155173
const a = new Uint32Array(1024)
156174
a.set(uint32, 0)
157175
return a

lib/remote-bitfield.js

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,45 @@
11
const BigSparseArray = require('big-sparse-array')
2+
const bits = require('bits-to-bytes')
23

34
module.exports = class RemoteBitfield {
45
constructor () {
6+
this.pageSize = 32768
57
this.pages = new BigSparseArray()
68
}
79

810
get (index) {
9-
const r = index & 32767
10-
const i = (index - r) / 32768
11+
const j = index & (this.pageSize - 1)
12+
const i = (index - j) / this.pageSize
13+
1114
const p = this.pages.get(i)
1215

13-
return p ? (p[r >>> 5] & (1 << (r & 31))) !== 0 : false
16+
return p ? bits.get(p, j) : false
1417
}
1518

1619
set (index, val) {
17-
const r = index & 32767
18-
const i = (index - r) / 32768
20+
const j = index & (this.pageSize - 1)
21+
const i = (index - j) / this.pageSize
22+
1923
const p = this.pages.get(i) || this.pages.set(i, new Uint32Array(1024))
2024

21-
if (val) p[r >>> 5] |= (1 << (r & 31))
22-
else p[r >>> 5] &= ~(1 << (r & 31))
25+
bits.set(p, j, val)
26+
}
27+
28+
setRange (start, length, val) {
29+
let j = start & (this.pageSize - 1)
30+
let i = (start - j) / this.pageSize
31+
32+
while (length > 0) {
33+
const p = this.pages.get(i) || this.pages.set(i, new Uint32Array(1024))
34+
35+
const end = Math.min(j + length, this.pageSize)
36+
const range = end - j
37+
38+
bits.fill(p, val, j, end)
39+
40+
j = 0
41+
i++
42+
length -= range
43+
}
2344
}
2445
}

lib/replicator.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -577,9 +577,7 @@ class Peer {
577577
onrange ({ drop, start, length }) {
578578
const has = drop === false
579579

580-
for (const end = start + length; start < end; start++) {
581-
this.remoteBitfield.set(start, has)
582-
}
580+
this.remoteBitfield.setRange(start, length, has)
583581

584582
if (drop === false) this._update()
585583
}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"@hyperswarm/secret-stream": "^6.0.0",
3636
"b4a": "^1.1.0",
3737
"big-sparse-array": "^1.0.2",
38+
"bits-to-bytes": "^1.2.0",
3839
"codecs": "^3.0.0",
3940
"compact-encoding": "^2.5.0",
4041
"crc32-universal": "^1.0.1",

0 commit comments

Comments
 (0)