[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: 6502 illegal opcodes questions



mdj wrote:
Michael J. Mahon wrote:

<snip>

I think of "strict" tools as "discipline for the undisciplined", but
so much of system design is outside the realm of any formal tools, that
there is no substitute for design discipline.  A terrible fate awaits
those who think that there is.


It's also premanaged complexity, for those that have neither the time
or the resources to mangage it themselves. Tools don't necessarily have
to be strict, just work, and provide access to complex functionality
that's already proven. This is where tool and language evolution is
key. This complexity goes up all the time, while human discipline
evolves slowly and has real, known limits. In order to build more
complex systems, more complex toolsets that encapsulate that complexity
must be employed.

Of course there is a need for higher levels of abstraction.

But much of the discussion here has been driven by "strongly typed"
and "restricted" languages and semantics vs. "weakly typed" and
unrestricted (nonportable) languages.

The presumed superiority of the former is that it either:
1) makes it harder to do bad things accidentally,
2) makes it harder to do bad things intentionally,
3) makes it easier to do good things accidentally, or
4) makes it easier to do good things intentionally.

I note that 1) and 3) (the unintentional flavors) can be readily
supported, and can be construed as support for relative newbies.

It is much more difficult to make a case for 2), since intention
knows no bounds, and I think we have all seen cases where the
finest language has been horribly abused.

It is certainly possible to write non-portable code in any sufficiently
complex system, simply by exploiting some irregular behavior--and there
will *always* be some irregular behavior to be exploited.  ;-)

It is also possible to write spaghetti code in any language, if
only by writing it as a state sequencer in a looped case statement.

Case 4) is the most interesting, since it offers the real possibility
of improvment by raising the level of abstraction.  It is worth noting,
though, that most abstractions employed in more modern languages were
developed by designers working in less restrictive languages, doing
explicitly what was later encapsulated implicitly.

Therefore, a less abstract language, far from preventing a good designer
from doing beautiful abstractions, actually provides the medium for the
abstractions to be created and prove their worth.  It remains to be seen
whether abstract languages like Java will similarly give birth to new
levels of abstraction "beyond objects".

