Closed oscbyspro closed 1 year ago
Wrapping the two models in an enum makes encoding ~(2.04, 1.50)x slower, but branching on power.isZero
seems viable.
// MARK: Status Quo
[D10] passed (0.020 seconds)
[D16] passed (0.018 seconds)
[E10] passed (0.055 seconds)
[E16] passed (0.023 seconds)
// MARK: With AnyRadixUIntRoot (deleted Perfect & Imperfect & Protocol)
[D10] passed (0.020 seconds)
[D16] passed (0.018 seconds)
[E10] passed (0.055 seconds)
[E16] passed (0.027 seconds) // 0.028 when non-generic & non-@inlinable
I mean, a dedicated hex formatter is naturally faster so optimizing for size when the difference is small seems reasonable.
By loop unswitching a small part of it, the dynamic algorithm performs as well as multiple generic specializations. Nice.
As it turns out, !
is surprisingly great. Dunno if it adds some kind of compiler hint, but init?()!
seems like a viable alternative to init(unchecked:)
in most situations. I'm changing that while I'm at it, and I'm making the radix model generic over the size of each chunk because why not? Sounds neat.
[D10] passed (0.020 seconds)
[D16] passed (0.018 seconds)
[E10] passed (0.053 seconds) -0.02
[E16] passed (0.025 seconds) +0.02
func integerTextUnchecked(
chunks: NBKTwinHeaded<NBK.UnsafeWords>,
radix: AnyRadixSolution<Int>,
alphabet: MaxRadixAlphabetEncoder,
prefix: UnsafeUTF8,
suffix: UnsafeUTF8) -> String
It seems like the difference in performance comes from whether the first-chunk-method gets inlined in the main method. In case it matters.
Here's something neat that seems somewhat viable, and by that I mean that it performs well but the associated divisor loses its type constraint (immediately punished for making it generic). Perhaps it's possible to do something similar with a macro?
@inlinable func unswitch(_ body: (any NBK.RadixSolution<Size>) -> Void) {
switch self.power.isZero {
case true: body(NBK .PerfectRadixSolution(self)!)
case false: body(NBK.ImperfectRadixSolution(self)!)
}
}
It works when you have a generic method, but they are the ones I want to remove :expressionless:
Edit: this approach viable, but marking it as throws/rethrows makes it slow.
radix.unswitch({ generic($0) })
Unswitching without error-prone copy-past would be nice. I imagine something like:
#unswitch(radix) {/* it looks like one method body, but it's actually two! */}
I haven't played with macros much, but pure text transformations would allow me to drop two protocols: RadixSolution and RadixSolutionDivisior. I use them to avoid copy-pasta, but I think an AnyRadixSolution unswitch would be a viable alternative.
Edit: it would have to be a pure text transformation, absent generic constraints.
Edit: a cool thing about transforming text is that you can call different non-generic overloads. Hm (:bulb:).
Perhaps I can trim complexity by replacing the divisor models with a closure. Hm.
func division() -> (Element) -> QR<Element, Element>
Wow. Would you look at that. The any
unswitch method works with closures :star_struck:
Edit: but, it's way too slow. So sad :disappointed:
Edit: it's also possible to unswitch the divisor if it's the closure argument, but it's still slow :disappointed:
Hm. I just think that adding this kind of protocol creates an unnecessary project dialect.
public protocol Map<Input, Output> {
associatedtype Input
associatedtype Output
func callAsFunction(_ input: Input) -> Output
}
On a side note, however, I have a private project built around it and it's Sliced Bread 2.0™
I have this side-quest-y goal of reducing the following type of generics to a single compiled specialization (while allowing more for development purposes). The dynamic
NBKTwinHeaded<Base>
(#72) combinesBase
andReversedCollection<Base>
in a sufficiently efficient manner, but that still leaves two types of radices and two specializations. I haven't tried it yet, but I believe that switching on an enum (or similar) should be viable.