Skip to content

Commit c668010

Browse files
authored
Efficient implementation of ColPack's buckets (#247)
* Efficient implementation of ColPack's buckets * Add missing order * Rename * Remove colpack repro warning
1 parent df101d7 commit c668010

File tree

3 files changed

+133
-115
lines changed

3 files changed

+133
-115
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "SparseMatrixColorings"
22
uuid = "0a514795-09f3-496d-8182-132a7b665d35"
33
authors = ["Guillaume Dalle", "Alexis Montoison"]
4-
version = "0.4.17"
4+
version = "0.4.18"
55

66
[deps]
77
ADTypes = "47edcb42-4c32-4615-8424-f2b9edc5f35b"

src/order.jl

Lines changed: 120 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -106,12 +106,6 @@ function vertices(bg::BipartiteGraph{T}, ::Val{side}, ::LargestFirst) where {T,s
106106
return sort!(visited; by=criterion, rev=true)
107107
end
108108

109-
const COLPACK_WARNING = """
110-
!!! danger
111-
The option `reproduce_colpack=true` induces a large slowdown to mirror the original implementation details of ColPack, it should not be used in performance-sensitive applications.
112-
This setting is mostly for the purpose of reproducing past research results which rely on implementation details.
113-
"""
114-
115109
"""
116110
DynamicDegreeBasedOrder{degtype,direction}(; reproduce_colpack=false)
117111
@@ -137,36 +131,56 @@ This order works by assigning vertices to buckets based on their dynamic degree,
137131
- When `reproduce_colpack=false` (the default), we can append and remove vertices either at the start or at the end of a bucket (bilateral).
138132
139133
Allowing modifications on both sides of a bucket enables storage optimization, with a single fixed-size vector for all buckets instead of one dynamically-sized vector per bucket.
140-
Our implementation is optimized for this bilateral setting, which means we pay a large performance penalty to artificially imitate the unilateral setting.
141-
142-
$COLPACK_WARNING
134+
As a result, the default setting `reproduce_colpack=false` is slightly more memory-efficient.
143135
144136
# References
145137
146138
- [_ColPack: Software for graph coloring and related problems in scientific computing_](https://dl.acm.org/doi/10.1145/2513109.2513110), Gebremedhin et al. (2013), Section 5
147139
"""
148-
struct DynamicDegreeBasedOrder{degtype,direction} <: AbstractOrder
149-
reproduce_colpack::Bool
150-
end
140+
struct DynamicDegreeBasedOrder{degtype,direction,reproduce_colpack} <: AbstractOrder end
151141

152142
function DynamicDegreeBasedOrder{degtype,direction}(;
153143
reproduce_colpack::Bool=false
154144
) where {degtype,direction}
155-
return DynamicDegreeBasedOrder{degtype,direction}(reproduce_colpack)
145+
return DynamicDegreeBasedOrder{degtype,direction,reproduce_colpack}()
146+
end
147+
148+
abstract type AbstractDegreeBuckets{T} end
149+
150+
struct DegreeBucketsColPack{T} <: AbstractDegreeBuckets{T}
151+
degrees::Vector{T}
152+
buckets::Vector{Vector{T}}
153+
positions::Vector{T}
156154
end
157155

158-
struct DegreeBuckets{T}
156+
struct DegreeBucketsSMC{T} <: AbstractDegreeBuckets{T}
159157
degrees::Vector{T}
160158
bucket_storage::Vector{T}
161159
bucket_low::Vector{T}
162160
bucket_high::Vector{T}
163161
positions::Vector{T}
164-
reproduce_colpack::Bool
165162
end
166163

167-
function DegreeBuckets(
168-
::Type{T}, degrees::Vector{T}, dmax::Integer; reproduce_colpack::Bool
169-
) where {T}
164+
function DegreeBucketsColPack(::Type{T}, degrees::Vector{T}, dmax::Integer) where {T}
165+
# number of vertices per degree class
166+
deg_count = zeros(T, dmax + 1)
167+
for d in degrees
168+
deg_count[d + 1] += 1
169+
end
170+
# one vector per bucket
171+
buckets = [Vector{T}(undef, deg_count[d + 1]) for d in 0:dmax]
172+
positions = similar(degrees, T)
173+
# assign each vertex to the correct local position inside its bucket
174+
for v in eachindex(positions, degrees)
175+
d = degrees[v]
176+
positions[v] = length(buckets[d + 1]) - deg_count[d + 1] + 1
177+
buckets[d + 1][positions[v]] = v
178+
deg_count[d + 1] -= 1
179+
end
180+
return DegreeBucketsColPack(degrees, buckets, positions)
181+
end
182+
183+
function DegreeBucketsSMC(::Type{T}, degrees::Vector{T}, dmax::Integer) where {T}
170184
# number of vertices per degree class
171185
deg_count = zeros(T, dmax + 1)
172186
for d in degrees
@@ -177,7 +191,7 @@ function DegreeBuckets(
177191
bucket_low = similar(bucket_high)
178192
bucket_low[1] = 1
179193
bucket_low[2:end] .= @view(bucket_high[1:(end - 1)]) .+ 1
180-
# assign each vertex to the correct position inside its degree class
194+
# assign each vertex to the correct global position inside its bucket
181195
bucket_storage = similar(degrees, T)
182196
positions = similar(degrees, T)
183197
for v in eachindex(positions, degrees)
@@ -186,12 +200,18 @@ function DegreeBuckets(
186200
bucket_storage[positions[v]] = v
187201
deg_count[d + 1] -= 1
188202
end
189-
return DegreeBuckets(
190-
degrees, bucket_storage, bucket_low, bucket_high, positions, reproduce_colpack
191-
)
203+
return DegreeBucketsSMC(degrees, bucket_storage, bucket_low, bucket_high, positions)
192204
end
193205

194-
maxdeg(db::DegreeBuckets) = length(db.bucket_low) - 1
206+
maxdeg(db::DegreeBucketsColPack) = length(db.buckets) - 1
207+
maxdeg(db::DegreeBucketsSMC) = length(db.bucket_low) - 1
208+
209+
function nonempty_bucket(db::DegreeBucketsSMC, d::Integer)
210+
return db.bucket_high[d + 1] >= db.bucket_low[d + 1]
211+
end
212+
function nonempty_bucket(db::DegreeBucketsColPack, d::Integer)
213+
return !isempty(db.buckets[d + 1])
214+
end
195215

196216
function degree_increasing(; degtype, direction)
197217
increasing =
@@ -200,78 +220,52 @@ function degree_increasing(; degtype, direction)
200220
return increasing
201221
end
202222

203-
function mark_ordered!(db::DegreeBuckets{T}, v::Integer) where {T}
223+
function mark_ordered!(db::AbstractDegreeBuckets{T}, v::Integer) where {T}
204224
db.degrees[v] = -1
205225
db.positions[v] = typemin(T)
206226
return nothing
207227
end
208228

209-
already_ordered(db::DegreeBuckets, v::Integer) = db.degrees[v] == -1
229+
already_ordered(db::AbstractDegreeBuckets, v::Integer) = db.degrees[v] == -1
210230

211-
function pop_next_candidate!(db::DegreeBuckets; direction::Symbol)
212-
(; bucket_storage, bucket_low, bucket_high) = db
231+
function pop_next_candidate!(db::AbstractDegreeBuckets; direction::Symbol)
213232
dmax = maxdeg(db)
214233
if direction == :low2high
215234
candidate_degree = dmax + 1
216235
for d in dmax:-1:0
217-
if bucket_high[d + 1] >= bucket_low[d + 1] # not empty
236+
if nonempty_bucket(db, d)
218237
candidate_degree = d
219238
break
220239
end
221240
end
222241
else
223242
candidate_degree = -1
224243
for d in 0:dmax
225-
if bucket_high[d + 1] >= bucket_low[d + 1] # not empty
244+
if nonempty_bucket(db, d)
226245
candidate_degree = d
227246
break
228247
end
229248
end
230249
end
231-
high = bucket_high[candidate_degree + 1]
232-
candidate = bucket_storage[high]
233-
bucket_storage[high] = -1
234-
bucket_high[candidate_degree + 1] -= 1
250+
if db isa DegreeBucketsColPack
251+
(; buckets) = db
252+
bucket = buckets[candidate_degree + 1]
253+
candidate = pop!(bucket)
254+
else
255+
(; bucket_storage, bucket_high) = db
256+
high = bucket_high[candidate_degree + 1]
257+
candidate = bucket_storage[high]
258+
bucket_storage[high] = -1
259+
bucket_high[candidate_degree + 1] -= 1
260+
end
235261
mark_ordered!(db, candidate)
236262
return candidate
237263
end
238264

239-
function rotate_bucket_left!(db::DegreeBuckets, d::Integer)
240-
(; bucket_storage, bucket_high, bucket_low, positions) = db
241-
low, high = bucket_low[d + 1], bucket_high[d + 1]
242-
# remember first element v
243-
v = bucket_storage[low]
244-
# shift everyone else one index down
245-
for i in (low + 1):high
246-
w = bucket_storage[i]
247-
bucket_storage[i - 1] = w
248-
positions[w] = i - 1
249-
end
250-
# put v back at the end
251-
bucket_storage[high] = v
252-
positions[v] = high
253-
return nothing
254-
end
255-
256-
function rotate_bucket_right!(db::DegreeBuckets, d::Integer)
257-
(; bucket_storage, bucket_high, bucket_low, positions) = db
258-
low, high = bucket_low[d + 1], bucket_high[d + 1]
259-
# remember last element v
260-
v = bucket_storage[high]
261-
# shift everyone else one index up
262-
for i in (high - 1):-1:low
263-
w = bucket_storage[i]
264-
bucket_storage[i + 1] = w
265-
positions[w] = i + 1
266-
end
267-
# put v back at the start
268-
bucket_storage[low] = v
269-
positions[v] = low
270-
return nothing
271-
end
272-
273-
function update_bucket!(db::DegreeBuckets, v::Integer; degtype::Symbol, direction::Symbol)
274-
(; degrees, bucket_storage, bucket_low, bucket_high, positions, reproduce_colpack) = db
265+
function update_bucket!(
266+
db::DegreeBucketsSMC, v::Integer; degtype::Symbol, direction::Symbol
267+
)
268+
(; degrees, bucket_storage, bucket_low, bucket_high, positions) = db
275269
d, p = degrees[v], positions[v]
276270
low, high = bucket_low[d + 1], bucket_high[d + 1]
277271
# select previous or next bucket for the move
@@ -292,27 +286,11 @@ function update_bucket!(db::DegreeBuckets, v::Integer; degtype::Symbol, directio
292286
# update v's stats
293287
degrees[v] = d_new
294288
positions[v] = low_new - 1
295-
if reproduce_colpack
296-
# move v from start to end of the next bucket, preserving order
297-
rotate_bucket_left!(db, d_new) # expensive
298-
end
299289
else
300-
if reproduce_colpack
301-
# move the vertex w located at the end of the current bucket to v's position
302-
w = bucket_storage[high]
303-
bucket_storage[p] = w
304-
positions[w] = p
305-
# explicitly put v at the end
306-
bucket_storage[high] = v
307-
positions[v] = high
308-
# move v from end to start of the current bucket, preserving order
309-
rotate_bucket_right!(db, d) # expensive
310-
else
311-
# move the vertex w located at the start of the current bucket to v's position (!= ColPack)
312-
w = bucket_storage[low]
313-
bucket_storage[p] = w
314-
positions[w] = p
315-
end
290+
# move the vertex w located at the start of the current bucket to v's position (!= ColPack)
291+
w = bucket_storage[low]
292+
bucket_storage[p] = w
293+
positions[w] = p
316294
# shrink current bucket from the left
317295
# morally we put v at the start and then ignore it
318296
bucket_low[d + 1] += 1
@@ -329,15 +307,42 @@ function update_bucket!(db::DegreeBuckets, v::Integer; degtype::Symbol, directio
329307
return nothing
330308
end
331309

310+
function update_bucket!(
311+
db::DegreeBucketsColPack, v::Integer; degtype::Symbol, direction::Symbol
312+
)
313+
(; degrees, buckets, positions) = db
314+
d, p = degrees[v], positions[v]
315+
bucket = buckets[d + 1]
316+
# select previous or next bucket for the move
317+
d_new = degree_increasing(; degtype, direction) ? d + 1 : d - 1
318+
bucket_new = buckets[d_new + 1]
319+
# put v at the end of its bucket by swapping
320+
w = bucket[end]
321+
bucket[p] = w
322+
positions[w] = p
323+
bucket[end] = v
324+
positions[v] = length(bucket)
325+
# move v from the old bucket to the new one
326+
@assert pop!(bucket) == v
327+
push!(bucket_new, v)
328+
degrees[v] = d_new
329+
positions[v] = length(bucket_new)
330+
return nothing
331+
end
332+
332333
function vertices(
333-
g::AdjacencyGraph{T}, order::DynamicDegreeBasedOrder{degtype,direction}
334-
) where {T<:Integer,degtype,direction}
334+
g::AdjacencyGraph{T}, ::DynamicDegreeBasedOrder{degtype,direction,reproduce_colpack}
335+
) where {T<:Integer,degtype,direction,reproduce_colpack}
335336
true_degrees = degrees = T[degree(g, v) for v in vertices(g)]
336337
max_degrees = maximum(true_degrees)
337338
if degree_increasing(; degtype, direction)
338339
fill!(degrees, zero(T))
339340
end
340-
db = DegreeBuckets(T, degrees, max_degrees; reproduce_colpack=order.reproduce_colpack)
341+
db = if reproduce_colpack
342+
DegreeBucketsColPack(T, degrees, max_degrees)
343+
else
344+
DegreeBucketsSMC(T, degrees, max_degrees)
345+
end
341346
nv = nb_vertices(g)
342347
π = Vector{T}(undef, nv)
343348
index_π = (direction == :low2high) ? (1:nv) : (nv:-1:1)
@@ -354,8 +359,10 @@ function vertices(
354359
end
355360

356361
function vertices(
357-
g::BipartiteGraph{T}, ::Val{side}, order::DynamicDegreeBasedOrder{degtype,direction}
358-
) where {T<:Integer,side,degtype,direction}
362+
g::BipartiteGraph{T},
363+
::Val{side},
364+
::DynamicDegreeBasedOrder{degtype,direction,reproduce_colpack},
365+
) where {T<:Integer,side,degtype,direction,reproduce_colpack}
359366
other_side = 3 - side
360367
# compute dist-2 degrees in an optimized way
361368
n = nb_vertices(g, Val(side))
@@ -375,7 +382,11 @@ function vertices(
375382
if degree_increasing(; degtype, direction)
376383
fill!(degrees, zero(T))
377384
end
378-
db = DegreeBuckets(T, degrees, maxd2; reproduce_colpack=order.reproduce_colpack)
385+
db = if reproduce_colpack
386+
DegreeBucketsColPack(T, degrees, maxd2)
387+
else
388+
DegreeBucketsSMC(T, degrees, maxd2)
389+
end
379390
π = Vector{T}(undef, n)
380391
index_π = (direction == :low2high) ? (1:n) : (n:-1:1)
381392
for index in index_π
@@ -400,39 +411,39 @@ end
400411
401412
Instance of [`AbstractOrder`](@ref) which sorts vertices from lowest to highest using the dynamic back degree.
402413
403-
$COLPACK_WARNING
404-
405414
# See also
406415
407416
- [`DynamicDegreeBasedOrder`](@ref)
408417
"""
409-
const IncidenceDegree = DynamicDegreeBasedOrder{:back,:low2high}
418+
function IncidenceDegree(; reproduce_colpack::Bool=false)
419+
return DynamicDegreeBasedOrder{:back,:low2high,reproduce_colpack}()
420+
end
410421

411422
"""
412423
SmallestLast(; reproduce_colpack=false)
413424
414425
Instance of [`AbstractOrder`](@ref) which sorts vertices from highest to lowest using the dynamic back degree.
415426
416-
$COLPACK_WARNING
417-
418427
# See also
419428
420429
- [`DynamicDegreeBasedOrder`](@ref)
421430
"""
422-
const SmallestLast = DynamicDegreeBasedOrder{:back,:high2low}
431+
function SmallestLast(; reproduce_colpack::Bool=false)
432+
return DynamicDegreeBasedOrder{:back,:high2low,reproduce_colpack}()
433+
end
423434

424435
"""
425436
DynamicLargestFirst(; reproduce_colpack=false)
426437
427438
Instance of [`AbstractOrder`](@ref) which sorts vertices from lowest to highest using the dynamic forward degree.
428439
429-
$COLPACK_WARNING
430-
431440
# See also
432441
433442
- [`DynamicDegreeBasedOrder`](@ref)
434443
"""
435-
const DynamicLargestFirst = DynamicDegreeBasedOrder{:forward,:low2high}
444+
function DynamicLargestFirst(; reproduce_colpack::Bool=false)
445+
return DynamicDegreeBasedOrder{:forward,:low2high,reproduce_colpack}()
446+
end
436447

437448
"""
438449
PerfectEliminationOrder(elimination_algorithm=CliqueTrees.MCS())
@@ -461,7 +472,12 @@ function all_orders()
461472
RandomOrder(),
462473
LargestFirst(),
463474
SmallestLast(),
475+
SmallestLast(; reproduce_colpack=true),
464476
IncidenceDegree(),
477+
IncidenceDegree(; reproduce_colpack=true),
465478
DynamicLargestFirst(),
479+
DynamicLargestFirst(; reproduce_colpack=true),
480+
DynamicDegreeBasedOrder{:forward,:high2low}(),
481+
DynamicDegreeBasedOrder{:forward,:high2low}(; reproduce_colpack=true),
466482
]
467483
end

0 commit comments

Comments
 (0)