If it were not for languages with explicit pointers, all our current
"object oriented" languages would never have come to be.  The first
such programs were written in assembly language, and were far more
versatile and efficient in their realm than comparable programs
written today.  (Sutherland's Sketchpad comes immediately to mind.)

Further, though abstraction is essential, the best designers are
characterized by a much wider "abstraction bandwidth" than is
common, and their ability to span many levels of abstraction in
their thought is what allows them to create *efficient* designs,
rather than just "correct" designs.  (BTW, designers with "high
bandwidth" are always in short supply, and much of todays software
reflects their absence.)

I have been frequently appalled to find that *system* programmers
working at the lowest levels of OS design are often almost completly
ignorant of the *machine* implications of the code that they write.

In one unfortunate example, a designer had invoked a double-precision
integer divide in the OS timer interrupt routine, because they had
decided to keep the timer queue in human time units rather than
in machine ticks.  They were completley unaware that moving this
unit conversion from the enqueuing action to the dequeuing action
would have latency and performance impact throughout the system!

In another case (same OS, slightly higher level), the designer of
the process scheduler had decided to set a timeslice timer on each
process as it was launched, so that its expiration could be used to
time out the process if it did not wait for I/O prior to expiration.
If the process waited for I/O first, the timer was cancelled.

As it happened, the time required to create and enqueue each new
timer at process launch, and the time to dequeue and cancel it when
in the overwhelming majority of cases the process waited prior to
expiration, accounted for over 80 percent of the time spent in the
process scheduler!

The fix, obvious with a little "wider bandwidth" perception of things,
was to eliminate the timers, and instead place an interval count into
each process structure that was initialized at process launch time.
The standard "heartbeat" timer would then decrement this field
regularly, and, when it went to zero, notify the process scheduler
that the active process had timed out.  This accomplishes the same
functional goal, though with less time resolution, in that very small
fraction of cases where the active process does not wait prior to
expiration of its timeslice.

This "perspective shift" on the handling of a relatively rare event
resulted in speeding up the process scheduler by more than a factor
of four, and dropped the usage of the timer facility by two orders
of magnitude!

There is *no* substitute for a low-level understanding of what is
actually going on in a system.  Unfortunately, higher level tools tend
to further obscure actual system behavior by making it more "esoteric".

My second software tools phase was strict typing and enforced structure.
My mantra was, "If you think you need a macro, then something is missing
from the language."  Experienced programmers chafed at the "training
wheels" the language forced upon them.  Some of them filled their code
with unstructured "workarounds", perhaps a sign of their resentment at
the strictures of the programming environment.  (Unstructured code can
be written in any language.)


Been there too. Time has proven it doesn't work well, and that test
driven development techniques provide more safety, and allow more
flexible forms of expression in the process, easing chafing.

No argument here--test scaffolds have proven their worth many times
over, yet their adoption as a standard part of any development has
been widely rejected.  Maybe we're past that now...

My third software tools phase was "the only thing that matters is
the team".  I strove for a small team of 98th percentile people, who
implicitly understood the need for and benefits of discipline, and
who had learned this by experience.  Tools are useful, but secondary.
If a tool is really needed, it will be written.  (Structured code can
be written in any language.)

Although I don't consider any of the three approaches ideal, there
is no doubt that the third worked the best, both in terms of team
esprit and in terms of product quality (function & reliability).

Don't count too much on tools--it's the people that make the real
difference.


The problem is such teams are very hard to build, and keep. And often
the 98th percentile people are already consumed by the very companies
that produce the technology you're trying to leverage. It's really up
to say, the 90th percentile group to manage the complexity for the
rest, and provide it in more accessible forms, through tools that
support higher abstractions, allowing more to be done.

It's certainly not ideal either, and it shifts a lot of 'waste' onto
the machines. But this is the only place you can feasibly put it,
because the machines are cheap and get bigger all the time. The humans
on the other hand....

You know, I fundamentally agree with your point.  But I am sobered by
the realization that 90% of the software we are talking about is code
to download music or animate the "bounce" in a cursor bar.

The fundamentally inessential and, at most, transient importance of the
vase majority of code that is written, constrasted with the much smaller
amount of truly essential, even critical, code, makes the argument for
armies of "average" coders much less relevant.

The truth is that very little code needs to be really good.  If it's
just 2% of all the code written, then it could all be done by 98th
percentile people.  And, frankly, I don't really care whether the other
98% of code is written well, or even written at all.

It reminds me of "Sturgeon's Law".  When, at a cocktail party, Theodore
Sturgeon was asked what he did, he replied, "I write science fiction."
The response was, "Science fiction!  90% of science fiction is crap!"
To which Sturgeon replied, "Sir, 90% of *everything* is crap."

So there is no real shortage of good designers, just a poor system of
allocating them to projects.  Almost 90% of them are working on crap!

It is a frequently stated goal of language designers to make bad things
harder and good things easier.  What has never been studied is whether
these tools actually improve the productivity and quality of output of
the 2% of *great* programmers, on whom we all ultimately depend.

Most programmer productivity (not to be confused with quality, but not
unrelated) studies have shown *much* greater variance (50:1!) is
attributed to individuals rather than to the tools they use.  My own
experience supports this finding.  (BTW, "programmer productivity"
has nothing to do with "lines of code" but with "problems solved".)

I can appreciate an editor that pops up the full procedure interface
for me as soon as I type a half-dozen characters of its name, but I
note that it facilitates calling the *wrong* procedure as well as the
right one.  I'm glad that it lets *me* choose.  ;-)

This is the principle reason for evolving languages and tools. Improved
langugages allow ideas to be expressed more concisely, support
encapsulation mechanisms that allow complex modules to reused, thus
allowing complexity to be more effectively managed. Sure it's
idealistic to expect new tools solve all the problems, they don't. They
do however mitigate some of the old issues and allow some progress to
be made.

For balance, I have to point out that they also permit *needless*
complexity to be more effectively managed.  When "Hello, World!"
executes 8 megabytes of code, you know something has gone sour.
(And, yes, I do include *all* the code executed, not just the code
in the "Hello, World!" module.)


Sure. Of course, it bares pointing out that you're referring to 8mb of
code that Hello World won't execute, but will carry around as a payload
anyway. Runtime systems are getting larger that's true, but they also
only have to be loaded once, thanks to copy on write memory, and much
of the initialisation work can be cached and shared amongst running
applications. Over time the issues bought about by this approach are
being mitigated, and besides, it's good fun work finding ways to tune
it out.

Actually, I was talking about the 8MB of code--most of it *not* within
the Hellow World application--that will be executed in the act of
painting "Hello, World!" on the screen.  The 8MB of libraries embedded
in a trivial application are a separate issue (to which there are well
known but seldom applied solutions).

It's not ideal, but what's the alternative? If you don't follow this
road, a cap is placed on the possible solutions you can build. The
overheads introduced by high level abstraction systems is a very
interesting field of research, and one that great inroads into managing
has been made.

I have no problem with the upper two or three "layers" being rather
loosely bound--after all they are, or should be, in flux.

My issue is with the dozen levels beneath these, which have not changed
for a decade, and are much in need of collapsing into a much more
efficient and, yes, less portable structure.  (BTW, have you noticed
the steadily decreasing value of architectural portability?  ;-)

Code which is executed many times per day by tens of millions of people
can be justifiably optimized, since it will save *huge* amounts of human
and machine resources to do so.

I can see a time when massive parallel computing clusters 'churn'
through algorithms, fitting them to particular machines are problem
domains. Programming done by the human will be not much more than
assembling from vast libraries of prevalidated solutions. It doesn't
take much thinking into the future to imagine a time when software
complexity is so high that this is the only feasible solution to
building more complex systems.

I remain radically suspicious of any approach that minimizes the
fundamental role of conscious design--unless we plan to use genetic
algorithms to evolve programs.  (I'm not entirely kidding here, but
this is clearly not "around the corner".)

I think we're more or less on the right track, but breaking the ties to
legacy implementations that simply cannot be scaled in this way is one
of the biggest hurdles to moving further towards solving the current
issues in software design.

I'm beginning to wonder what you consider the current issues in
software design to be.

This is not the same thing as identifying important problems that
computers could help solve, though we seem to be less imaginative
here than we once were.  Nor is it the same thing as figuring out
how to solve a problem using computers, though this is a *very*
significant problem.  It would have to be issues in converting a
known solution to an important problem into a program that realizes
that solution.

In this area, the fundamental limiters I see are complexity (human
limiter), resources (human and machine limiters), and performance
(machine limiter).

I expect tools to help manage complexity, as you do, but I also
require tools to manage human and machine resources, and machine
performance.  In the (related) areas of resources and performance,
tools can be used to help visualize usage so that a designer can
make better informed tradeoffs.

I am quite disappointed with the relatively poor quality of tools
to correlate system design choiced (like class structures) with
real system metrics, like memory resources and performance.  As
we move into more concurrent systems, measures of locality will
be extremely important, and remain largely unaddressed in the
current generation of tools.

In the not too distant future, we will be able to treat today's large,
"nonportable" bodies of code, together with the behavioral spec of the
systems they run on, as a "source language" from which we will be able
to compile a program with equivalent behavior on any system we choose.

The real problem with legacy applications is that we need them to
continue to behave *exactly* the same, to avoid compatibility ripples.

The best way forward has always been not to "port" legacy apps, but to
*replace* them entirely with new subsystems, supporting new functions.

The practical useful lifetime for code is measured in the cost of
human change, not machine change, so having practical portablity
may turn out to obstruct rather than expedite the move to a truly
better solution.

-michael

Parallel computing for 8-bit Apple II's!
Home page:  http://members.aol.com/MJMahon/

"The wastebasket is our most important design
tool--and it is seriously underused."