I have a follow up question. I have not implemented the BNNS approach proposed above and it works. However, the calculation speed is still about 3 times as high when I use a similar approach in Python - without any particular special packages or approaches on Python side. I was hoping that Swift would generally be faster so wanted to get your view on whether maybe my approach is generally flawed and her any potential alternatives. I would love to continue with coding in Swift on the project but with runtime being 300% it is just too compelling to rather use Python.
Let me explain what I am doing: I create a lot of (random) binary trees which contain arithmetic or logic operations in all non-leaf nodes and numeric arrays (all of the same length) on leaf nodes which are taken from return_data
. I.e. my trees are representing (simple) mathematic formulas. I have tens of thousands such trees and need to efficiently evaluate them. I currently do this via a recursive function evaluate_signal
. As said above, the same approach in Python only takes 1/3 of the time to finish in a comparable setting. See below the code. There is obviously a lot of redundant code in evaluate_signal
but that shouldn't be a problem for the efficiency (I would obviously improve that, but first need to get the runtime tackled).
Please see below my code (snippet). I am happy to explain anything or provide more code/background if required. Would be really great to hear your thought and whether you think this could somehow be improved.
import Accelerate
enum NodeValue {
case add
case sub
case mul
case div
case sml
case lrg
case opp
case met(columnIndex: Int) // This is a column index of return_data
case cst(constant: Float) // This is a constant value
}
final class Node {
var value: NodeValue
var lhs: Node?
var rhs: Node?
init(value: NodeValue, lhs: Node?, rhs: Node?) {
self.value = value
self.lhs = lhs
self.rhs = rhs
}
func evaluate_signal(return_data: [Int: [Float]]) -> [Any] {
switch self.value {
case .add:
let eval_left = lhs!.evaluate_signal(return_data: return_data) as! [Float]
let eval_right = rhs!.evaluate_signal(return_data: return_data) as! [Float]
return vDSP.add(eval_left, eval_right)
case .sub:
let eval_left = lhs!.evaluate_signal(return_data: return_data) as! [Float]
let eval_right = rhs!.evaluate_signal(return_data: return_data) as! [Float]
return vDSP.subtract(eval_left, eval_right)
case .mul:
let eval_left = lhs!.evaluate_signal(return_data: return_data) as! [Float]
let eval_right = rhs!.evaluate_signal(return_data: return_data) as! [Float]
return vDSP.multiply(eval_left, eval_right)
case .div:
let eval_left = lhs!.evaluate_signal(return_data: return_data) as! [Float]
let eval_right = rhs!.evaluate_signal(return_data: return_data) as! [Float]
return vDSP.divide(eval_left, eval_right)
case .sml:
let eval_left = lhs!.evaluate_signal(return_data: return_data) as! [Float]
let eval_right = rhs!.evaluate_signal(return_data: return_data) as! [Float]
let leftDescriptor = BNNSNDArrayDescriptor.allocate(initializingFrom: eval_left, shape: .vector(eval_left.count))
let rightDescriptor = BNNSNDArrayDescriptor.allocate(initializingFrom: eval_right, shape: .vector(eval_left.count))
let resultDescriptor = BNNSNDArrayDescriptor.allocateUninitialized(scalarType: Bool.self, shape: .vector(eval_left.count))
try! BNNS.compare(leftDescriptor, rightDescriptor, using: .less, output: resultDescriptor)
let resultVector: [Bool] = resultDescriptor.makeArray(of: Bool.self)!
leftDescriptor.deallocate()
rightDescriptor.deallocate()
resultDescriptor.deallocate()
return resultVector
case .lrg:
let eval_left = lhs!.evaluate_signal(return_data: return_data) as! [Float]
let eval_right = rhs!.evaluate_signal(return_data: return_data) as! [Float]
let leftDescriptor = BNNSNDArrayDescriptor.allocate(initializingFrom: eval_left, shape: .vector(eval_left.count))
let rightDescriptor = BNNSNDArrayDescriptor.allocate(initializingFrom: eval_right, shape: .vector(eval_left.count))
let resultDescriptor = BNNSNDArrayDescriptor.allocateUninitialized(scalarType: Bool.self, shape: .vector(eval_left.count))
try! BNNS.compare(leftDescriptor, rightDescriptor, using: .greater, output: resultDescriptor)
let resultVector: [Bool] = resultDescriptor.makeArray(of: Bool.self)!
leftDescriptor.deallocate()
rightDescriptor.deallocate()
resultDescriptor.deallocate()
return resultVector
case .opp:
let eval_left = lhs!.evaluate_signal(return_data: return_data) as! [Bool]
let leftDescriptor = BNNSNDArrayDescriptor.allocate(initializingFrom: eval_left, shape: .vector(eval_left.count))
let resultDescriptor = BNNSNDArrayDescriptor.allocateUninitialized(scalarType: Bool.self, shape: .vector(eval_left.count))
try! BNNS.compare(leftDescriptor, leftDescriptor, using: .not, output: resultDescriptor)
let resultVector: [Bool] = resultDescriptor.makeArray(of: Bool.self)!
leftDescriptor.deallocate()
resultDescriptor.deallocate()
return resultVector
case .met(let columnIndex):
return return_data[columnIndex]!
case .cst(let constant):
return Array<Float>(repeating: constant, count: return_data[0]!.count)
}
}
}