shortest subtask time to finish for scheduler
This commit is contained in:
		
							parent
							
								
									8926ae0f5c
								
							
						
					
					
						commit
						ff0a13e761
					
				
							
								
								
									
										11
									
								
								main.odin
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								main.odin
									
									
									
									
									
								
							@ -1,6 +1,7 @@
 | 
				
			|||||||
package rune
 | 
					package rune
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import "core:fmt"
 | 
					import "core:fmt"
 | 
				
			||||||
 | 
					import "core:time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
test_task :: proc(subtask: int, user_data: rawptr) {
 | 
					test_task :: proc(subtask: int, user_data: rawptr) {
 | 
				
			||||||
	fmt.printfln("test task %v", subtask)
 | 
						fmt.printfln("test task %v", subtask)
 | 
				
			||||||
@ -13,9 +14,13 @@ test_finished :: proc(user_data: rawptr) {
 | 
				
			|||||||
main :: proc() {
 | 
					main :: proc() {
 | 
				
			||||||
	init_scheduler(100, 1000)
 | 
						init_scheduler(100, 1000)
 | 
				
			||||||
	add_worker({.General, .Streaming, .Physics, .Rendering})
 | 
						add_worker({.General, .Streaming, .Physics, .Rendering})
 | 
				
			||||||
	add_worker({.General, .Streaming, .Physics, .Rendering})
 | 
						//add_worker({.General, .Streaming, .Physics, .Rendering})
 | 
				
			||||||
	add_worker({.General, .Streaming, .Physics, .Rendering})
 | 
						//add_worker({.General, .Streaming, .Physics, .Rendering})
 | 
				
			||||||
	add_worker({.General, .Streaming, .Physics, .Rendering})
 | 
						//add_worker({.General, .Streaming, .Physics, .Rendering})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						queue_task(test_task, test_finished, nil, 10)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						time.sleep(1 * time.Second)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	queue_task(test_task, test_finished, nil, 10)
 | 
						queue_task(test_task, test_finished, nil, 10)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										99
									
								
								task.odin
									
									
									
									
									
								
							
							
						
						
									
										99
									
								
								task.odin
									
									
									
									
									
								
							@ -1,9 +1,12 @@
 | 
				
			|||||||
package rune
 | 
					package rune
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import "core:container/queue"
 | 
					import "core:container/queue"
 | 
				
			||||||
 | 
					import "core:hash"
 | 
				
			||||||
import "core:mem"
 | 
					import "core:mem"
 | 
				
			||||||
 | 
					import "core:slice"
 | 
				
			||||||
import "core:sync"
 | 
					import "core:sync"
 | 
				
			||||||
import "core:thread"
 | 
					import "core:thread"
 | 
				
			||||||
 | 
					import "core:time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Task_Error :: enum {
 | 
					Task_Error :: enum {
 | 
				
			||||||
	None,
 | 
						None,
 | 
				
			||||||
@ -56,27 +59,32 @@ Worker :: struct {
 | 
				
			|||||||
@(private = "file")
 | 
					@(private = "file")
 | 
				
			||||||
Scheduler :: struct {
 | 
					Scheduler :: struct {
 | 
				
			||||||
	// Order of subtasks to execute. Highest priority is at index 0
 | 
						// Order of subtasks to execute. Highest priority is at index 0
 | 
				
			||||||
	schedule_storage:   []int,
 | 
						schedule_storage:    []int,
 | 
				
			||||||
	schedule:           queue.Queue(int),
 | 
						schedule:            queue.Queue(int),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// List of tasks. We don't move these to avoid expensive
 | 
						// List of tasks. We don't move these to avoid expensive
 | 
				
			||||||
	// copies when the schedule changes.
 | 
						// copies when the schedule changes.
 | 
				
			||||||
	tasks:              []Task,
 | 
						tasks:               []Task,
 | 
				
			||||||
	subtasks:           []Sub_Task,
 | 
						subtasks:            []Sub_Task,
 | 
				
			||||||
	first_free_task:    int,
 | 
						first_free_task:     int,
 | 
				
			||||||
	first_free_subtask: int,
 | 
						first_free_subtask:  int,
 | 
				
			||||||
	free_subtasks:      int,
 | 
						free_subtasks:       int,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Used when adding or removing tasks from the list,
 | 
						// Used when adding or removing tasks from the list,
 | 
				
			||||||
	// otherwise tasks are read-only (except for the remaining subtask counter, which is atomic)
 | 
						// otherwise tasks are read-only (except for the remaining subtask counter, which is atomic)
 | 
				
			||||||
	task_list_mutex:    sync.Mutex,
 | 
						task_list_mutex:     sync.Mutex,
 | 
				
			||||||
	subtask_list_mutex: sync.Mutex,
 | 
						subtask_list_mutex:  sync.Mutex,
 | 
				
			||||||
	schedule_mutex:     sync.Mutex,
 | 
						schedule_mutex:      sync.Mutex,
 | 
				
			||||||
 | 
						subtask_times_mutex: sync.Mutex,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Keeps track of how long subtasks (hash of function + subtask idx) ran in the past.
 | 
				
			||||||
 | 
						// We keep the last runtime and use it when scheduling subtasks.
 | 
				
			||||||
 | 
						subtask_times:       map[u64]f64,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// List of workers. We allow the user to dynamically add or remove workers.
 | 
						// List of workers. We allow the user to dynamically add or remove workers.
 | 
				
			||||||
	workers:            [dynamic]^Worker,
 | 
						workers:             [dynamic]^Worker,
 | 
				
			||||||
	workers_mutex:      sync.Mutex,
 | 
						workers_mutex:       sync.Mutex,
 | 
				
			||||||
	allocator:          mem.Allocator,
 | 
						allocator:           mem.Allocator,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@(private = "file")
 | 
					@(private = "file")
 | 
				
			||||||
@ -100,10 +108,24 @@ init_scheduler :: proc(
 | 
				
			|||||||
		_scheduler.subtasks[i].next_free = i + 1
 | 
							_scheduler.subtasks[i].next_free = i + 1
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	_scheduler.free_subtasks = max_subtasks_per_task * max_tasks
 | 
						_scheduler.free_subtasks = max_subtasks_per_task * max_tasks
 | 
				
			||||||
 | 
						_scheduler.subtask_times = make(map[u64]f64, allocator)
 | 
				
			||||||
	_scheduler.workers = make([dynamic]^Worker, allocator)
 | 
						_scheduler.workers = make([dynamic]^Worker, allocator)
 | 
				
			||||||
	_scheduler.allocator = allocator
 | 
						_scheduler.allocator = allocator
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					shutdown_scheduler :: proc() {
 | 
				
			||||||
 | 
						for &worker in _scheduler.workers {
 | 
				
			||||||
 | 
							worker.run = false
 | 
				
			||||||
 | 
							thread.destroy(worker.thread)
 | 
				
			||||||
 | 
							free(worker, _scheduler.allocator)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						delete(_scheduler.workers)
 | 
				
			||||||
 | 
						delete(_scheduler.subtask_times)
 | 
				
			||||||
 | 
						delete(_scheduler.schedule_storage, _scheduler.allocator)
 | 
				
			||||||
 | 
						delete(_scheduler.tasks, _scheduler.allocator)
 | 
				
			||||||
 | 
						delete(_scheduler.subtasks, _scheduler.allocator)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
add_worker :: proc(task_types: bit_set[Task_Type]) -> (int, Task_Error) {
 | 
					add_worker :: proc(task_types: bit_set[Task_Type]) -> (int, Task_Error) {
 | 
				
			||||||
	sync.lock(&_scheduler.workers_mutex)
 | 
						sync.lock(&_scheduler.workers_mutex)
 | 
				
			||||||
	defer sync.unlock(&_scheduler.workers_mutex)
 | 
						defer sync.unlock(&_scheduler.workers_mutex)
 | 
				
			||||||
@ -139,6 +161,17 @@ remove_worker :: proc(idx: int) {
 | 
				
			|||||||
	unordered_remove(&_scheduler.workers, idx)
 | 
						unordered_remove(&_scheduler.workers, idx)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Subtask_Timing :: struct {
 | 
				
			||||||
 | 
						slot: int,
 | 
				
			||||||
 | 
						time: f64,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@(private = "file")
 | 
				
			||||||
 | 
					sort_subtask_timings :: proc(lhs: Subtask_Timing, rhs: Subtask_Timing) -> bool {
 | 
				
			||||||
 | 
						return lhs.time < rhs.time
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
queue_task :: proc(
 | 
					queue_task :: proc(
 | 
				
			||||||
	entry: Task_Proc,
 | 
						entry: Task_Proc,
 | 
				
			||||||
	finished: Task_Finished_Proc,
 | 
						finished: Task_Finished_Proc,
 | 
				
			||||||
@ -177,6 +210,10 @@ queue_task :: proc(
 | 
				
			|||||||
		return .Too_Many_Tasks
 | 
							return .Too_Many_Tasks
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	_scheduler.free_subtasks -= subtask_count
 | 
						_scheduler.free_subtasks -= subtask_count
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						subtask_timings := make([]Subtask_Timing, subtask_count)
 | 
				
			||||||
 | 
						task_hash := u64(uintptr(&task.entry))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for i := 0; i < subtask_count; i += 1 {
 | 
						for i := 0; i < subtask_count; i += 1 {
 | 
				
			||||||
		subtask_slot := _scheduler.first_free_subtask
 | 
							subtask_slot := _scheduler.first_free_subtask
 | 
				
			||||||
		assert(subtask_slot < len(_scheduler.subtasks))
 | 
							assert(subtask_slot < len(_scheduler.subtasks))
 | 
				
			||||||
@ -186,13 +223,36 @@ queue_task :: proc(
 | 
				
			|||||||
		subtask.task = slot
 | 
							subtask.task = slot
 | 
				
			||||||
		subtask.idx = i
 | 
							subtask.idx = i
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							subtask_id: [1]int = {i}
 | 
				
			||||||
 | 
							subtask_hash := hash.fnv64a(slice.to_bytes(subtask_id[:]))
 | 
				
			||||||
 | 
							final_hash := task_hash ~ subtask_hash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							subtask_timings[i].slot = i
 | 
				
			||||||
 | 
							if final_hash in _scheduler.subtask_times {
 | 
				
			||||||
 | 
								sync.lock(&_scheduler.subtask_times_mutex)
 | 
				
			||||||
 | 
								subtask_timings[i].time = _scheduler.subtask_times[final_hash]
 | 
				
			||||||
 | 
								sync.unlock(&_scheduler.subtask_times_mutex)
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								subtask_timings[i].time = 0.0
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*
 | 
				
			||||||
		sync.lock(&_scheduler.schedule_mutex)
 | 
							sync.lock(&_scheduler.schedule_mutex)
 | 
				
			||||||
		// Add to schedule. This is FIFO. We could be more clever (for example use shortest time to finish)
 | 
							// Add to schedule. This is FIFO. We could be more clever (for example use shortest time to finish)
 | 
				
			||||||
		queue.push_back(&_scheduler.schedule, subtask_slot)
 | 
							queue.push_back(&_scheduler.schedule, subtask_slot)
 | 
				
			||||||
		sync.unlock(&_scheduler.schedule_mutex)
 | 
							sync.unlock(&_scheduler.schedule_mutex)
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	sync.unlock(&_scheduler.subtask_list_mutex)
 | 
						sync.unlock(&_scheduler.subtask_list_mutex)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						slice.sort_by(subtask_timings, sort_subtask_timings)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sync.lock(&_scheduler.schedule_mutex)
 | 
				
			||||||
 | 
						for i := 0; i < subtask_count; i += 1 {
 | 
				
			||||||
 | 
							queue.push_back(&_scheduler.schedule, subtask_timings[i].slot)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						sync.unlock(&_scheduler.schedule_mutex)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return .None
 | 
						return .None
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -219,7 +279,20 @@ worker_proc :: proc(t: ^thread.Thread) {
 | 
				
			|||||||
		task := &_scheduler.tasks[taskidx]
 | 
							task := &_scheduler.tasks[taskidx]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if task.type in task_types {
 | 
							if task.type in task_types {
 | 
				
			||||||
 | 
								start := time.tick_now()
 | 
				
			||||||
			task.entry(subtask.idx, task.user_data)
 | 
								task.entry(subtask.idx, task.user_data)
 | 
				
			||||||
 | 
								end := time.tick_now()
 | 
				
			||||||
 | 
								duration := time.tick_diff(start, end)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Compute a hash that identifies this subtask.
 | 
				
			||||||
 | 
								// We just treat the (64bit) entry address as a hash
 | 
				
			||||||
 | 
								task_hash := u64(uintptr(&task.entry))
 | 
				
			||||||
 | 
								subtask_id: [1]int = {subtask.idx}
 | 
				
			||||||
 | 
								subtask_hash := hash.fnv64a(slice.to_bytes(subtask_id[:]))
 | 
				
			||||||
 | 
								final_hash := task_hash ~ subtask_hash
 | 
				
			||||||
 | 
								sync.lock(&_scheduler.subtask_times_mutex)
 | 
				
			||||||
 | 
								_scheduler.subtask_times[final_hash] = time.duration_microseconds(duration)
 | 
				
			||||||
 | 
								sync.unlock(&_scheduler.subtask_times_mutex)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			sync.lock(&_scheduler.subtask_list_mutex)
 | 
								sync.lock(&_scheduler.subtask_list_mutex)
 | 
				
			||||||
			subtask.next_free = _scheduler.first_free_subtask
 | 
								subtask.next_free = _scheduler.first_free_subtask
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user