Re: object system...
- From: "cr88192" <cr88192@xxxxxxxxxxx>
- Date: Fri, 9 Jan 2009 12:09:48 +1000
"H. S. Lahman" <h.lahman@xxxxxxxxxxx> wrote in message
news:j_p9l.4319$BC4.944@xxxxxxxxxxxxxxxxxxxxxxx
Responding to Riemenschneider...
It's true that they aren't *designed* to be slower. But the price ofDo you have sources for this information? Your numbers seem high to me.
abstraction is performance. Any nontrivial C program with be 30-50%
slower than hand-tuned Assembly. Any C++ program will be 50-200% slower
than the C program.
For C at least, I would have thought that modern optimizing compilers
would be at the point of surpassing human ability, especially for
non-trivial programs.
Not publicly available. Back in the '80s I was in a process-oriented shop
that needed to convert a 3 MLOC system written in BLISS. We did a lot a
lot of experiments. We ended up building the system from scratch twice,
once in C and again in C++. We collected data on everything.
FYI, C is highly overrated for both performance and portability. BLISS
could usually get within 15-30% of hand tuned Assembly. (BLISS is also
portable at the binary file level across architectures.) There are some
fundamental problems in the C semantics that inhibit optimization, such as
loop control variables being lvalues and the switch statement syntax.
[A C compiler cannot put a loop control variable in a register without
violating the rule that a debugger should be able to monitor the lvalue
memory location as values are changed and see those change. So a C
compiler that does that sort of optimization is not a valid C compiler
because the language semantics are being violated. Note that even if one
writes a very smart debugger that looks at the register, the language
semantics is still being broken because the lvalue memory location is not
being updated as the instructions are executed. So show me a C compiler
that generates code as good as hand-tuned Assembly and I will show you a C
compiler that is not implementing the C language specification properly.]
you sure about this?...
AFAIK, this is the whole point of the 'volatile' keyword, namely that only
volatile variables are required to be always kept up to date, but a
non-volatile variable may be safely stored in a register.
or, at least I think this is how it is with C99, but I thought also C95 and
C89/C90, however I have not looked into this point in detail...
FWIW, the compilers we used were all DEC compilers. DEC used the same
optimizing rear end for all their languages (they actually pioneered
intermediate bytecodes long before the Java VM). So when the performance
was different for different languages, that reflected fundamental issues
with the language semantics going into the rear end.
someone is being stupid if they think Java invented bytecode...
but, alas, many things are older than the things they are often associated
with...
and, I personally doubt that any particular bytecode format will ever
completely displace native code (at least in the near term).
The Assembly code produced by a good optimizing compiler is, indeed, very
nearly unreadable. (One of my fonder recollections of the Good Old Days
was debugging a large application at a customer site at the Assembly level
with no source code.) But if you are an Assembly programmer, it is
readable and you can sometimes improve upon it. That's because the
compiler has to make decisions that are sometimes arbitrary and it is
usually limited by the language design in the sorts of optimizations it
can do for a given construct.
yes, it is also a compiler writing trick:
very rarely does one need to use an opcode exactly for its designated or
intended use, but very often, the meaning or use of an opcode will be
re-interpreted for whatever context it is being used in, with the main
factors being speed and behavior.
so, the idea then is not to try to engineer everything for a particular
narrow use-case or purpose, but rather, to make it based on as many general
purpose components as is reasonable, performing operations that are both
simple and direct, and can then be assembled in whatever way as is needed to
implement more specialized components.
the creation of generalized components can also save a lot of time and
effort if the project is open-ended (AKA: there is no high-level "design" or
"purpose"), and so a design change can be implemented quickly, as a few
components may be broken apart and rebuilt in a new form, and even the
central application structures can be displaced and rebuilt.
so, one specifies and implements the particular components (the rules they
follow and their external interface), and then later may assemble them into
the desired form (I can make an analogy to LEGOs, where, at least in the
past, a lego set consists of a large number of largely similar blocks, but
can then be assembled into all sorts of things).
but, alas, most other people don't share these convictions, producing lots
of codebases where most of the code is not easily reusable, and where as the
app becomes larger is becomes severely less flexible (a big tangled mess
that almost no one can sort through...).
well, ok, this is one severe annoyance, right up there with unrestricted
creation of external dependencies (the most obvious example of this being
apps that depend on glib... where apps that depend on it will almost
invariably not work right on Windows, and I almost suspect that modular
coding techniques are mutually incompatible with use of glib, as I can't
really think of any case where I have seen both a modular design and the use
of glib, although I can't seem to locate a reason for why this would be the
case...).
but, it may also be that my belief in modularity demands that one regularly
use the minimum facilities required to complete a given task, and so if
something can be accomplished purely by standalone code, it should be done
so, and if purely within the confines of the standard library, it should be
done so.
in this case, one only resorts to things beyond this (including even other
libraries and subsystems within the same project/codebase), when it is
either not possible or practical to complete the task otherwise (and then,
these dependencies can be documented as such...). I also tend to structure
my headers and makefiles in such a way to help enforce these restrictions.
often, along major borders (such as between libraries or subsystems), it may
make sense to wrap the imported functionality, such that if one later needs
to break or redirect the dependency, it can be done from a central location
(this may also often be the only location which uses the headers assigned to
the different libraries, further reducing ones' temptation to violate the
currently enforced modularity rules).
in other cases, the API is exported much more generally, and it is assumed
that if the need arrises, this whole API may be modified and redirected (in
this case, often the main 'API' file does not directly implement much of the
functionality it exports, but instead redirects it to the particular library
or subsystem which does implement this functionality).
most often, I use different naming conventions between these. my shared APIs
most often use the 'glCamelCase' convention, where 'gl' is an API-specific
prefix (gc, dy, dyc, dyx, dyp, vf, ...), which may not necessarily relate to
the library this API is contained in, or sometimes the location of the
functionality in question.
also, it makes it much less painless when moving code between Windows and
Linux, or between x86 and x86-64, or when significant pieces of
functionality may be swapped out...
it also makes it much easier to reuse code, since I can keep using the same
code, but only need to make sure that "an implementation" of this API is
provided (often a copy-paste-edit of the prior 'api' related source files),
rather than being bound to a particular library. it also helps with being
able to replace the particular implementation of certain functionality if
needed.
for example, if I needed to replace the implementation of my GC with another
one, this much could be done without "too" much pain. actually, I have
replaced much of my core GC functionality, fairly often, over the years, but
the only real time this really time this required large-scale modification
was when I went over to the new API (the old API was much older, and a good
deal more crufty, so I had decided to drop it and force everything over to
the newer API).
however, I realize now that there are a few API details I might want to
change (mostly modifications to the naming and capitalization of some
functions), but doing so would involve modifying a good deal of code, so I
have not done so...
an "alternative" though could be to add new functions with the desired
notation, and maybe start to phase out the old forms, but I am not certain
if I should do this either. but, oh well...
note:
I tend to use strictly C-friendly APIs even when the code inside a given
module or library is written in C++, as has been done in a few cases (this
is mostly because using C++ for the API prevents C code from using it).
note, it is acceptable IMO to provide a C++ API as well, so long as all
functionality available in the C++ API is also available in the C version
(actually, typically the C++ API, if provided, exists as sugaring over the
existing C API, thus allowing the same API code to still be used if the
underlying implementation is changed). however, typically, the C API is the
only API (I neither use C++ enough, nor am enough of a "purist", to really
justify the effort in providing a "proper" OO API for everything...).
sometimes, the C++ API exists, but serves primarily to emulate functionality
which is available in my C compiler via compiler extensions.
however, this practice is reduced now, with me instead continuing to use a C
function-call based API, but silently implementing many of the API functions
as compiler intrinsics (this has become the de-facto state of my vector-math
extensions, where I am far more likely at this point to make use of the
calls than the operators, even though the operators are more convinient...
in this case with the calls reducing to intrinsics which end up generating
more or less the same code as if the operator had been used directly...).
FWIW, one advantage of BLISS was an SOP technique for ensuring a high
level of optimization. The macro facility in BLISS is orders of magnitude
more sophisticated than C's. [A couple of guys at DEC with too much time
on their hands wrote an entire Basic interpreter as a BLISS macro; another
guy solved the Towers of Hanoi problem as a compile-time macro. How they
debugged that stuff I have no idea.]. One just had to try different
constructs within the macro and look at the produced Assembly until the
compiler does the right thing. Then one always used the macro when the
situation arose.
interesting...
BTW, as a translationist you should consider why transformation engines
routinely target C or Assembly for output. B-) Clearly it would be easier
the write the engine to produce OOPL code because the mapping is more
direct and higher level abstractions are supported in the OOPL. They don't
target the OOPLs because of the inherent performance penalties for all
that convenient abstraction.
yes, this is true...
I made a similar point elsewhere...
--
Life is the only flaw in an otherwise perfect nonexistence
-- Schopenhauer
H. S. Lahman
H.lahman@xxxxxxxxxxx
software blog: http://pathfinderpeople.blogs.com/hslahman/index.html
.
- Follow-Ups:
- Re: object system...
- From: H. S. Lahman
- Re: object system...
- References:
- object system...
- From: cr88192
- Re: object system...
- From: H. S. Lahman
- Re: object system...
- From: Grizlyk
- Re: object system...
- From: H. S. Lahman
- Re: object system...
- From: Grizlyk
- Re: object system...
- From: H. S. Lahman
- Re: object system...
- From: Lee Riemenschneider
- Re: object system...
- From: H. S. Lahman
- object system...
- Prev by Date: Re: object system...
- Next by Date: Re: object system...
- Previous by thread: Re: object system...
- Next by thread: Re: object system...
- Index(es):
Relevant Pages
|