Skip to content

Commit

Permalink
Merge pull request #28 from li3zhen1/dev
Browse files Browse the repository at this point in the history
Optimize performance
  • Loading branch information
li3zhen1 authored Nov 25, 2023
2 parents 5f70fc1 + cbc1b0b commit 1dbce40
Show file tree
Hide file tree
Showing 11 changed files with 164 additions and 162 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,9 +210,9 @@ See [Example](https://github.com/li3zhen1/Grape/tree/main/Examples/ForceDirected

#### Simulation

Grape uses simd to calculate position and velocity. Currently it takes ~0.014 seconds to iterate 120 times over the example graph(2D). (77 vertices, 254 edges, with manybody, center, collide and link forces. Release build on a M1 Max, tested with command `swift test -c release`)
Grape uses simd to calculate position and velocity. Currently it takes ~0.012 seconds to iterate 120 times over the example graph(2D). (77 vertices, 254 edges, with manybody, center, collide and link forces. Release build on a M1 Max, tested with command `swift test -c release`)

For 3D simulation, it takes ~0.019 seconds for the same graph and same configs.
For 3D simulation, it takes ~0.018 seconds for the same graph and same configs.

> [!IMPORTANT]
> Due to heavy use of generics (some are not specialized in Debug mode), the performance in Debug build is ~100x slower than Release build. Grape might ship a version with pre-inlined generics to address this problem.
Expand Down
5 changes: 2 additions & 3 deletions Sources/ForceSimulation/AttributeDescriptor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,14 @@ extension AttributeDescriptor {
count: count,
initialValue: m
)
case .varied(let radiusProvider):
case .varied(let valueProvider):
let array = UnsafeArray<T>.createBuffer(
withHeader: count,
count: count,
initialValue: .zero
)

for i in 0..<count {
array[i] = radiusProvider(i)
array[i] = valueProvider(i)
}
return array
}
Expand Down
7 changes: 4 additions & 3 deletions Sources/ForceSimulation/Forces/CenterForce.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@ extension Kinetics {
public func apply() {
assert(self.kinetics != nil, "Kinetics not bound to force")
var meanPosition = Vector.zero
let positionBufferPointer = kinetics!.position.withUnsafeMutablePointerToElements { $0 }
for i in kinetics.range {
meanPosition += kinetics.position[i] //.position
meanPosition += positionBufferPointer[i] //.position
}
let delta = meanPosition * (self.strength / Vector.Scalar(kinetics.validCount))

for i in 0..<kinetics.validCount {
kinetics.position[i] -= delta
for i in kinetics.range {
positionBufferPointer[i] -= delta
}
}
@inlinable
Expand Down
28 changes: 10 additions & 18 deletions Sources/ForceSimulation/Forces/CollideForce.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@ public struct MaxRadiusNDTreeDelegate<Vector>: KDTreeDelegate
where Vector: SimulatableVector {
@inlinable
public mutating func didAddNode(_ node: Int, at position: Vector) {
let p = radiusGetter(node)
let p = radiusBufferPointer[node]
maxNodeRadius = max(maxNodeRadius, p)
}

@usableFromInline
var radiusGetter: (Int) -> Vector.Scalar
var radiusBufferPointer: UnsafeMutablePointer<Vector.Scalar>

public var maxNodeRadius: Vector.Scalar = .zero

@inlinable
public mutating func didRemoveNode(_ node: Int, at position: Vector) {
if radiusGetter(node) >= maxNodeRadius {
if radiusBufferPointer[node] >= maxNodeRadius {
// 🤯 for Collide force, set to 0 is fine
// Otherwise you need to traverse the delegate again
maxNodeRadius = 0
Expand All @@ -27,15 +27,15 @@ where Vector: SimulatableVector {

@inlinable
public func spawn() -> MaxRadiusNDTreeDelegate<Vector> {
return Self(radiusProvider: radiusGetter)
return Self(radiusBufferPointer: radiusBufferPointer)
}

// public typealias NodeID = Int

@inlinable
init(maxNodeRadius: Vector.Scalar = 0, radiusProvider: @escaping (Int) -> Vector.Scalar) {
init(maxNodeRadius: Vector.Scalar = 0, radiusBufferPointer: UnsafeMutablePointer<Vector.Scalar>) {
self.maxNodeRadius = maxNodeRadius
self.radiusGetter = radiusProvider
self.radiusBufferPointer = radiusBufferPointer
}
}

Expand Down Expand Up @@ -83,25 +83,17 @@ extension Kinetics {
assert(self.kinetics != nil, "Kinetics not bound to force")

let kinetics = self.kinetics!
let calculatedRadius = self.calculatedRadius!
let calculatedRadius = self.calculatedRadius!.withUnsafeMutablePointerToElements { $0 }
let strength = self.strength

for _ in 0..<iterationsPerTick {

// let coveringBox = KDBox<Vector>.cover(of: kinetics.position)

var tree = KDTree<Vector, MaxRadiusNDTreeDelegate<Vector>>(
covering: kinetics.position
) {
return switch self.radius {
case .constant(let m):
MaxRadiusNDTreeDelegate<Vector> { _ in m }
case .varied(_):
MaxRadiusNDTreeDelegate<Vector> { index in
self.calculatedRadius[index]
}
}
}
covering: kinetics.position,
rootDelegate: MaxRadiusNDTreeDelegate<Vector>(radiusBufferPointer: calculatedRadius)
)

// for i in kinetics.range {
// tree.add(i, at: kinetics.position[i])
Expand Down
180 changes: 90 additions & 90 deletions Sources/ForceSimulation/Forces/KDTreeForce.swift
Original file line number Diff line number Diff line change
@@ -1,104 +1,104 @@
public protocol KDTreeForce<Vector>: ForceProtocol
where
Vector: SimulatableVector & L2NormCalculatable
{
associatedtype Delegate: KDTreeDelegate where Delegate.Vector == Vector, Delegate.NodeID == Int

var kinetics: Kinetics<Vector>! { get set }

func epilogue()
func buildDelegate() -> Delegate
func visitForeignTree<D: KDTreeDelegate>(
tree: inout KDTree<Vector, D>, getDelegate: (D) -> Delegate)
}

public struct CompositedKDTreeDelegate<V, D1, D2>: KDTreeDelegate
where
V: SimulatableVector & L2NormCalculatable,
D1: KDTreeDelegate<Int, V>, D2: KDTreeDelegate<Int, V>
{
var d1: D1
var d2: D2

mutating public func didAddNode(_ node: Int, at position: V) {
d1.didAddNode(node, at: position)
d2.didAddNode(node, at: position)
}

mutating public func didRemoveNode(_ node: Int, at position: V) {
d1.didRemoveNode(node, at: position)
d2.didRemoveNode(node, at: position)
}

public func spawn() -> CompositedKDTreeDelegate<V, D1, D2> {
return .init(d1: d1.spawn(), d2: d2.spawn())
}

}

extension Kinetics.ManyBodyForce: KDTreeForce {
public typealias Delegate = MassCentroidKDTreeDelegate<Vector>

public func epilogue() {

}

public func buildDelegate() -> MassCentroidKDTreeDelegate<Vector> {
return .init(massProvider: { self.precalculatedMass[$0] })
}

public func visitForeignTree<D: KDTreeDelegate>(
tree: inout KDTree<Vector, D>, getDelegate: (D) -> MassCentroidKDTreeDelegate<Vector>
) {
// public protocol KDTreeForce<Vector>: ForceProtocol
// where
// Vector: SimulatableVector & L2NormCalculatable
// {
// associatedtype Delegate: KDTreeDelegate where Delegate.Vector == Vector, Delegate.NodeID == Int

// var kinetics: Kinetics<Vector>! { get set }

// func epilogue()
// func buildDelegate() -> Delegate
// func visitForeignTree<D: KDTreeDelegate>(
// tree: inout KDTree<Vector, D>, getDelegate: (D) -> Delegate)
// }

// public struct CompositedKDTreeDelegate<V, D1, D2>: KDTreeDelegate
// where
// V: SimulatableVector & L2NormCalculatable,
// D1: KDTreeDelegate<Int, V>, D2: KDTreeDelegate<Int, V>
// {
// var d1: D1
// var d2: D2

// mutating public func didAddNode(_ node: Int, at position: V) {
// d1.didAddNode(node, at: position)
// d2.didAddNode(node, at: position)
// }

// mutating public func didRemoveNode(_ node: Int, at position: V) {
// d1.didRemoveNode(node, at: position)
// d2.didRemoveNode(node, at: position)
// }

// public func spawn() -> CompositedKDTreeDelegate<V, D1, D2> {
// return .init(d1: d1.spawn(), d2: d2.spawn())
// }

// }

// extension Kinetics.ManyBodyForce: KDTreeForce {
// public typealias Delegate = MassCentroidKDTreeDelegate<Vector>

// public func epilogue() {

// }

// public func buildDelegate() -> MassCentroidKDTreeDelegate<Vector> {
// return .init(massProvider: { self.precalculatedMass[$0] })
// }

// public func visitForeignTree<D: KDTreeDelegate>(
// tree: inout KDTree<Vector, D>, getDelegate: (D) -> MassCentroidKDTreeDelegate<Vector>
// ) {

}
}
// }
// }

extension Kinetics.CollideForce: KDTreeForce {
public typealias Delegate = MaxRadiusNDTreeDelegate<Vector>
// extension Kinetics.CollideForce: KDTreeForce {
// public typealias Delegate = MaxRadiusNDTreeDelegate<Vector>

public func epilogue() {
// public func epilogue() {

}
// }

public func buildDelegate() -> MaxRadiusNDTreeDelegate<Vector> {
return .init(radiusProvider: { self.calculatedRadius[$0] })
}
// public func buildDelegate() -> MaxRadiusNDTreeDelegate<Vector> {
// return .init(radiusProvider: { self.calculatedRadius[$0] })
// }

public func visitForeignTree<D>(
tree: inout KDTree<Vector, D>, getDelegate: (D) -> MaxRadiusNDTreeDelegate<Vector>
) where D: KDTreeDelegate, Vector == D.Vector, D.NodeID == Int {
// public func visitForeignTree<D>(
// tree: inout KDTree<Vector, D>, getDelegate: (D) -> MaxRadiusNDTreeDelegate<Vector>
// ) where D: KDTreeDelegate, Vector == D.Vector, D.NodeID == Int {

}
}
// }
// }

public struct CompositedKDTreeForce<Vector, KF1, KF2>: ForceProtocol
where
KF1: KDTreeForce<Vector>, KF2: KDTreeForce<Vector>,
Vector: SimulatableVector & L2NormCalculatable,
KF1.Vector == Vector, KF2.Vector == Vector, KF1.Vector == Vector
{
var force1: KF1
var force2: KF2
// public struct CompositedKDTreeForce<Vector, KF1, KF2>: ForceProtocol
// where
// KF1: KDTreeForce<Vector>, KF2: KDTreeForce<Vector>,
// Vector: SimulatableVector & L2NormCalculatable,
// KF1.Vector == Vector, KF2.Vector == Vector, KF1.Vector == Vector
// {
// var force1: KF1
// var force2: KF2

public func apply() {
force1.epilogue()
force2.epilogue()
// public func apply() {
// force1.epilogue()
// force2.epilogue()

var tree = KDTree<Vector, CompositedKDTreeDelegate<Vector, KF1.Delegate, KF2.Delegate>>(
covering: force1.kinetics!.position,
rootDelegate: CompositedKDTreeDelegate(
d1: force1.buildDelegate(),
d2: force2.buildDelegate()
)
)
// var tree = KDTree<Vector, CompositedKDTreeDelegate<Vector, KF1.Delegate, KF2.Delegate>>(
// covering: force1.kinetics!.position,
// rootDelegate: CompositedKDTreeDelegate(
// d1: force1.buildDelegate(),
// d2: force2.buildDelegate()
// )
// )

force1.visitForeignTree(tree: &tree, getDelegate: \.d1)
force2.visitForeignTree(tree: &tree, getDelegate: \.d2)
}
// force1.visitForeignTree(tree: &tree, getDelegate: \.d1)
// force2.visitForeignTree(tree: &tree, getDelegate: \.d2)
// }

public mutating func bindKinetics(_ kinetics: Kinetics<Vector>) {
// public mutating func bindKinetics(_ kinetics: Kinetics<Vector>) {

}
// }

}
// }
2 changes: 1 addition & 1 deletion Sources/ForceSimulation/Forces/LinkForce.swift
Original file line number Diff line number Diff line change
Expand Up @@ -162,4 +162,4 @@ public struct LinkLookup<NodeID: Hashable> {
self.count = count
}

}
}
Loading

0 comments on commit 1dbce40

Please sign in to comment.