Advertisement
Guest User

Untitled

a guest
Jul 5th, 2019
138
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 5.00 KB | None | 0 0
  1. # Nim coroutines benchmark v2, try to speed up
  2. import asyncdispatch
  3. import asyncfutures
  4. import deques
  5. import math
  6. import times
  7.  
  8. const
  9. FRAMES = 2 # future frames to keep
  10. DISTANCE_TO_MINE = 10
  11. WORKER_SPEED = 1
  12. WORKER_CAPACITY = 1
  13. FRAMES_TO_MINE = 10
  14. NUM_TASKS = 1000
  15. FRAMES_TO_RUN = 1000000
  16.  
  17. type
  18. World = ref object of RootObj
  19. total_mined: int
  20. workers: array[NUM_TASKS, Worker]
  21. Worker = ref object of RootObj
  22. carrying: int
  23. position: int
  24. mining_progress: int
  25. ticks: int
  26. total_mined: int
  27.  
  28. proc newWorker(): Worker =
  29. var w = Worker(
  30. carrying: 0,
  31. position: 0,
  32. mining_progress: 0,
  33. ticks: 0,
  34. total_mined: 0,
  35. )
  36. return w
  37.  
  38. proc gather(this: Worker) =
  39. this.ticks += 1
  40. this.mining_progress += 1
  41. if(this.mining_progress >= FRAMES_TO_MINE):
  42. this.carrying = WORKER_CAPACITY
  43. this.mining_progress = 0
  44.  
  45. proc isMining(this: Worker): bool =
  46. return this.mining_progress > 0
  47.  
  48. proc dropoff(this: Worker) =
  49. this.total_mined += this.carrying
  50. this.carrying = 0
  51.  
  52. proc moveToMine(this: Worker) =
  53. this.ticks += 1
  54. this.position += WORKER_SPEED
  55.  
  56. proc moveToHome(this: Worker) =
  57. this.ticks += 1
  58. this.position -= WORKER_SPEED
  59.  
  60. proc isAtMine(this: Worker): bool =
  61. return this.position >= DISTANCE_TO_MINE
  62.  
  63. proc isAtHome(this: Worker): bool =
  64. return this.position <= 0
  65.  
  66. type
  67. TaskManager* = ref object of RootObj
  68. frame_futures: Deque[seq[Future[void]]]
  69. current_frame*: int
  70. world: World
  71. # Fix the immediate execution of tasks, and associate data with an object
  72. Task = ref object of RootObj
  73. name: string
  74. tm: TaskManager
  75. worker: Worker
  76.  
  77. # create a new TaskManager with futures preallocated
  78. proc newTaskManager*(): TaskManager =
  79. var tm = TaskManager(
  80. frame_futures: initDeque[seq[Future[void]]](nextPowerOfTwo(FRAMES)),
  81. current_frame: 1,
  82. world: World(total_mined: 0)
  83. )
  84. for i in 1..FRAMES:
  85. tm.frame_futures.addLast(newSeqOfCap[Future[void]](NUM_TASKS))
  86. return tm
  87.  
  88. # suspend async proc for a number of frames before running again
  89. proc frame*(this: TaskManager, frames = 1): Future[void] =
  90. var f = newFuture[void]("frame")
  91. # index 0 is current frame
  92. this.frame_futures[frames].add(f)
  93. return f
  94.  
  95. # removes the current frame, adding a replacement
  96. proc popFrame*(this: TaskManager) =
  97. this.frame_futures.popFirst()
  98. this.frame_futures.addLast(@[])
  99. this.current_frame += 1
  100.  
  101. # execute one future
  102. proc step(this: TaskManager) =
  103. if this.frame_futures.peekFirst().len() == 0:
  104. this.popFrame()
  105. else:
  106. this.frame_futures[0].pop().complete()
  107.  
  108. proc doFrame(this: TaskManager) =
  109. while this.frame_futures.peekFirst().len() > 0:
  110. this.frame_futures[0].pop().complete()
  111. this.popFrame()
  112.  
  113. proc goToMine(this: Task) {.async.} =
  114. while not this.worker.isAtMine():
  115. this.worker.moveToMine()
  116. await this.tm.frame()
  117.  
  118. proc doMining(this: Task) {.async.} =
  119. while true:
  120. this.worker.gather()
  121. await this.tm.frame()
  122. if not this.worker.isMining():
  123. break
  124.  
  125. proc dropoff(this: Task) {.async.} =
  126. while not this.worker.isAtHome():
  127. this.worker.moveToHome()
  128. await this.tm.frame()
  129. this.worker.dropoff()
  130.  
  131. proc run(this: Task) {.async, gcsafe.}=
  132. while true:
  133. await this.goToMine()
  134. await this.doMining()
  135. await this.dropoff()
  136.  
  137. var task_id = 0
  138. proc nextTaskId(): int =
  139. task_id += 1
  140. return task_id
  141.  
  142. # creates new task internally, don't see a reason why not
  143. proc addTask*(this: TaskManager, worker: Worker) =
  144. var task = Task(
  145. name: "Task " & $nextTaskId(),
  146. tm: this,
  147. worker: worker,
  148. )
  149. var cb = proc(future: Future[void]) {.closure, gcsafe.} =
  150. discard task.run()
  151. var f = newFuture[void]("starter")
  152. f.callback = cb
  153. this.frame_futures[0].add(f)
  154.  
  155. let tm = newTaskManager()
  156.  
  157. for i in 1..NUM_TASKS:
  158. let worker = newWorker()
  159. tm.world.workers[i-1] = worker
  160. tm.addTask(worker)
  161.  
  162. let start_t = getTime()
  163. for i in 1..FRAMES_TO_RUN:
  164. #tm.doFrame()
  165. tm.step()
  166. let end_t = getTime()
  167. let diff = end_t - start_t
  168.  
  169. tm.world.total_mined = 0
  170. for i in 0..NUM_TASKS-1:
  171. tm.world.total_mined += tm.world.workers[i].total_mined
  172.  
  173. echo "Mined a total of " & $tm.world.total_mined & " took " & $diff
  174. echo "ticks: " & $tm.world.workers[0].ticks
  175. # @1000/1000, Mined a total of 33000 took 164 milliseconds, 9 microseconds, and 400 nanoseconds
  176. # @1000/1000000 stepping, Mined a total of 33000 took 7 seconds, 507 milliseconds, 429 microseconds, and 400 nanoseconds
  177. # about 7.5 microseconds per step
  178. # v0.20
  179. # @1000/1000000 stepping, Mined a total of 33000 took 6 seconds, 127 milliseconds, 268 microseconds, and 600 nanoseconds
  180. # about 6127 ns per step
  181.  
  182. # in contrast, C++ duff's device coroutines took 0.293494 seconds (unoptimized), or 0.3 microseonds per step.
  183. # C++ MiLi coroutines are 30x faster (unoptimized)
  184. # optimized @1000/1000000 stepping, 15372180 ns = 15ns per step, or 500x faster?
  185. #[ C++
  186. Release completes 33000 trips in 20ns per step (300x faster)
  187.  
  188. ]#
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement