New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
koka data structures #102
Comments
Knowing generally if a recursive function is divergent is impossible. That being said for cases where we can prove that it's not convergent we can only get full type safety for all such functions in a language with a proof system (i.e. idris). As for whether this function can be rewritten to not be divergent, I'm not 100% sure, but my guess would be no. |
Interesting -- make sure to share your results :-) |
Move to #104
Yields me
|
Fixed in #104 :-) |
@daanx Hi, I have finished the fingertree experiment; but the results aren't satisfactory:
Scala:
Haskell
Scala uses a radix balanced fingertree (a very special data structure, may take a while to implement: scala/scala#8534); therefore it is expected to be fast; In Haskell, thou I didn't apply random access, the time difference is already significant enough. This result is little bit wield to me: yes, the tree is implemented in strict environment, but look-up operation should even be faster it the tree is strict evaluated. Perhaps I will need some external helps to figure out what is going on here. The code follows:
Scala: import scala.collection.immutable.Vector
import scala.util.Random
object Test {
val rand = new Random()
def create(cnt: Int, acc: Vector[Int]): Vector[Int] = {
if (cnt <= 0) {
acc
} else {
create(cnt - 1, cnt +: acc)
}
}
def heading(cnt: Int, x: Vector[Int], y: Int): Int ={
if (cnt > 0) {
heading(cnt - 1, x, x.head + y)
} else {
y
}
}
def tailing(cnt: Int, x: Vector[Int]): Vector[Int] = {
if (cnt > 0) { tailing(cnt - 1, x.tail) } else { x }
}
def access(cnt: Int, x: Vector[Int], acc: Int): Int = {
if (cnt >= 0) { access(cnt - 1, x, acc + x(rand.nextInt(x.length))) } else { acc }
}
}
object Main {
def run(name: String)(f: => Unit): Unit = {
val t1 = System.nanoTime
val res = f
val delta = (System.nanoTime - t1) / 1e9d
println(name + delta.toString)
return f
}
def main(args: Array[String]): Unit = {
val c = Test.create(10000000, Vector.empty)
run ( "heading 1000000: " ) { Test.heading(1000000, c, 0) }
run ( "tailing 1000000: " ) { System.err.println(Test.tailing(1000000, c).head) }
run ( "access 10000000: " ) { System.err.println(Test.access(10000000 - 1, c, 0)) }
System.err.println(c.head) // keep it, don't gc
}
} Haskell import Text.Printf
import Control.Exception
import System.CPUTime
import Data.Sequence
import System.Random
import Control.DeepSeq
time :: NFData t => t -> IO ()
time a = do
start <- getCPUTime
end <- a `deepseq` getCPUTime
let diff = (fromIntegral (end - start)) / (10^12)
printf "Computation time: %0.3f sec\n" (diff :: Double)
testData = fromList [0..10000000]
access :: Int -> Seq Int -> Int -> Int
access (-1) _ acc = acc
access x s acc = access (x - 1) s (acc + s `index` x)
main = do
time $ access (10000000 - 1) testData 0 |
Ouch -- that is no good :-( I am going to look into this to see what is the cause -- in principle there should be no such performance surprises. (Might be a mistake in Koka with the arbitrary precision integers causing allocations, or perhaps failed inlining causing allocation, or ???) . |
Great to see your nice code! You are a koka expert already :-) I have been playing with the sample and made it about 3x faster (see next post). When I look at the code it looks actually pretty good without doing any allocation in access. That makes me suspicious that perhaps you are not benchmarking correctly against Haskell/Scala? It could also be that here the reference counting is not doing well since Koka does not do "borrowing" inference yet which means there are lots of redundant ref counts (maybe not though, in the benchmarks like rbtree-ck this is also the case but koka still does great there -- we would need to profile it). (Here are some things to consider: the Scala bench does random access instead of sequential; the Scala one uses a different algorithm .. as you remarked that can be a huge difference in these sort of benchmarks; the Haskell one may be a different algorithm as well? You need to check if it is exactly the same; The amount of work is not the same: the Haskell one also constructs the tree in the time it accesses it so that is unfair to Haskell; but also it may construct it lazily while accessing and thus allocate a lot less; you need to construct first, deepseq, and then do the access loop.) |
With regard to the Koka code; I tried:
which is now a full value type without needing allocation (and passed and returned in registers). (this saves 200MiB allocations) With that I created a fresh
(I also created a version using just 32-bit integers to see if this is the culprit and that dropped from Since the code is now allocation free, I am not sure how to get this faster -- some "size" calls on the digit will match the structures twice and perhaps Scala/Haskell get rid of that? There is also a lot size recalculation on the nodes. The "optimized" code is:
|
Very nice code btw. Surprised to see such large complex structure implemented in Koka :-) It worries me too -- fingertrees seem quite complex :-) Some things I noticed:
|
So after the optimization, koka's implementation seems to only have a constant penalty compared with Haskell in lookup. Refined and added two more tests with scala:
Scala
Results - Scala
heading 1000000: 0.014681623
tailing 1000000: 0.067306418
access 10000000: 0.830828113
split 10000000/100: 0.067702266
# (GC was called explicitly brefore the next part)
merge 10000*10000: 0.705405871
- Koka
heading 1000000: 0.068s
tailing 1000000: 0.09s
access 10000000: 8.918s
split 10000000/100: 0.127s
# notice that split is also refined
# to cache the size without calculating them for multiple times
merge 10000*10000: 0.011s Koka seems to win a lot in merge since it just need to do in-place update here (maybe it is unfair for |
I am trying to write a finger tree in koka for https://github.com/SchrodingerZhu/koka-collections, following the template of https://github.com/liuxinyu95/AlgoXY/blob/algoxy/datastruct/elementary/sequence/src/FingerTree.hs.
I noticed that some function like the above is derived to have a divergence side effect though if the data structure is properly written, such functions should never diverge. Is it possible to workaround such situations.
BTW, what should I pay especially attention to in order to utilize the FBIP optimization?
The text was updated successfully, but these errors were encountered: