Commit ba62abef authored by ram's avatar ram
Browse files

Initial revision

parent 2e6fb6d8
Newsgroups: comp.lang.smalltalk
Distribution: comp
Subject: Smalltalk vs. C(++) performance
Executive summary: a non-trivial benchmark written in C++ and
Smalltalk is available from otis.stanford.edu; please send me your
results.
-------------------------------------------------------------------
As some have pointed out, it is difficult to compare the runtime
performance of Smalltalk programs with the performance of equivalent C
programs. One reason for this is that for most non-trivial programs
there is no equivalent program written in the other language (because
it would be a non-trivial effort to write it).
The "best" benchmark I know of is the Richards benchmark, an operating
system simulation. It is written in an object-oriented style, uses
polymorphism, and is reasonably non-trivial (700 lines). It's
probably not the world's greatest benchmark, but better than
micro-benchamrks, and it is available in Smalltalk, Self, T (an
object-oriented version of Scheme) and C++.
[Historical note: the Richards benchmark was originally written in
BCPL by Mark Richards. Many thanks to L. Peter Deutsch for the
Smalltalk version.]
The sources for Richards are available from otis.stanford.edu
(36.22.0.201) in /pub/benchmarks. I would be interested in
comparisons of the performance of the C++ and Smalltalk versions on
various systems. We measured it a while ago for the Sun-3/4 versions
of PP 2.4, and the difference was about a factor of 10.
Disclaimer: Richards is *not* a typical application: it is relatively
small and contains no graphics or other user interaction. Thus it may
not reflect the relative performance of Your Own Real-World (TM)
Application, but I think it tests the efficiency of the basic language
mechanisms fairly well. If you think you have a better benchmark
which is available both in ST-80 and C (or Fortran or...), please let
me know.
******* ADVERTISEMENT ********
The goal of the Self project at Stanford is to improve the performance
of dynamically-typed object-oriented languages such as Smalltalk and
Self. Though similar to Smalltalk, Self is simpler and more flexible.
Our current system runs significantly faster than any Smalltalk
implementation we know of. For example, here are the numbers for
Richards on a Sun-4/260:
C++ (-O2) 730ms
Self 2160ms (Nov'90; 1940ms for an experimental system)
PP ST-80 2.4 7740ms
T 9800ms (8100ms with some not-so-kosher "tuning")
[On some other benchmarks, the Stanford Integer Benchmarks, Self
actually looks even better, running at around 60-70% of the speed of
optimized C.]
More information (papers, documentation, how to get the current system)
is available via anonymous ftp from otis.stanford.edu.
******* END OF ADVERTISEMENT ********
/* cbase.h */
#ifndef CBASE
#define CBASE
#define BOOLEAN int
#define TRUE 1
#define FALSE 0
#include <stdio.h>
#include <ctype.h>
#include <assert.h>
#endif
/* rbase.h */
#ifndef RBASE
#define RBASE
typedef enum {DevicePacket, WorkPacket}
PacketKind;
typedef enum {Idler, Worker, HandlerA, HandlerB, DeviceA, DeviceB}
Identity;
#define NTASKS 6 /* # elements of Identity */
/* = # of tasks */
#define NoWork NULL
#define NoTask NULL
#endif
/* richards.c - Richards Benchmark in C++ */
/* uh 2/2/89 */
#include <time.h>
#define clock_t long /* because Sun library isn't ANSI */
#define CLK_TCK 1000000
#include "richards.h"
/*
// creation
*/
void RBench::CreateDevice (Identity id, int prio, Packet *work,
TaskState *state)
{ DeviceTaskRec *data;
TaskControlBlock *t;
data = new DeviceTaskRec;
t = new DeviceTCB(taskList, id, prio, work, state, data);
EnterTask(id, t);
}
void RBench::CreateHandler(Identity id, int prio, Packet *work,
TaskState *state)
{ HandlerTaskRec *data;
TaskControlBlock *t;
data = new HandlerTaskRec;
t = new HandlerTCB(taskList, id, prio, work, state, data);
EnterTask(id, t);
}
void RBench::CreateIdler (Identity id, int prio, Packet *work,
TaskState *state)
{ IdleTaskRec *data;
TaskControlBlock *t;
data = new IdleTaskRec;
t = new IdlerTCB(taskList, id, prio, work, state, data);
EnterTask(id, t);
}
void RBench::CreateWorker (Identity id, int prio, Packet *work,
TaskState *state)
{ WorkerTaskRec *data;
TaskControlBlock *t;
data = new WorkerTaskRec;
t = new WorkerTCB(taskList, id, prio, work, state, data);
EnterTask(id, t);
}
void RBench::EnterTask(Identity id, TaskControlBlock *t)
{
taskList = t;
taskTable[id] = t;
}
/*
//private
*/
TaskControlBlock *RBench::FindTask(Identity id)
{ TaskControlBlock *t;
t = taskTable[id];
if (t == NULL) printf("***error: FindTask failed! ");
return t;
}
TaskControlBlock *RBench::HoldSelf()
{
holdCount++;
currentTask->SetTaskHolding(TRUE);
return currentTask->Link();
}
TaskControlBlock *RBench::QueuePacket(Packet *p)
{ TaskControlBlock *t;
t = FindTask(p->Ident());
queuePacketCount++;
p->SetLink(NoWork);
p->SetIdent(currentTaskIdent);
return t->AddPacket(p, currentTask);
}
TaskControlBlock *RBench::Release(Identity id)
{ TaskControlBlock *t;
t = FindTask(id);
t->SetTaskHolding(FALSE);
return (t->Priority() > currentTask->Priority()) ? t : currentTask;
}
void RBench::Trace(Identity id)
{
if(! --layout) {printf("\n"); layout = 50;}
printf("%d", id + 1);
}
TaskControlBlock *RBench::Wait()
{
currentTask->SetTaskWaiting(TRUE);
return currentTask;
}
/*
//scheduling
*/
void RBench::Schedule()
{
currentTask = taskList;
while (currentTask != NoTask) {
if (currentTask->IsTaskHoldingOrWaiting())
currentTask = currentTask->Link();
else {
currentTaskIdent = currentTask->Ident();
if (tracing) Trace(currentTaskIdent);
currentTask = currentTask->RunTask();
}
}
}
/*
//initializing
*/
void RBench::InitScheduler()
{
queuePacketCount = 0;
holdCount = 0;
for (int i = 0; i < NTASKS; i++) {taskTable[i] = NoTask;}
taskList = NoTask;
}
void RBench::InitTrace()
{ char c;
printf("Trace (y/n)? ");
c = getchar();
tracing = (_toupper(c) == 'Y');
}
void RBench::Start(BOOLEAN trace) {
// clock_t t1, t2, t3, t4;
TaskState *t;
Packet *workQ;
if (trace) InitTrace(); else tracing = FALSE;
InitScheduler();
// t1 = clock();
// printf("\nRichards benchmark: initializing...\n");
t = new TaskState; t->Running(); // Idler
CreateIdler(Idler, 0, NoWork, t);
workQ = new Packet(NoWork, Worker, WorkPacket); // Worker
workQ = new Packet(workQ , Worker, WorkPacket);
t = new TaskState; t->WaitingWithPacket();
CreateWorker(Worker, 1000, workQ, t);
workQ = new Packet(NoWork, DeviceA, DevicePacket); // HandlerA
workQ = new Packet(workQ , DeviceA, DevicePacket);
workQ = new Packet(workQ , DeviceA, DevicePacket);
t = new TaskState; t->WaitingWithPacket();
CreateHandler(HandlerA, 2000, workQ, t);
workQ = new Packet(NoWork, DeviceB, DevicePacket); // HandlerB
workQ = new Packet(workQ , DeviceB, DevicePacket);
workQ = new Packet(workQ , DeviceB, DevicePacket);
t = new TaskState; t->WaitingWithPacket();
CreateHandler(HandlerB, 3000, workQ, t);
t = new TaskState; t->Waiting(); // DeviceA
CreateDevice(DeviceA, 4000, NoWork, t);
t = new TaskState; t->Waiting(); // DeviceB
CreateDevice(DeviceB, 5000, NoWork, t);
// printf("starting...\n");
// t2 = clock();
Schedule();
// t3 = clock();
// printf("done.\n");
// printf("QueuePacketCount = %d, HoldCount = %d.\nThese results are %s",
// queuePacketCount, holdCount,
// (queuePacketCount == 23246 && holdCount == 9297) ?
// "correct." : "wrong!"
// );
if (! (queuePacketCount == 23246 && holdCount == 9297)) {
printf("error: richards results are incorrect\n");
}
// t4 = clock();
// printf("\nScheduler time = %g seconds, total time = %g\n",
// (double)(t3 - t2) / CLK_TCK,
// (double)(t4 - t1) / CLK_TCK);
}
#define ITER 10 /* # of iterations in main loop */
int main()
{ clock_t t_start, t_stop;
t_start = clock();
for (int i = 0; i < ITER; i++)
bm.Start(ITER == 1);
t_stop = clock();
clock_t diff = (t_stop - t_start)/ITER;
printf("richards: %d ms\n", diff/(CLK_TCK/1000));
// if (ITER > 1)
// printf("\n*** %d iterations, average of %g secs / iteration.\n",
// ITER,
// (double)(t_stop - t_start) / (ITER * CLK_TCK));
}
/* richards.h */
/* Richards benchmark in C++, translated from Smalltalk */
/* uh 2/2/89 */
#include "rbase.h"
#include "tasks.h"
/*
// RBench class definition
*/
class RBench {
private:
TaskControlBlock *taskList, *currentTask;
Identity currentTaskIdent;
TaskControlBlock *taskTable[NTASKS];
int layout;
int holdCount, queuePacketCount;
/* creation */
void CreateDevice (Identity id, int prio, Packet *work,
TaskState *state);
void CreateHandler(Identity id, int prio, Packet *work,
TaskState *state);
void CreateIdler (Identity id, int prio, Packet *work,
TaskState *state);
void CreateWorker (Identity id, int prio, Packet *work,
TaskState *state);
void EnterTask(Identity id, TaskControlBlock *t);
/* scheduling */
void Schedule();
/* initializing */
void InitScheduler();
void InitTrace();
public:
/* task management */
TaskControlBlock *FindTask(Identity id);
TaskControlBlock *HoldSelf();
TaskControlBlock *QueuePacket(Packet *p);
TaskControlBlock *Release(Identity id);
TaskControlBlock *Wait();
/* tracing */
BOOLEAN tracing;
void Trace(Identity id);
void Start(BOOLEAN askTrace);
};
RBench bm; // benchmark currently executing
(in-package "USER")
#+declare-unsafe
(proclaim '(optimize speed (space 0) (safety 0) (compilation-speed 0)))
(defconstant deviceA 5)
(defconstant deviceB 6)
(defconstant devicePacketKind 1)
(defconstant handlerA 3)
(defconstant handlerB 4)
(defconstant idler 1)
(defconstant noWork nil)
(defconstant noTask nil)
(defconstant worker 2)
(defconstant workPacketKind 2)
(defvar taskList noTask)
(defvar currentTask nil)
(defvar currentTaskIdentity nil)
(defvar taskTable (make-array 6 :initial-element noTask))
(proclaim '(simple-vector taskTable))
(defvar tracing nil)
(defvar layout 0)
(defvar queuePacketCount 0)
(defvar holdCount 0)
(proclaim '(fixnum layout queuePacketCount holdCount))
(declaim (inline make-taskControlBlock make-packet make-deviceTaskDataRecord
make-handlerTaskDataRecord make-idleTaskDataRecord
make-workerTaskDataRecord wait))
(defstruct (taskControlBlock (:constructor make-taskControlBLock ()))
packetPending taskWaiting taskHolding link identity
(priority 0 :type fixnum)
input state handle)
(defstruct (packet (:constructor make-packet ()))
link identity
(kind 0 :type fixnum)
(datum 0 :type fixnum)
(data '#() :type simple-vector))
(defstruct (deviceTaskDataRecord (:constructor make-deviceTaskDataRecord ()))
pending)
(defstruct (handlerTaskDataRecord (:constructor make-handlerTaskDataRecord ()))
workIn deviceIn)
(defstruct (idleTaskDataRecord (:constructor make-idleTaskDataRecord ()))
(control 0 :type fixnum)
(count 0 :type fixnum))
(defstruct (workerTaskDataRecord (:constructor make-workerTaskDataRecord ()))
(destination 0 :type fixnum)
(count 0 :type fixnum))
(defun wait ()
(setf (taskControlBlock-taskWaiting currentTask) t)
currentTask)
#+cmu
(declaim (ext:freeze-type taskControlBlock packet deviceTaskDataRecord
handlerTaskDataRecord idleTaskDataRecord
workerTaskDataRecord))
#+cmu
(declaim (ext:start-block richards))
(defun deviceTaskDataRecord-run (self work)
(let ((functionWork work))
(if (eq noWork functionWork)
(progn
(setq functionWork (deviceTaskDataRecord-pending self))
(if (eq noWork functionWork)
(wait)
(progn
(setf (deviceTaskDataRecord-pending self) noWork)
(queuePacket functionWork))))
(progn
(setf (deviceTaskDataRecord-pending self) functionWork)
(if tracing (trace-it (packet-datum functionWork)))
(holdSelf)))))
(defun handlerTaskDataRecord-run (self work)
(if (eq noWork work)
nil
(if (= workPacketKind (packet-kind work))
(workInAdd self work)
(deviceInAdd self work)))
(let ((workPacket (handlerTaskDataRecord-workIn self)))
(if (eq noWork workPacket)
(wait)
(let ((count (packet-datum workPacket)))
(if (> count 4)
(progn
(setf (handlerTaskDataRecord-workIn self)
(packet-link workPacket))
(queuePacket workPacket))
(let ((devicePacket (handlerTaskDataRecord-deviceIn self)))
(if (eq noWork devicePacket)
(wait)
(progn
(setf (handlerTaskDataRecord-deviceIn self)
(packet-link devicePacket))
(setf (packet-datum devicePacket)
(svref (packet-data workPacket) (- count 1)))
(setf (packet-datum workPacket) (+ count 1))
(queuePacket devicePacket)))))))))
(defun idleTaskDataRecord-run (self work)
(declare (ignore work))
(setf (idleTaskDataRecord-count self)
(- (idleTaskDataRecord-count self) 1))
(if (= 0 (idleTaskDataRecord-count self))
(holdSelf)
(if (= 0 (logand (idleTaskDataRecord-control self) 1))
(progn
(setf (idleTaskDataRecord-control self)
(floor (idleTaskDataRecord-control self) 2))
(release deviceA))
(progn
(setf (idleTaskDataRecord-control self)
(logxor (floor (idleTaskDataRecord-control self) 2)
53256))
(release deviceB)))))
(defun workerTaskDataRecord-run (self work)
(if (eq noWork work)
(wait)
(progn
(setf (workerTaskDataRecord-destination self)
(if (= handlerA (workerTaskDataRecord-destination self))
handlerB
handlerA))
(setf (packet-identity work) (workerTaskDataRecord-destination self))
(setf (packet-datum work) 1)
(do ((i 0 (+ i 1)))
((> i 3) nil)
(declare (fixnum i))
(setf (workerTaskDataRecord-count self)
(+ (workerTaskDataRecord-count self) 1))
(if (> (workerTaskDataRecord-count self) 256)
(setf (workerTaskDataRecord-count self) 1))
(setf (svref (packet-data work) i)
(the fixnum
(+ (char-code #\A)
(- (workerTaskDataRecord-count self) 1)))))
(queuePacket work))))
(defun appendHead (packet queueHead)
(setf (packet-link packet) noWork)
(if (eq noWork queueHead)
packet
(let ((mouse queueHead))
(let ((link (packet-link mouse)))
(do ()
((eq noWork link) nil)
(setq mouse link)
(setq link (packet-link mouse)))
(setf (packet-link mouse) packet)
queueHead))))
(defun initialize-globals ()
(setq taskList noTask)
(setq currentTask nil)
(setq currentTaskIdentity nil)
(setq taskTable (make-array 6 :initial-element noTask))
(setq tracing nil)
(setq layout 0)
(setq queuePacketCount 0)
(setq holdCount 0))
(defun richards ()
(initialize-globals)
(createIdler idler 0 noWork (running (make-taskControlBlock)))
(let ((workQ))
(setq workQ (createPacket noWork worker workPacketKind))
(setq workQ (createPacket workQ worker workPacketKind))
(createWorker worker 1000 workQ (waitingWithPacket))
(setq workQ (createPacket noWork deviceA devicePacketKind))
(setq workQ (createPacket workQ deviceA devicePacketKind))
(setq workQ (createPacket workQ deviceA devicePacketKind))
(createHandler handlerA 2000 workQ (waitingWithPacket))
(setq workQ (createPacket noWork deviceB devicePacketKind))
(setq workQ (createPacket workQ deviceB devicePacketKind))
(setq workQ (createPacket workQ deviceB devicePacketKind))
(createHandler handlerB 3000 workQ (waitingWithPacket))
(createDevice deviceA 4000 noWork (waiting))
(createDevice deviceB 5000 noWork (waiting))
)
(schedule)
(if (not (and (= queuePacketCount 23246) (= holdCount 9297)))
(error "richards results incorrect"))
nil)
(defun schedule ()
(setq currentTask taskList)
(do ()
((eq noTask currentTask) nil)
(if (isTaskHoldingOrWaiting currentTask)
(setq currentTask (taskControlBlock-link currentTask))
(progn
(setq currentTaskIdentity (taskControlBlock-identity currentTask))
(if tracing (trace-it currentTaskIdentity))
(setq currentTask (runTask currentTask))))))
(defun findTask (identity)
(declare (fixnum identity))
(let ((tk (svref taskTable (- identity 1))))
(if (eq noTask tk) (error "findTask failed"))
tk))
(defun holdSelf ()
(setq holdCount (+ holdCount 1))
(setf (taskControlBlock-taskHolding currentTask) t)
(taskControlBlock-link currentTask))
(defun queuePacket (packet)
(let ((tk (findTask (packet-identity packet))))