From e8f5367219aaa2319f5193a893f41355305ef9ee Mon Sep 17 00:00:00 2001 From: Stephen Adams Date: Thu, 15 Apr 2021 22:29:07 +0000 Subject: [PATCH] [benchmarks] Move Richards from internal benchmarks to SDK benchmarks Change-Id: I523b8c29ff97235e2dec0fd50f67ee4e8cd4ef13 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/195191 Commit-Queue: Stephen Adams Reviewed-by: Jonas Termansen --- benchmarks/Richards/dart/Richards.dart | 446 +++++++++++++++++ benchmarks/Richards/dart2/Richards.dart | 449 +++++++++++++++++ benchmarks/Richards/javascript/Richards.js | 536 +++++++++++++++++++++ 3 files changed, 1431 insertions(+) create mode 100644 benchmarks/Richards/dart/Richards.dart create mode 100644 benchmarks/Richards/dart2/Richards.dart create mode 100644 benchmarks/Richards/javascript/Richards.js diff --git a/benchmarks/Richards/dart/Richards.dart b/benchmarks/Richards/dart/Richards.dart new file mode 100644 index 00000000000..f9306516d4f --- /dev/null +++ b/benchmarks/Richards/dart/Richards.dart @@ -0,0 +1,446 @@ +// Copyright 2006-2008 the V8 project authors. All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Ported by the Dart team to Dart. + +// This is a Dart implementation of the Richards benchmark from: +// +// http://www.cl.cam.ac.uk/~mr10/Bench.html +// +// The benchmark was originally implemented in BCPL by +// Martin Richards. + +import 'package:benchmark_harness/benchmark_harness.dart'; + +void main() { + const Richards().report(); +} + +/// Richards imulates the task dispatcher of an operating system. +class Richards extends BenchmarkBase { + const Richards() : super('Richards'); + + @override + void run() { + final Scheduler scheduler = Scheduler(); + scheduler.addIdleTask(ID_IDLE, 0, null, COUNT); + + Packet queue = Packet(null, ID_WORKER, KIND_WORK); + queue = Packet(queue, ID_WORKER, KIND_WORK); + scheduler.addWorkerTask(ID_WORKER, 1000, queue); + + queue = Packet(null, ID_DEVICE_A, KIND_DEVICE); + queue = Packet(queue, ID_DEVICE_A, KIND_DEVICE); + queue = Packet(queue, ID_DEVICE_A, KIND_DEVICE); + scheduler.addHandlerTask(ID_HANDLER_A, 2000, queue); + + queue = Packet(null, ID_DEVICE_B, KIND_DEVICE); + queue = Packet(queue, ID_DEVICE_B, KIND_DEVICE); + queue = Packet(queue, ID_DEVICE_B, KIND_DEVICE); + scheduler.addHandlerTask(ID_HANDLER_B, 3000, queue); + + scheduler.addDeviceTask(ID_DEVICE_A, 4000, null); + + scheduler.addDeviceTask(ID_DEVICE_B, 5000, null); + + scheduler.schedule(); + + if (scheduler.queueCount != EXPECTED_QUEUE_COUNT || + scheduler.holdCount != EXPECTED_HOLD_COUNT) { + print('Error during execution: queueCount = ${scheduler.queueCount}' + ', holdCount = ${scheduler.holdCount}.'); + } + if (EXPECTED_QUEUE_COUNT != scheduler.queueCount) { + throw 'bad scheduler queue-count'; + } + if (EXPECTED_HOLD_COUNT != scheduler.holdCount) { + throw 'bad scheduler hold-count'; + } + } + + static const int DATA_SIZE = 4; + static const int COUNT = 1000; + + /// These two constants specify how many times a packet is queued and + /// how many times a task is put on hold in a correct run of richards. + /// They don't have any meaning a such but are characteristic of a + /// correct run so if the actual queue or hold count is different from + /// the expected there must be a bug in the implementation. + static const int EXPECTED_QUEUE_COUNT = 2322; + static const int EXPECTED_HOLD_COUNT = 928; + + static const int ID_IDLE = 0; + static const int ID_WORKER = 1; + static const int ID_HANDLER_A = 2; + static const int ID_HANDLER_B = 3; + static const int ID_DEVICE_A = 4; + static const int ID_DEVICE_B = 5; + static const int NUMBER_OF_IDS = 6; + + static const int KIND_DEVICE = 0; + static const int KIND_WORK = 1; +} + +/// A scheduler can be used to schedule a set of tasks based on their relative +/// priorities. Scheduling is done by maintaining a list of task control blocks +/// which holds tasks and the data queue they are processing. +class Scheduler { + int queueCount = 0; + int holdCount = 0; + TaskControlBlock? currentTcb; + int currentId = Richards.ID_IDLE; + TaskControlBlock? list; + final List blocks = + List.filled(Richards.NUMBER_OF_IDS, null); + + /// Add an idle task to this scheduler. + void addIdleTask(int id, int priority, Packet? queue, int count) { + addRunningTask(id, priority, queue, IdleTask(this, 1, count)); + } + + /// Add a work task to this scheduler. + void addWorkerTask(int id, int priority, Packet? queue) { + addTask(id, priority, queue, WorkerTask(this, Richards.ID_HANDLER_A, 0)); + } + + /// Add a handler task to this scheduler. + void addHandlerTask(int id, int priority, Packet? queue) { + addTask(id, priority, queue, HandlerTask(this)); + } + + /// Add a handler task to this scheduler. + void addDeviceTask(int id, int priority, Packet? queue) { + addTask(id, priority, queue, DeviceTask(this)); + } + + /// Add the specified task and mark it as running. + void addRunningTask(int id, int priority, Packet? queue, Task task) { + addTask(id, priority, queue, task); + currentTcb!.setRunning(); + } + + /// Add the specified task to this scheduler. + void addTask(int id, int priority, Packet? queue, Task task) { + currentTcb = TaskControlBlock(list, id, priority, queue, task); + list = currentTcb; + blocks[id] = currentTcb; + } + + /// Execute the tasks managed by this scheduler. + void schedule() { + currentTcb = list; + while (currentTcb != null) { + if (currentTcb!.isHeldOrSuspended()) { + currentTcb = currentTcb!.link; + } else { + currentId = currentTcb!.id; + currentTcb = currentTcb!.run(); + } + } + } + + /// Release a task that is currently blocked and return the next block to run. + TaskControlBlock? release(int id) { + final TaskControlBlock? tcb = blocks[id]; + if (tcb == null) return tcb; + tcb.markAsNotHeld(); + if (tcb.priority > currentTcb!.priority) return tcb; + return currentTcb; + } + + /// Block the currently executing task and return the next task control block + /// to run. The blocked task will not be made runnable until it is explicitly + /// released, even if new work is added to it. + TaskControlBlock? holdCurrent() { + holdCount++; + currentTcb!.markAsHeld(); + return currentTcb!.link; + } + + /// Suspend the currently executing task and return the next task + /// control block to run. + /// If new work is added to the suspended task it will be made runnable. + TaskControlBlock suspendCurrent() { + currentTcb!.markAsSuspended(); + return currentTcb!; + } + + /// Add the specified packet to the end of the worklist used by the task + /// associated with the packet and make the task runnable if it is currently + /// suspended. + TaskControlBlock? queue(Packet packet) { + final TaskControlBlock? t = blocks[packet.id]; + if (t == null) return t; + queueCount++; + packet.link = null; + packet.id = currentId; + return t.checkPriorityAdd(currentTcb!, packet); + } +} + +/// A task control block manages a task and the queue of work packages +/// associated with it. +class TaskControlBlock { + TaskControlBlock? link; + int id; // The id of this block. + int priority; // The priority of this block. + Packet? queue; // The queue of packages to be processed by the task. + Task task; + int state; + + TaskControlBlock(this.link, this.id, this.priority, this.queue, this.task) + : state = queue == null ? STATE_SUSPENDED : STATE_SUSPENDED_RUNNABLE; + + /// The task is running and is currently scheduled. + static const int STATE_RUNNING = 0; + + /// The task has packets left to process. + static const int STATE_RUNNABLE = 1; + + /// The task is not currently running. The task is not blocked as such and may + /// be started by the scheduler. + static const int STATE_SUSPENDED = 2; + + /// The task is blocked and cannot be run until it is explicitly released. + static const int STATE_HELD = 4; + + static const int STATE_SUSPENDED_RUNNABLE = STATE_SUSPENDED | STATE_RUNNABLE; + static const int STATE_NOT_HELD = ~STATE_HELD; + + void setRunning() { + state = STATE_RUNNING; + } + + void markAsNotHeld() { + state = state & STATE_NOT_HELD; + } + + void markAsHeld() { + state = state | STATE_HELD; + } + + bool isHeldOrSuspended() { + return (state & STATE_HELD) != 0 || (state == STATE_SUSPENDED); + } + + void markAsSuspended() { + state = state | STATE_SUSPENDED; + } + + void markAsRunnable() { + state = state | STATE_RUNNABLE; + } + + /// Runs this task, if it is ready to be run, and returns the next + /// task to run. + TaskControlBlock? run() { + Packet? packet; + if (state == STATE_SUSPENDED_RUNNABLE) { + packet = queue; + queue = packet!.link; + state = queue == null ? STATE_RUNNING : STATE_RUNNABLE; + } else { + packet = null; + } + return task.run(packet); + } + + /// Adds a packet to the worklist of this block's task, marks this as + /// runnable if necessary, and returns the next runnable object to run + /// (the one with the highest priority). + TaskControlBlock checkPriorityAdd(TaskControlBlock task, Packet packet) { + if (queue == null) { + queue = packet; + markAsRunnable(); + if (priority > task.priority) return this; + } else { + queue = packet.addTo(queue); + } + return task; + } + + @override + String toString() => 'tcb { $task@$state }'; +} + +/// Abstract task that manipulates work packets. +abstract class Task { + Scheduler scheduler; // The scheduler that manages this task. + + Task(this.scheduler); + + TaskControlBlock? run(Packet? packet); +} + +/// An idle task doesn't do any work itself but cycles control between the two +/// device tasks. +class IdleTask extends Task { + int v1; // A seed value that controls how the device tasks are scheduled. + int count; // The number of times this task should be scheduled. + + IdleTask(Scheduler scheduler, this.v1, this.count) : super(scheduler); + + @override + TaskControlBlock? run(Packet? packet) { + count--; + if (count == 0) return scheduler.holdCurrent(); + if ((v1 & 1) == 0) { + v1 = v1 >> 1; + return scheduler.release(Richards.ID_DEVICE_A); + } + v1 = (v1 >> 1) ^ 0xD008; + return scheduler.release(Richards.ID_DEVICE_B); + } + + @override + String toString() => 'IdleTask'; +} + +/// A task that suspends itself after each time it has been run to simulate +/// waiting for data from an external device. +class DeviceTask extends Task { + Packet? v1; + + DeviceTask(Scheduler scheduler) : super(scheduler); + + @override + TaskControlBlock? run(Packet? packet) { + if (packet == null) { + if (v1 == null) return scheduler.suspendCurrent(); + final Packet v = v1!; + v1 = null; + return scheduler.queue(v); + } + v1 = packet; + return scheduler.holdCurrent(); + } + + @override + String toString() => 'DeviceTask'; +} + +/// A task that manipulates work packets. +class WorkerTask extends Task { + int v1; // A seed used to specify how work packets are manipulated. + int v2; // Another seed used to specify how work packets are manipulated. + + WorkerTask(Scheduler scheduler, this.v1, this.v2) : super(scheduler); + + @override + TaskControlBlock? run(Packet? packet) { + if (packet == null) { + return scheduler.suspendCurrent(); + } + if (v1 == Richards.ID_HANDLER_A) { + v1 = Richards.ID_HANDLER_B; + } else { + v1 = Richards.ID_HANDLER_A; + } + packet.id = v1; + packet.a1 = 0; + for (int i = 0; i < Richards.DATA_SIZE; i++) { + v2++; + if (v2 > 26) v2 = 1; + packet.a2[i] = v2; + } + return scheduler.queue(packet); + } + + @override + String toString() => 'WorkerTask'; +} + +/// A task that manipulates work packets and then suspends itself. +class HandlerTask extends Task { + Packet? v1; + Packet? v2; + + HandlerTask(Scheduler scheduler) : super(scheduler); + + @override + TaskControlBlock? run(Packet? packet) { + if (packet != null) { + if (packet.kind == Richards.KIND_WORK) { + v1 = packet.addTo(v1); + } else { + v2 = packet.addTo(v2); + } + } + if (v1 != null) { + final int count = v1!.a1; + if (count < Richards.DATA_SIZE) { + if (v2 != null) { + final Packet v = v2!; + v2 = v.link; + v.a1 = v1!.a2[count]; + v1!.a1 = count + 1; + return scheduler.queue(v); + } + } else { + final Packet v = v1!; + v1 = v.link; + return scheduler.queue(v); + } + } + return scheduler.suspendCurrent(); + } + + @override + String toString() => 'HandlerTask'; +} + +/// A simple package of data that is manipulated by the tasks. The exact layout +/// of the payload data carried by a packet is not importaint, and neither is +/// the nature of the work performed on packets by the tasks. Besides carrying +/// data, packets form linked lists and are hence used both as data and +/// worklists. +class Packet { + Packet? link; // The tail of the linked list of packets. + int id; // An ID for this packet. + int kind; // The type of this packet. + int a1 = 0; + + List a2 = List.filled(Richards.DATA_SIZE, 0); + + Packet(this.link, this.id, this.kind); + + /// Add this packet to the end of a worklist, and return the worklist. + Packet addTo(Packet? queue) { + link = null; + if (queue == null) return this; + Packet? peek; + Packet next = queue; + while ((peek = next.link) != null) { + next = peek!; + } + next.link = this; + return queue; + } + + @override + String toString() => 'Packet'; +} diff --git a/benchmarks/Richards/dart2/Richards.dart b/benchmarks/Richards/dart2/Richards.dart new file mode 100644 index 00000000000..f479a3216f2 --- /dev/null +++ b/benchmarks/Richards/dart2/Richards.dart @@ -0,0 +1,449 @@ +// Copyright 2006-2008 the V8 project authors. All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Ported by the Dart team to Dart. + +// This is a Dart implementation of the Richards benchmark from: +// +// http://www.cl.cam.ac.uk/~mr10/Bench.html +// +// The benchmark was originally implemented in BCPL by +// Martin Richards. + +// @dart=2.9 + +import 'package:benchmark_harness/benchmark_harness.dart'; + +void main() { + const Richards().report(); +} + +/// Richards imulates the task dispatcher of an operating system. +class Richards extends BenchmarkBase { + const Richards() : super('Richards'); + + @override + void run() { + final Scheduler scheduler = Scheduler(); + scheduler.addIdleTask(ID_IDLE, 0, null, COUNT); + + Packet queue = Packet(null, ID_WORKER, KIND_WORK); + queue = Packet(queue, ID_WORKER, KIND_WORK); + scheduler.addWorkerTask(ID_WORKER, 1000, queue); + + queue = Packet(null, ID_DEVICE_A, KIND_DEVICE); + queue = Packet(queue, ID_DEVICE_A, KIND_DEVICE); + queue = Packet(queue, ID_DEVICE_A, KIND_DEVICE); + scheduler.addHandlerTask(ID_HANDLER_A, 2000, queue); + + queue = Packet(null, ID_DEVICE_B, KIND_DEVICE); + queue = Packet(queue, ID_DEVICE_B, KIND_DEVICE); + queue = Packet(queue, ID_DEVICE_B, KIND_DEVICE); + scheduler.addHandlerTask(ID_HANDLER_B, 3000, queue); + + scheduler.addDeviceTask(ID_DEVICE_A, 4000, null); + + scheduler.addDeviceTask(ID_DEVICE_B, 5000, null); + + scheduler.schedule(); + + if (scheduler.queueCount != EXPECTED_QUEUE_COUNT || + scheduler.holdCount != EXPECTED_HOLD_COUNT) { + print('Error during execution: queueCount = ${scheduler.queueCount}' + ', holdCount = ${scheduler.holdCount}.'); + } + if (EXPECTED_QUEUE_COUNT != scheduler.queueCount) { + throw 'bad scheduler queue-count'; + } + if (EXPECTED_HOLD_COUNT != scheduler.holdCount) { + throw 'bad scheduler hold-count'; + } + } + + static const int DATA_SIZE = 4; + static const int COUNT = 1000; + + /// These two constants specify how many times a packet is queued and + /// how many times a task is put on hold in a correct run of richards. + /// They don't have any meaning a such but are characteristic of a + /// correct run so if the actual queue or hold count is different from + /// the expected there must be a bug in the implementation. + static const int EXPECTED_QUEUE_COUNT = 2322; + static const int EXPECTED_HOLD_COUNT = 928; + + static const int ID_IDLE = 0; + static const int ID_WORKER = 1; + static const int ID_HANDLER_A = 2; + static const int ID_HANDLER_B = 3; + static const int ID_DEVICE_A = 4; + static const int ID_DEVICE_B = 5; + static const int NUMBER_OF_IDS = 6; + + static const int KIND_DEVICE = 0; + static const int KIND_WORK = 1; +} + +/// A scheduler can be used to schedule a set of tasks based on their relative +/// priorities. Scheduling is done by maintaining a list of task control blocks +/// which holds tasks and the data queue they are processing. +class Scheduler { + int queueCount = 0; + int holdCount = 0; + TaskControlBlock currentTcb; + int currentId; + TaskControlBlock list; + List blocks = + List(Richards.NUMBER_OF_IDS); + + /// Add an idle task to this scheduler. + void addIdleTask(int id, int priority, Packet queue, int count) { + addRunningTask(id, priority, queue, IdleTask(this, 1, count)); + } + + /// Add a work task to this scheduler. + void addWorkerTask(int id, int priority, Packet queue) { + addTask(id, priority, queue, WorkerTask(this, Richards.ID_HANDLER_A, 0)); + } + + /// Add a handler task to this scheduler. + void addHandlerTask(int id, int priority, Packet queue) { + addTask(id, priority, queue, HandlerTask(this)); + } + + /// Add a handler task to this scheduler. + void addDeviceTask(int id, int priority, Packet queue) { + addTask(id, priority, queue, DeviceTask(this)); + } + + /// Add the specified task and mark it as running. + void addRunningTask(int id, int priority, Packet queue, Task task) { + addTask(id, priority, queue, task); + currentTcb.setRunning(); + } + + /// Add the specified task to this scheduler. + void addTask(int id, int priority, Packet queue, Task task) { + currentTcb = TaskControlBlock(list, id, priority, queue, task); + list = currentTcb; + blocks[id] = currentTcb; + } + + /// Execute the tasks managed by this scheduler. + void schedule() { + currentTcb = list; + while (currentTcb != null) { + if (currentTcb.isHeldOrSuspended()) { + currentTcb = currentTcb.link; + } else { + currentId = currentTcb.id; + currentTcb = currentTcb.run(); + } + } + } + + /// Release a task that is currently blocked and return the next block to run. + TaskControlBlock release(int id) { + final TaskControlBlock tcb = blocks[id]; + if (tcb == null) return tcb; + tcb.markAsNotHeld(); + if (tcb.priority > currentTcb.priority) return tcb; + return currentTcb; + } + + /// Block the currently executing task and return the next task control block + /// to run. The blocked task will not be made runnable until it is explicitly + /// released, even if new work is added to it. + TaskControlBlock holdCurrent() { + holdCount++; + currentTcb.markAsHeld(); + return currentTcb.link; + } + + /// Suspend the currently executing task and return the next task + /// control block to run. + /// If new work is added to the suspended task it will be made runnable. + TaskControlBlock suspendCurrent() { + currentTcb.markAsSuspended(); + return currentTcb; + } + + /// Add the specified packet to the end of the worklist used by the task + /// associated with the packet and make the task runnable if it is currently + /// suspended. + TaskControlBlock queue(Packet packet) { + final TaskControlBlock t = blocks[packet.id]; + if (t == null) return t; + queueCount++; + packet.link = null; + packet.id = currentId; + return t.checkPriorityAdd(currentTcb, packet); + } +} + +/// A task control block manages a task and the queue of work packages +/// associated with it. +class TaskControlBlock { + TaskControlBlock link; + int id; // The id of this block. + int priority; // The priority of this block. + Packet queue; // The queue of packages to be processed by the task. + Task task; + int state; + + TaskControlBlock(this.link, this.id, this.priority, this.queue, this.task) { + state = queue == null ? STATE_SUSPENDED : STATE_SUSPENDED_RUNNABLE; + } + + /// The task is running and is currently scheduled. + static const int STATE_RUNNING = 0; + + /// The task has packets left to process. + static const int STATE_RUNNABLE = 1; + + /// The task is not currently running. The task is not blocked as such and may + /// be started by the scheduler. + static const int STATE_SUSPENDED = 2; + + /// The task is blocked and cannot be run until it is explicitly released. + static const int STATE_HELD = 4; + + static const int STATE_SUSPENDED_RUNNABLE = STATE_SUSPENDED | STATE_RUNNABLE; + static const int STATE_NOT_HELD = ~STATE_HELD; + + void setRunning() { + state = STATE_RUNNING; + } + + void markAsNotHeld() { + state = state & STATE_NOT_HELD; + } + + void markAsHeld() { + state = state | STATE_HELD; + } + + bool isHeldOrSuspended() { + return (state & STATE_HELD) != 0 || (state == STATE_SUSPENDED); + } + + void markAsSuspended() { + state = state | STATE_SUSPENDED; + } + + void markAsRunnable() { + state = state | STATE_RUNNABLE; + } + + /// Runs this task, if it is ready to be run, and returns the next + /// task to run. + TaskControlBlock run() { + Packet packet; + if (state == STATE_SUSPENDED_RUNNABLE) { + packet = queue; + queue = packet.link; + state = queue == null ? STATE_RUNNING : STATE_RUNNABLE; + } else { + packet = null; + } + return task.run(packet); + } + + /// Adds a packet to the worklist of this block's task, marks this as + /// runnable if necessary, and returns the next runnable object to run + /// (the one with the highest priority). + TaskControlBlock checkPriorityAdd(TaskControlBlock task, Packet packet) { + if (queue == null) { + queue = packet; + markAsRunnable(); + if (priority > task.priority) return this; + } else { + queue = packet.addTo(queue); + } + return task; + } + + @override + String toString() => 'tcb { $task@$state }'; +} + +/// Abstract task that manipulates work packets. +abstract class Task { + Scheduler scheduler; // The scheduler that manages this task. + + Task(this.scheduler); + + TaskControlBlock run(Packet packet); +} + +/// An idle task doesn't do any work itself but cycles control between the two +/// device tasks. +class IdleTask extends Task { + int v1; // A seed value that controls how the device tasks are scheduled. + int count; // The number of times this task should be scheduled. + + IdleTask(Scheduler scheduler, this.v1, this.count) : super(scheduler); + + @override + TaskControlBlock run(Packet packet) { + count--; + if (count == 0) return scheduler.holdCurrent(); + if ((v1 & 1) == 0) { + v1 = v1 >> 1; + return scheduler.release(Richards.ID_DEVICE_A); + } + v1 = (v1 >> 1) ^ 0xD008; + return scheduler.release(Richards.ID_DEVICE_B); + } + + @override + String toString() => 'IdleTask'; +} + +/// A task that suspends itself after each time it has been run to simulate +/// waiting for data from an external device. +class DeviceTask extends Task { + Packet v1; + + DeviceTask(Scheduler scheduler) : super(scheduler); + + @override + TaskControlBlock run(Packet packet) { + if (packet == null) { + if (v1 == null) return scheduler.suspendCurrent(); + final Packet v = v1; + v1 = null; + return scheduler.queue(v); + } + v1 = packet; + return scheduler.holdCurrent(); + } + + @override + String toString() => 'DeviceTask'; +} + +/// A task that manipulates work packets. +class WorkerTask extends Task { + int v1; // A seed used to specify how work packets are manipulated. + int v2; // Another seed used to specify how work packets are manipulated. + + WorkerTask(Scheduler scheduler, this.v1, this.v2) : super(scheduler); + + @override + TaskControlBlock run(Packet packet) { + if (packet == null) { + return scheduler.suspendCurrent(); + } + if (v1 == Richards.ID_HANDLER_A) { + v1 = Richards.ID_HANDLER_B; + } else { + v1 = Richards.ID_HANDLER_A; + } + packet.id = v1; + packet.a1 = 0; + for (int i = 0; i < Richards.DATA_SIZE; i++) { + v2++; + if (v2 > 26) v2 = 1; + packet.a2[i] = v2; + } + return scheduler.queue(packet); + } + + @override + String toString() => 'WorkerTask'; +} + +/// A task that manipulates work packets and then suspends itself. +class HandlerTask extends Task { + Packet v1; + Packet v2; + + HandlerTask(Scheduler scheduler) : super(scheduler); + + @override + TaskControlBlock run(Packet packet) { + if (packet != null) { + if (packet.kind == Richards.KIND_WORK) { + v1 = packet.addTo(v1); + } else { + v2 = packet.addTo(v2); + } + } + if (v1 != null) { + final int count = v1.a1; + Packet v; + if (count < Richards.DATA_SIZE) { + if (v2 != null) { + v = v2; + v2 = v2.link; + v.a1 = v1.a2[count]; + v1.a1 = count + 1; + return scheduler.queue(v); + } + } else { + v = v1; + v1 = v1.link; + return scheduler.queue(v); + } + } + return scheduler.suspendCurrent(); + } + + @override + String toString() => 'HandlerTask'; +} + +/// A simple package of data that is manipulated by the tasks. The exact layout +/// of the payload data carried by a packet is not importaint, and neither is +/// the nature of the work performed on packets by the tasks. Besides carrying +/// data, packets form linked lists and are hence used both as data and +/// worklists. +class Packet { + Packet link; // The tail of the linked list of packets. + int id; // An ID for this packet. + int kind; // The type of this packet. + int a1 = 0; + + List a2 = List(Richards.DATA_SIZE); + + Packet(this.link, this.id, this.kind); + + /// Add this packet to the end of a worklist, and return the worklist. + Packet addTo(Packet queue) { + link = null; + if (queue == null) return this; + Packet peek, next = queue; + while ((peek = next.link) != null) { + next = peek; + } + next.link = this; + return queue; + } + + @override + String toString() => 'Packet'; +} diff --git a/benchmarks/Richards/javascript/Richards.js b/benchmarks/Richards/javascript/Richards.js new file mode 100644 index 00000000000..8a71f99ca26 --- /dev/null +++ b/benchmarks/Richards/javascript/Richards.js @@ -0,0 +1,536 @@ +// Copyright 2006-2008 the V8 project authors. All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +// This is a JavaScript implementation of the Richards +// benchmark from: +// +// http://www.cl.cam.ac.uk/~mr10/Bench.html +// +// The benchmark was originally implemented in BCPL by +// Martin Richards. + + +/** + * The Richards benchmark simulates the task dispatcher of an + * operating system. + **/ +function runRichards() { + var scheduler = new Scheduler(); + scheduler.addIdleTask(ID_IDLE, 0, null, COUNT); + + var queue = new Packet(null, ID_WORKER, KIND_WORK); + queue = new Packet(queue, ID_WORKER, KIND_WORK); + scheduler.addWorkerTask(ID_WORKER, 1000, queue); + + queue = new Packet(null, ID_DEVICE_A, KIND_DEVICE); + queue = new Packet(queue, ID_DEVICE_A, KIND_DEVICE); + queue = new Packet(queue, ID_DEVICE_A, KIND_DEVICE); + scheduler.addHandlerTask(ID_HANDLER_A, 2000, queue); + + queue = new Packet(null, ID_DEVICE_B, KIND_DEVICE); + queue = new Packet(queue, ID_DEVICE_B, KIND_DEVICE); + queue = new Packet(queue, ID_DEVICE_B, KIND_DEVICE); + scheduler.addHandlerTask(ID_HANDLER_B, 3000, queue); + + scheduler.addDeviceTask(ID_DEVICE_A, 4000, null); + + scheduler.addDeviceTask(ID_DEVICE_B, 5000, null); + + scheduler.schedule(); + + if (scheduler.queueCount != EXPECTED_QUEUE_COUNT || + scheduler.holdCount != EXPECTED_HOLD_COUNT) { + var msg = + "Error during execution: queueCount = " + scheduler.queueCount + + ", holdCount = " + scheduler.holdCount + "."; + throw new Error(msg); + } +} + +var COUNT = 1000; + +/** + * These two constants specify how many times a packet is queued and + * how many times a task is put on hold in a correct run of richards. + * They don't have any meaning a such but are characteristic of a + * correct run so if the actual queue or hold count is different from + * the expected there must be a bug in the implementation. + **/ +var EXPECTED_QUEUE_COUNT = 2322; +var EXPECTED_HOLD_COUNT = 928; + + +/** + * A scheduler can be used to schedule a set of tasks based on their relative + * priorities. Scheduling is done by maintaining a list of task control blocks + * which holds tasks and the data queue they are processing. + * @constructor + */ +function Scheduler() { + this.queueCount = 0; + this.holdCount = 0; + this.blocks = new Array(NUMBER_OF_IDS); + this.list = null; + this.currentTcb = null; + this.currentId = null; +} + +var ID_IDLE = 0; +var ID_WORKER = 1; +var ID_HANDLER_A = 2; +var ID_HANDLER_B = 3; +var ID_DEVICE_A = 4; +var ID_DEVICE_B = 5; +var NUMBER_OF_IDS = 6; + +var KIND_DEVICE = 0; +var KIND_WORK = 1; + +/** + * Add an idle task to this scheduler. + * @param {int} id the identity of the task + * @param {int} priority the task's priority + * @param {Packet} queue the queue of work to be processed by the task + * @param {int} count the number of times to schedule the task + */ +Scheduler.prototype.addIdleTask = function (id, priority, queue, count) { + this.addRunningTask(id, priority, queue, new IdleTask(this, 1, count)); +}; + +/** + * Add a work task to this scheduler. + * @param {int} id the identity of the task + * @param {int} priority the task's priority + * @param {Packet} queue the queue of work to be processed by the task + */ +Scheduler.prototype.addWorkerTask = function (id, priority, queue) { + this.addTask(id, priority, queue, new WorkerTask(this, ID_HANDLER_A, 0)); +}; + +/** + * Add a handler task to this scheduler. + * @param {int} id the identity of the task + * @param {int} priority the task's priority + * @param {Packet} queue the queue of work to be processed by the task + */ +Scheduler.prototype.addHandlerTask = function (id, priority, queue) { + this.addTask(id, priority, queue, new HandlerTask(this)); +}; + +/** + * Add a handler task to this scheduler. + * @param {int} id the identity of the task + * @param {int} priority the task's priority + * @param {Packet} queue the queue of work to be processed by the task + */ +Scheduler.prototype.addDeviceTask = function (id, priority, queue) { + this.addTask(id, priority, queue, new DeviceTask(this)) +}; + +/** + * Add the specified task and mark it as running. + * @param {int} id the identity of the task + * @param {int} priority the task's priority + * @param {Packet} queue the queue of work to be processed by the task + * @param {Task} task the task to add + */ +Scheduler.prototype.addRunningTask = function (id, priority, queue, task) { + this.addTask(id, priority, queue, task); + this.currentTcb.setRunning(); +}; + +/** + * Add the specified task to this scheduler. + * @param {int} id the identity of the task + * @param {int} priority the task's priority + * @param {Packet} queue the queue of work to be processed by the task + * @param {Task} task the task to add + */ +Scheduler.prototype.addTask = function (id, priority, queue, task) { + this.currentTcb = new TaskControlBlock(this.list, id, priority, queue, task); + this.list = this.currentTcb; + this.blocks[id] = this.currentTcb; +}; + +/** + * Execute the tasks managed by this scheduler. + */ +Scheduler.prototype.schedule = function () { + this.currentTcb = this.list; + while (this.currentTcb != null) { + if (this.currentTcb.isHeldOrSuspended()) { + this.currentTcb = this.currentTcb.link; + } else { + this.currentId = this.currentTcb.id; + this.currentTcb = this.currentTcb.run(); + } + } +}; + +/** + * Release a task that is currently blocked and return the next block to run. + * @param {int} id the id of the task to suspend + */ +Scheduler.prototype.release = function (id) { + var tcb = this.blocks[id]; + if (tcb == null) return tcb; + tcb.markAsNotHeld(); + if (tcb.priority > this.currentTcb.priority) { + return tcb; + } else { + return this.currentTcb; + } +}; + +/** + * Block the currently executing task and return the next task control block + * to run. The blocked task will not be made runnable until it is explicitly + * released, even if new work is added to it. + */ +Scheduler.prototype.holdCurrent = function () { + this.holdCount++; + this.currentTcb.markAsHeld(); + return this.currentTcb.link; +}; + +/** + * Suspend the currently executing task and return the next task control block + * to run. If new work is added to the suspended task it will be made runnable. + */ +Scheduler.prototype.suspendCurrent = function () { + this.currentTcb.markAsSuspended(); + return this.currentTcb; +}; + +/** + * Add the specified packet to the end of the worklist used by the task + * associated with the packet and make the task runnable if it is currently + * suspended. + * @param {Packet} packet the packet to add + */ +Scheduler.prototype.queue = function (packet) { + var t = this.blocks[packet.id]; + if (t == null) return t; + this.queueCount++; + packet.link = null; + packet.id = this.currentId; + return t.checkPriorityAdd(this.currentTcb, packet); +}; + +/** + * A task control block manages a task and the queue of work packages associated + * with it. + * @param {TaskControlBlock} link the preceding block in the linked block list + * @param {int} id the id of this block + * @param {int} priority the priority of this block + * @param {Packet} queue the queue of packages to be processed by the task + * @param {Task} task the task + * @constructor + */ +function TaskControlBlock(link, id, priority, queue, task) { + this.link = link; + this.id = id; + this.priority = priority; + this.queue = queue; + this.task = task; + if (queue == null) { + this.state = STATE_SUSPENDED; + } else { + this.state = STATE_SUSPENDED_RUNNABLE; + } +} + +/** + * The task is running and is currently scheduled. + */ +var STATE_RUNNING = 0; + +/** + * The task has packets left to process. + */ +var STATE_RUNNABLE = 1; + +/** + * The task is not currently running. The task is not blocked as such and may +* be started by the scheduler. + */ +var STATE_SUSPENDED = 2; + +/** + * The task is blocked and cannot be run until it is explicitly released. + */ +var STATE_HELD = 4; + +var STATE_SUSPENDED_RUNNABLE = STATE_SUSPENDED | STATE_RUNNABLE; +var STATE_NOT_HELD = ~STATE_HELD; + +TaskControlBlock.prototype.setRunning = function () { + this.state = STATE_RUNNING; +}; + +TaskControlBlock.prototype.markAsNotHeld = function () { + this.state = this.state & STATE_NOT_HELD; +}; + +TaskControlBlock.prototype.markAsHeld = function () { + this.state = this.state | STATE_HELD; +}; + +TaskControlBlock.prototype.isHeldOrSuspended = function () { + return (this.state & STATE_HELD) != 0 || (this.state == STATE_SUSPENDED); +}; + +TaskControlBlock.prototype.markAsSuspended = function () { + this.state = this.state | STATE_SUSPENDED; +}; + +TaskControlBlock.prototype.markAsRunnable = function () { + this.state = this.state | STATE_RUNNABLE; +}; + +/** + * Runs this task, if it is ready to be run, and returns the next task to run. + */ +TaskControlBlock.prototype.run = function () { + var packet; + if (this.state == STATE_SUSPENDED_RUNNABLE) { + packet = this.queue; + this.queue = packet.link; + if (this.queue == null) { + this.state = STATE_RUNNING; + } else { + this.state = STATE_RUNNABLE; + } + } else { + packet = null; + } + return this.task.run(packet); +}; + +/** + * Adds a packet to the worklist of this block's task, marks this as runnable if + * necessary, and returns the next runnable object to run (the one + * with the highest priority). + */ +TaskControlBlock.prototype.checkPriorityAdd = function (task, packet) { + if (this.queue == null) { + this.queue = packet; + this.markAsRunnable(); + if (this.priority > task.priority) return this; + } else { + this.queue = packet.addTo(this.queue); + } + return task; +}; + +TaskControlBlock.prototype.toString = function () { + return "tcb { " + this.task + "@" + this.state + " }"; +}; + +/** + * An idle task doesn't do any work itself but cycles control between the two + * device tasks. + * @param {Scheduler} scheduler the scheduler that manages this task + * @param {int} v1 a seed value that controls how the device tasks are scheduled + * @param {int} count the number of times this task should be scheduled + * @constructor + */ +function IdleTask(scheduler, v1, count) { + this.scheduler = scheduler; + this.v1 = v1; + this.count = count; +} + +IdleTask.prototype.run = function (packet) { + this.count--; + if (this.count == 0) return this.scheduler.holdCurrent(); + if ((this.v1 & 1) == 0) { + this.v1 = this.v1 >> 1; + return this.scheduler.release(ID_DEVICE_A); + } else { + this.v1 = (this.v1 >> 1) ^ 0xD008; + return this.scheduler.release(ID_DEVICE_B); + } +}; + +IdleTask.prototype.toString = function () { + return "IdleTask" +}; + +/** + * A task that suspends itself after each time it has been run to simulate + * waiting for data from an external device. + * @param {Scheduler} scheduler the scheduler that manages this task + * @constructor + */ +function DeviceTask(scheduler) { + this.scheduler = scheduler; + this.v1 = null; +} + +DeviceTask.prototype.run = function (packet) { + if (packet == null) { + if (this.v1 == null) return this.scheduler.suspendCurrent(); + var v = this.v1; + this.v1 = null; + return this.scheduler.queue(v); + } else { + this.v1 = packet; + return this.scheduler.holdCurrent(); + } +}; + +DeviceTask.prototype.toString = function () { + return "DeviceTask"; +}; + +/** + * A task that manipulates work packets. + * @param {Scheduler} scheduler the scheduler that manages this task + * @param {int} v1 a seed used to specify how work packets are manipulated + * @param {int} v2 another seed used to specify how work packets are manipulated + * @constructor + */ +function WorkerTask(scheduler, v1, v2) { + this.scheduler = scheduler; + this.v1 = v1; + this.v2 = v2; +} + +WorkerTask.prototype.run = function (packet) { + if (packet == null) { + return this.scheduler.suspendCurrent(); + } else { + if (this.v1 == ID_HANDLER_A) { + this.v1 = ID_HANDLER_B; + } else { + this.v1 = ID_HANDLER_A; + } + packet.id = this.v1; + packet.a1 = 0; + for (var i = 0; i < DATA_SIZE; i++) { + this.v2++; + if (this.v2 > 26) this.v2 = 1; + packet.a2[i] = this.v2; + } + return this.scheduler.queue(packet); + } +}; + +WorkerTask.prototype.toString = function () { + return "WorkerTask"; +}; + +/** + * A task that manipulates work packets and then suspends itself. + * @param {Scheduler} scheduler the scheduler that manages this task + * @constructor + */ +function HandlerTask(scheduler) { + this.scheduler = scheduler; + this.v1 = null; + this.v2 = null; +} + +HandlerTask.prototype.run = function (packet) { + if (packet != null) { + if (packet.kind == KIND_WORK) { + this.v1 = packet.addTo(this.v1); + } else { + this.v2 = packet.addTo(this.v2); + } + } + if (this.v1 != null) { + var count = this.v1.a1; + var v; + if (count < DATA_SIZE) { + if (this.v2 != null) { + v = this.v2; + this.v2 = this.v2.link; + v.a1 = this.v1.a2[count]; + this.v1.a1 = count + 1; + return this.scheduler.queue(v); + } + } else { + v = this.v1; + this.v1 = this.v1.link; + return this.scheduler.queue(v); + } + } + return this.scheduler.suspendCurrent(); +}; + +HandlerTask.prototype.toString = function () { + return "HandlerTask"; +}; + +/* --- * + * P a c k e t + * --- */ + +var DATA_SIZE = 4; + +/** + * A simple package of data that is manipulated by the tasks. The exact layout + * of the payload data carried by a packet is not importaint, and neither is the + * nature of the work performed on packets by the tasks. + * + * Besides carrying data, packets form linked lists and are hence used both as + * data and worklists. + * @param {Packet} link the tail of the linked list of packets + * @param {int} id an ID for this packet + * @param {int} kind the type of this packet + * @constructor + */ +function Packet(link, id, kind) { + this.link = link; + this.id = id; + this.kind = kind; + this.a1 = 0; + this.a2 = new Array(DATA_SIZE); +} + +/** + * Add this packet to the end of a worklist, and return the worklist. + * @param {Packet} queue the worklist to add this packet to + */ +Packet.prototype.addTo = function (queue) { + this.link = null; + if (queue == null) return this; + var peek, next = queue; + while ((peek = next.link) != null) + next = peek; + next.link = this; + return queue; +}; + +Packet.prototype.toString = function () { + return "Packet"; +}; + +Benchmark.report("Richards", runRichards);