Welcome to the Distinguished Lecture Series.
I'm Karen Matthews of University Video Communications.
Our program is designed to enrich
graduate and upper division curricula,
with academic presentations by top computer scientists
from industry.
Its speakers and topics were nominated
by computer science faculty of more than 50 universities.
This presentation is sponsored by Symbolics Inc. Tom Knight,
a major contributor to computer architectures
for artificial intelligence applications
received his PhD in electrical engineering
from the Massachusetts Institute of Technology in 1982.
In 1980, he co-founded Symbolics, and is now
their technical director.
Presently on leave from the MIT faculty,
he has served as assistant professor there since 1982.
Tom's talk today is architectural support
for symbolic computers.
It's our pleasure to introduce Tom Knight.
Dr. Knight.
Thank you.
Today I'd like to spend some time telling you
about special purpose computer architectures
for artificial intelligence applications.
I'd like to begin the talk by spending
a few minutes discussing the differences
between symbolic computing and other styles of computing,
notably the numeric styles of computing, which most
people are more familiar with.
And I'd like to move on to the subject of the languages
and operating system features, which
make it possible to run artificial intelligence
sorts of programs more effectively
on these architectures.
I'd like to spend the majority of my time
today discussing hardware support
within these architectures for the language and operating
system features I described above.
And finally, I'd like to conclude
with a discussion of some of the future directions of computer
architecture in this field.
The symbolic computing differs from conventional computing
in a number of ways.
The main difference is a difference
in emphasis on the style of computing
that is done on these machines.
In a conventional machine, by and large the architecture
is optimized for execution of large programs.
Very little attention is paid to the process
of program development, and the process
of interacting with users.
Instead, large Fortran programs typically
are used to execute-- are used to solve
large physical problems, and the dominant concern
is the concern for the speed with which those programs run.
In the symbolic world, we instead
have an emphasis on incremental software development
and the development process, rather than on the execution
speed of the resulting code.
This is not to say that the execution speed of the code
is irrelevant.
Often, you want to be able to develop programs effectively
and execute them quickly.
But that's not the whole picture.
Instead, we have to be able to construct those programs
in an effective way.
Typically, the programs that are solved
on symbolic architectures are unstructured
or poorly understood problems.
We wish to be able to construct software solutions which
consist of relatively malleable software subroutines,
and want to be able to construct reusable and flexible software.
The software is often thought of in terms of middle
out programming technology.
Usually people don't know how they
are going to solve these problems, how they
are going to construct their program before the program is
complete.
So the top down approaches to constructing programs simply
don't work.
Instead, often the programs are constructed
by building a few pieces of it, using
dummy subroutines, for example, to allow
us to test those features, and then going on and completing
the program, or perhaps changing directions
radically in the middle of writing the program.
In this process, in this development process
the debugging and the ability to modify and interact
with the program really are the crucial elements.
The programmer in this process is the most important process--
most important resource.
Programmers, after all, these days
are paid far more than the computers
that they use to solve their problems.
A typical workstation today is dropping in price very,
very rapidly.
Whereas the programmer's salary is going up almost as rapidly.
We need to be able to leverage the software
capabilities of that programmer to be able to solve problems
more effectively.
And this optimizes in a sense the process
of program development, because after all, the programmer now
becomes the dominant concern, the dominant cost
element in the system.
What sort of languages and operating system
features allow us to make this shift from an execution driven
environment to an environment where the development process
is the dominant concern?
One of the major features in the operating
system in the languages area is the ability
to support generic functions.
We'll say more about these in a minute.
Second feature is the automatic reclamation
of storage, the so-called garbage collection process.
And the third, about which I will
have much less to say today, is the process
of dynamically linking procedures together at runtime.
I'd like to spend a few minutes now talking
about generic functions and what they do for you,
and why they are important in this process.
In a symbolic computer, the operators in general
are capable of handling many different types of data.
The same program-- in this case, the simple addition operator
plus--
is capable of operating on many different types of data.
I've given some examples here.
For example, we can add together two integers like 6 and 9,
producing a result of 15.
That same program, when given a pair of arguments such as 6
and 9.1, where the second argument now
is a floating point number, produces a floating point
result. Similarly, the addition of two symbolic quantities--
these might, for example, represent vectors
perhaps implemented in the form of quaternions.
We could add together these two vectors
and produce a vector result.
The ability for the same program, for the same operator
to act on many different types of data
gives us tremendous leverage in the software development
process.
In other words, when it comes time
to write a subroutine, a subroutine
such as a square root subroutine,
it is not necessary to make a decision ahead of time
as to which type of arguments that subroutine receives.
Once we have defined that subroutine
in terms of the basic primitives of the language,
it will work with any set of arguments.
This ability to handle different types of arguments
and to do something sensible with them dynamically
at runtime is really one of the key components
of symbolic architectures.
These generic operations check the types
of the arguments coming into the operation,
and check the result of the operation
to make sure that the result of the operation
can be expressed in the correct form.
Typically, the process consists of several steps.
The types of the input data are checked prior to the operation.
Depending on how many input variables,
you may require more or less checking.
Data of an incompatible type-- for example, in the example
we saw a minute ago, we had both an integer
and a floating point number being added together--
data of incompatible types may be converted from one form
to another.
So in that add, for example, the integer
is converted into floating point format.
The operations will trap out and do that conversion process,
or perhaps error out and allow the programmer
to recover in some more manual fashion,
or perhaps call a user defined subroutine
for handling operations which are not otherwise handled
by the hardware.
And this operation of being able to trap out on the reference
data is another key characteristic
of these symbolic architectures.
Finally, after the operation is performed,
and in a lot of respects that's the easiest
part of the process, finally, when the operation
is performed, we check for exceptions.
For example, floating point, overflow or underflow,
integer overflow.
And we produce a result which has a data type which
is commensurate with the form of the result of that operation
it's producing.
As an example, if we add together
two integers, which are almost at the precision
of a 32-bit adder, we will produce an overflow
in a normal machine.
On a symbolic processor, that process
produces a result which is stored in a different form,
and produces a correct result, even
though the representation of that result
has to be done in a different way.
Next, I'd like to say a few words about the garbage
collection process.
The garbage collection is a term which
is used in the symbolic computing
community for the process of reclaiming storage which
is no longer necessary, no longer necessary
to the computation.
Traditionally, the approach of conventional languages
to this process has been to put the burden on the programmer
to explicitly return data storage which
is no longer being used by the program.
This is one of the major sources of bugs in these programs.
And any experienced programmer can tell you
that the process of debugging programs which
have incorrect storage allocation, and especially
deallocation is a tremendously difficult problem.
The book The Mythical Man-Month I
believe points this area out as one
of the major contributing factors to program bugs.
In the symbolic languages, such as Lisp and PROLOG,
this problem is avoided by eliminating the explicit return
of data under programmer control and making
the problem of reclaiming storage a process
which the system and the language are responsible for,
rather than the programmer.
This eliminates an entire class of bugs.
Early garbage collection technology
was a very expensive operation.
Two techniques were developed in the '60s as techniques
for solving the garbage collection problem.
One of them was reference counting.
Every time you constructed a new pointer to a particular object
in memory, we would increment the counter,
which kept track of how many locations were pointing
to a particular data item.
And correspondingly, every time a pointer
was destroyed to a particular location,
that counter was decremented.
The process of reference counting
reclaims objects automatically when that count reaches zero.
When in other words, there are no further locations
referencing a particular object.
The difficulty with reference counting as a strategy
is that it doesn't work on circular structures.
So if we build a structure at any time
during the process of running a program
or building a large complex environment,
then once that circular structure is constructed,
then there is no way of reclaiming
the storage associated with it.
This is because if you think of, for example,
a loop, a simply two-location loop,
where one location points to another
and that one points back to the first,
then the reference counts for those two locations are both 1,
and therefore neither of them can be reclaimed.
A second approach was the so-called mark sweep
collectors.
In this style of garbage collector,
the computation stops when the free storage runs out,
and we go through what's called a marking phase, where
we mark each location in memory, which is referenced
by certain root nodes which the ongoing computation knows
are important to it.
That process of marking each of the locations in memory traces
through all of the accessible storage in memory,
and any location which then has not
been marked as part of that process
is free to be reclaimed.
One of the difficulties with this process
is that it does not reclaim--
does not compact to the memory during this reclamation
process.
So we are left with fragmented memory,
with memory which has lots of holes in it.
And this makes it hard after we've
done several of these garbage collections
to allocate linear structures such as arrays
in an environment like this.
There's also a very poor paging locality
in a virtual memory environment using garbage collection
techniques like this.
Again, because the compaction process
does not fill up pages with valuable information.
Instead, whatever locality has been
lost as a result of the allocation process
is never reclaimed as part of this collection process.
The end result is that mark sweep collectors by and large
are not used in modern symbolic languages.
The stop and copy collectors were the next major development
in garbage collection.
Minsky, Fenichel, and Yochelson are
the people primarily responsible for this development.
Again, for this style of garbage collector,
the computation stops during the collection process.
Unlike the mark and sweep collectors,
however, this style of garbage collection compacts memory.
We'll say a few more words about how this process is
done in a minute.
This fact that we are compacting memory
leads, of course, to an easy job of allocating linear
structures, such as large arrays,
and much improved paging performance.
I'd like to tell you how this garbage collection
process works.
The Minsky-Fenichel-Yochelson collector.
To do this, I'd like to simulate the computer here for a minute.
The process starts where the processor holds pointers
into certain locations in memory.
Each of the locations in memory in general
will hold pointers to other locations in memory.
And the process, this processor is free
now to allocate new memory within this old space region.
Eventually, the allocation process, the application
of new memory by the processor will run out of space,
and old space will be full.
Now, unlike the mark sweep algorithms
which we discussed a minute ago, the Minsky-Fenichel-Yochelson
collector, copying garbage collectors,
the process they employ is to stop the computation
and to copy the valuable data items from this old space
region into a new space region.
The old space items are gradually
moved one at a time from old space to new space.
We start with the pointers that the processor holds, pointers
into this old space width.
So in other words, we start by taking
each of the pointers the processor holds
and copying the data item corresponding
to that into new space.
So for example, if we start with this pointer
right here from the processor, we
will copy this data item into new space.
So new space now will contain a pointer to the same location
that this pointer originally pointed to.
And we will update this processor pointer
to point down to this new location.
So we will destroy that pointer.
The processor now points to an object in new space.
We'll continue that with the second pointer,
and now allocate a new word in memory
in new space for this location up here.
And now destroy this pointer and update it
to point to the second location.
Now, during this process we also leave
behind a special kind of pointer.
We use the data type field of each word
to add to each of the old locations
a special kind of pointer called a forwarding pointer, which
tells where each of the locations that have been moved
has been forwarded to.
So in this case, this location will now be updated to a point
here.
And this location will be marked with a special pointer,
forwarding pointer to point here.
So we have now moved from old space into new space
the contents which are directly pointed to by the processor.
However, that's not all of the valuable information
which is held in old space.
We still have valuable information held in old space,
namely all of the things which these objects now point to.
Now we begin a process which is called scavenging.
In the scavenging process, we keep a pointer into new space.
This new space data--
this new space pointer is used to locate
all of the pointers which are in new space, which
point to objects which are still in old space.
So for example, when we first reference this location,
we see that it contains a pointer into old space.
This means we are obligated to move
the contents of this location into new space.
The way we do that is the same as we
moved the original processor pointers.
We allocate a new location in new space.
We change the pointer in old space
to point to this new location.
And we update the pointer which we
have encountered to that location
to point to this new object.
So we destroy this pointer.
Gradually, then we move our scavenging pointer
to the next location.
And we start that process again.
When the scavenging pointer runs out
of data items in new space, which have yet to be examined,
then the entire contents in old space is garbage.
And we are then free to completely eliminate this data
and perform what is sometimes called a flip, where
the contents of new space becomes old space.
And old space is reclaimed and is available for use
in the next phase of garbage collection operations.
This technique of garbage collection to review
is, however, still a stop and copy garbage collector.
What this means from a user's standpoint
is that this collector still has a serious problem in it.
The serious problem being that computation
stops for a long period of time while the garbage collection
process is in action.
For small programs, this is much less of a problem.
If we have a program that's a megaword of memory,
then the garbage collection process is quite rapid.
For very large environments, however, the process
can become quite excessive, and it becomes very annoying,
particularly if you're trying to build
an interactive application, it becomes
quite annoying to have these pauses which
can occur at any time, interrupt the interactive nature
of your computation.
So the fact that these collectors stop the world
and perform this garbage collection
has been a serious problem with garbage collection
technologies.
In about 1974, Henry Baker developed an algorithm,
which is called the Baker collector, which
solves this problem, and performs an operation known
as incremental garbage collection.
The Baker garbage collection does
something very similar to the Minsky-Fenichel-Yochelson
garbage collector.
However, it does it while computation continues
in the main processor.
I'd like to tell you a little bit about how that computation,
how that garbage collector works.
In a similar way to the way that the Minsky-Fenichel-Yochelson
collector works, we start with a set of pointers
to data items in main memory in old space.
The processor is computing along, referencing data,
and building new structures in old space.
Eventually, the space available in old space is used up.
And we are required then to garbage collect that space
and to allocate new space.
Now, what does that process look like?
The process of collecting now involves the same steps
that we saw a moment ago in the Minsky-Fenichel-Yochelson
collector.
We first move the data objects which are held in old space.
So we locate the pointers that the processor is holding.
We move those locations to new space.
So for example, in this particular case,
we have a pointer from this location to this location.
This relocation of this pointer will
involve moving this location into new space
and leaving behind, again, a forwarding
pointer in the old location.
So we will leave behind a forwarding location, forwarding
pointer in this location, change this pointer to point here,
and update this pointer to point in to new space.
And again, the process of scavenging is also necessary.
We have to relocate these other pointers as part
of this process as well.
But I won't go through that right now.
The difference between this collector
and the Minsky-Fenichel-Yochelson
collector is that the scavenging process continues
in the background while we are performing computations
in the processor.
And the way we get away with that, the trick that
allows us to do that in this particular style of collector
is that the processor, every time it
references a data item determines whether or not
the data item that it holds is a pointer into new space.
Those are the only pointers that the processor
is allowed to have.
The processor is only allowed to hold pointers into new space.
Every time, in general, however, new space
may contain pointers back into old space.
The processor is required every time
it performs a memory access to check
to assure that the pointer which it is picking up--
for example, if it references through this pointer,
picks up the contents of this data word,
that data word contains a pointer to old space.
We implement something called the barrier,
which allows the processor to determine that it is picking up
a pointer into old space.
And instead of allowing computation to proceed,
that process of loading a pointer into old space
triggers a trap in the processor.
Once that trap in the processor occurs,
that's called a transport trap, once that transport trap occurs
in the processor, we are obligated at that instant
to move the data object, which is being pointed
to by old space into new space to perform what's
called a transport of that object from old space
into new space.
Once we've done that moving of the object from old space
into new space, then of course the processor
is free to have a pointer to it, because it's now a new space
object.
This allows this process of trapping out on references
into old space allows the computation
to proceed at the same time the garbage collection is going on.
It's a major breakthrough in our ability
to build interactive environments with along
with garbage collection.
We still have to do this scavenging
work in the background.
There's a background process whose job
it is to scan through each item in new space,
moving objects found that are referenced in old space
from old space to new space.
And similarly to the Minsky-Fenichel-Yochelson
collector, when this scavenging pointer
reaches the end of allocated storage in new space,
we are done with the collection process
and we're allowed to reclaim old space entirely and throw it
away since there's no good data remaining in it.
This style of collector was first
implemented in the MIT Lisp machine in around 1974.
But there is still significant paging penalties associated
with this garbage collector.
And as we go and try to build larger and larger programming
environments, it becomes more important to make this garbage
collection process more and more efficient.
The next real stage of garbage collection development
occurred in about 1980, with the development
of what's called the ephemeral garbage collector.
And the ephemeral garbage collector,
we rely upon the fact that the lifetime of newly allocated
objects is substantially smaller than the lifetime of objects
which have already been in existence for a period of time.
In other words, newly allocated objects
tend to be allocated, used for a brief period of time,
and then thrown away.
We can take advantage of the fact
that these objects are ephemeral by making the garbage
collection process for these newly allocated objects occur
more rapidly and with a faster turnaround
time than would occur for a long-lived object,
a so-called dynamic object.
To get this idea across, we can, again,
think of memory as consisting of a number of different regions.
There might be a region of memory,
so-called static memory, which we
know we are never going to have to worry
about garbage collecting.
These are objects which are part of, perhaps,
the operating system, which we know we are never
going to have to reclaim.
Might be a class of objects which,
a smaller class in general, which is dynamic memory.
Dynamic memory is memory which might typically
be reclaimed as by use of a garbage
collector such as the Baker collector
that we talked about a moment ago.
And finally, the brand new allocated objects
that might be created as part of a short program segment which
had just been executed might be stored
in this ephemeral region.
What we would like to be able to do
is to garbage collect this ephemeral region independently
from the remainder of memory.
And the way we do this is by keeping
careful track of all of the pointers
into the ephemeral region.
The only data within the ephemeral region
which is valuable after all is data which
can be reached by a pointer.
Those pointers come from two places.
One of the places is processor registers,
which typically hold the stack, which typically holds pointers
to ephemeral objects such as this.
The other major component of pointers
into this ephemeral region is side effects
on existing data structures.
So we might, for example, have pointers from static memory
into the ephemeral region.
Or we might have pointers from dynamic memory
into the ephemeral region.
We need to be able to, if we're going
to perform garbage collection only of this ephemeral region,
it's important to be able to locate these pointers rapidly.
And hardware that assists us in rapidly identifying
the location of these pointers is
really critical to implementing these ephemeral styles
of garbage collectors efficiently.
We'll talk in a minute about what that hardware looks like.
Finally, having talked about generic operations
and operations such as operations involving storage
allocation, I'd like to spend a few minutes talking
about procedure calls, which are another major issue in terms
of high performance symbolic computing.
In a conventional machine, we typically
have a fixed number of arguments and we know at compile time
exactly what style of arguments we
are using in each of the function
calls, each of the procedure calls that we implement.
In the symbolic environment, typically the argument calls
are much more complex.
We use keyword argument calls.
We have variable numbers of procedures.
And in general, we don't know what kind of arguments
are going to be applied to a given function at compile time.
Typically, this is because we are incrementally
compiling programs.
We don't have the whole program available to us
at compile time.
And so we're going to try to do compiling
of this program in small pieces.
Object-oriented programming makes this problem even more
difficult.
Now, in symbolic computer's memory
contains not a raw collection of bits.
We instead store objects in memory, objects which
have some semantic meaning.
And we tag those objects so we can identify what kind
of object they are by adding to each location in memory
a tag field.
The tag, the tag identifier determines what kind of object
that is.
It might be pointers to other objects.
That's certainly the most common kind
of object in a language like Lisp.
It might be integers.
They might be floating point numbers.
It might be an array.
Sometimes more complex objects are also useful.
We saw a moment ago that we needed
special kinds of pointers for garbage collection,
such as the forwarding pointers used in the Baker collector.
Headers, which help us identify multi-word objects and arrays
is also very useful.
I'd like to turn now to what kind of hardware
we use to support these features in the programming language.
And this discussion will center primarily
in the area of support for generic function calls
for the garbage collector, and talk
about how we interface a machine like this with these tag
locations to the outside world.
The major point I want to make is that there
isn't a lot of hardware here.
We're not talking about doubling the size of the processor.
We're talking about adding 5% of the logic in the machine
to the processor.
The process of tag support in these machines
is handled by adding to the basic arithmetic unit
a tag lookup table and lookup table which
is based on the high order bits of the pointer address coming
as an operand into the arithmetic unit.
The tag lookup table tells us that the particular data
item that we are dealing with coming into the arithmetic unit
is of a particular type, whether it is a fixed point
number, a floating point number, whether it is a pointer,
whether it is an array.
The garbage collector region number tells us whether or not
the pointer, which is coming into the arithmetic unit,
is a pointer into old space, into new space,
or whether it is a pointer into one
of the ephemeral regions of memory,
such as we discussed a moment ago.
The amount of logic that's necessary here
really is very minor in comparison
to the amount of logic which is required in implementing
sophisticated arithmetic units.
The tag insertion is quite trivial.
And the total penalty for adding this logic is very small.
One of the places, one of the major places
this penalty arises, however, is in the main memory system,
where we must store these tag bits.
However, even there, the tag bits in a typical machine
tend to be in the 4 to 8-bit region, which I'd point out
is smaller than the amount of memory
which is allocated for storing error correction bits,
for example.
And people don't seem to have any problem
with using error correction as a technique in their processors.
Well, how much does all of this help?
Some people have said that tag logic really
isn't necessary in these machines,
and that we have the ability to do
all of this using conventional machines checking
the tags manually.
I would disagree with that.
Since every memory location, since every read and write
has to be checked manually in the conventional kinds
of architectures, there's a tremendous penalty to be paid.
Every operation must be checked.
We must check to see whether the types of the operands
are appropriate to the result. Bottom line
is that there's a very high penalty to doing this
on a conventional architecture.
To turn now to not the kinds of architectures which
we are building right now, but the kind we want to build,
there is also a strong incentive for adding tag logic
to our processors in terms of the future design
of architectures for symbolic computation.
The development of a language called MultiLisp at MIT
by Burt Halstead, and QLisp by John McCarthy and company
at Stanford involves a concept called futures.
Futures allow us to express parallelism
in a language such as Lisp by returning
a placeholder in place of a result,
when a function is called.
And then we return this placeholder immediately,
and then spawn a separate task whose job
it is to actually compute the value, which is going to go
into that placeholder location.
If we encounter such a placeholder
during the process of computing, we're
going to wait until that placeholder has been filled in
by the spawned task before we can proceed.
Here's an example.
We have a program which is constructing
a list of three elements, one of which is simply a number,
and two of which are surrounded by this special construct
called a future.
Then the computations within the range of that future
are spawned as separate tasks, separate computational tasks.
However, we return and can actually
build the list structure that specified
by this list prior to these computations completing.
This allows us to express in a very natural way
the process of implementing a parallel computation.
And a parallel computation in a much more natural way than we
would otherwise be able to do.
And the point I want to make is that every time we
reference a piece of list structure
as part of this process, we are required
to check to see whether the particular data
type that's being referenced is one of these futures.
If it is a future, we must, we are
obligated to wait for this computation to complete.
The only way we can effectively and efficiently
check to see whether this particular result is a future
is by using tag control logic in the central processor.
So to the extent that we believe in and follow up
these ideas for parallel computation,
we are also led directly in the path of tag logic
as an important element of processor design.
Well, in summary, I'd like to say
that small amounts of logic in the processor, notably
small amounts of logic to assist us
with garbage collection, small amounts of logic
to assist us in the process of checking and inserting
tags in the process--
in the data paths of the processor--
make a large difference in the performance
for this class of machines.
Not only for implementing generic operations
and solving the problem of graceful overflow
of fixed-length data fields, but also
in solving problems in the garbage collector area,
and implementing some constructs which
are going to be important to us in terms
of the future evolution of Lisp as a language.
Thank you.
I'm Judith Lemon from University Video Communications,
and our first question from faculty
is from the University of California Berkeley.
Do you think risk ideas and workstations are a fad?
Definitely not.
I think risk ideas are here to stay.
I would say, however, that the basic risk
ideas which people have usually thought of in terms
of conventional computing sorts of problems
really have to be augmented by some type control logic
and some special purpose hardware for garbage collection
assist, before I think they can effectively
address the applications which we are discussing today.
From Purdue University.
How do environments such as the Lucid environment
compared with those available on symbolic architectures?
Well, the performance that you obtain in environments
such as the Lucid compiler environment
are largely available to the programmer
by use of special purpose declarations, which
are inserted into the code.
These declarations allow the compiler
to produce instructions, which are tuned
to the types of objects which are being dealt
with in the program, and really restrict
the flexibility of the code, and remove much of the advantage
that the programmer sees, as opposed
to the execution environment.
So you end up with efficient execution.
However, the penalty for that is that the program
becomes far less flexible.
From the University of California Berkeley, again.
How much faster does the cycle time
of the straightfoward machine have
to be to perform like a Lisp machine?
That's a complicated question.
I believe, at the moment for single computer
implementations, that in many cases
the compiler can eliminate the type checks at compile time.
However, I think as we move towards a parallel sorts
of implementations, that the use of techniques such as futures,
and certainly the use of incremental garbage collectors
require that virtually every memory reference be tagged.
As a result of that, I would anticipate
that the overhead associated with reading and writing
locations from main memory could be quite substantial,
leading perhaps to factors of 2 or 3 in terms of performance.
Tom, can you comment on Symbolics' PROLOG compiler?
The implementation of PROLOG on Symbolic machines
really is very similar to the implementation
of other kinds of Symbolic languages such as Lisp.
The effective implementation of PROLOG
does involve additions of a few different types of data,
notably a particular data type called a logic variable,
which we use for binding identifiers
during the pattern matching process.
That, together with fast and careful attention
to the unify within the language use very effective performance.
One of the things that people perhaps are not aware of
is that PROLOG requires many of the same features
that Lisp does in terms of garbage collection
and the other generic sorts of operation handling mechanisms
that you find in Lisp.
How do the architectures you've discussed address programmer
productivity problems?
I think that's best answered by giving an example.
Our last architectural effort at Symbolics
is a custom microprocessor, which
was developed by a very small group of people.
It was developed with approximately a dozen people,
half of whom were devoted to building the CAD system which
allowed us to design that processor, and half of them
were devoted to actually implementing the architecture.
That process took about six man years of effort,
and is a semiconductor chip of approximately
the same complexity as a 386 or 68020 processor, which
you might find in a large number of commercial workstations.
The estimates by Intel and Motorola for those processors
were in the several hundred man years of effort,
and in most cases did not involve simultaneously
building a CAD environment.
So in that particular case, we are seeing huge productivity
advances over the industry norm in some applications
such as CAD.
Very interesting.
And a final question, and this is a little bit less technical.
What do you see as the problems a student in the university
faces today in terms of learning good system design?
I think it's a really serious problem today.
We are developing much more complex systems as we go on,
and these systems become harder and harder
to implement in their entirety within the university
environment.
This means that the students within the university
environment usually see small portions of systems
rather than systems as a whole.
And this is, I think, exacerbated by the fact
that the cost of implementing some of these large systems
is also growing rather rapidly.
It simply costs more to implement fast systems than it
does to implement slow systems.
So as we move away from the 1970s technologies such as wire
app and low overhead technology such as that,
and are required to move into an environment
where printed circuit boards, and in many cases custom
integrated circuits are necessary,
it becomes very difficult to teach students
about system level sorts of problems in the university
environment.
I'm quite concerned.
There is even a problem in doing that effectively
in the industrial setting, because typically
in the industrial setting, architectures and system level
design is controlled by relatively small groups.
And it's very difficult for a newcomer into an organization
to break into those groups.
Do you see any directions to go in terms of solutions?
Well, I think if we are going to become
other than the civil engineering sorts of departments
in the universities, that is a study about dams
rather than building them, I think that we really
have to redouble our efforts in terms of limiting what
it is that we're trying to do within the university
environment in terms of the complexity.
But still being able to address the problem of building
complete systems.
Because I think only in building a complete system and system
environments are we going to get to the kinds
of educational experience that I would like to see
in a university environment.
Thank you.
Thank you very much, Tom.
Thank you.
And thank you for joining us today.
We invite your comments and questions on this presentation.
And if you're viewing this on tape,
we've provided some postcards tucked under the video cassette
label.
Otherwise, our address is at the end of the tape.
Thank you again.