Re: object system...




"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 of
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.

Do you have sources for this information? Your numbers seem high to me.
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


.



Relevant Pages

  • Re: Quick question about streams...?
    ... For loops and structures are the C language. ... The API is a bunch of functions. ... I don't know what "runs on assembler" means. ... A C compiler usually starts ...
    (microsoft.public.vc.language)
  • Re: C Stack Corruption?
    ... and it seems that I'm getting some stack ... >corruption after any API call. ... It accepts a call from one compiler module, ... >values after any IBM API call. ...
    (comp.lang.c)
  • Re: VS2005 final delayed for september
    ... Besides, Borland isn't just relying on the compiler, but on the whole ... The dependency towards an MS OS is nowhere as tight as ... VS is the primary consumer of .Net, ... of OS API are legions in thousandths of independant companies. ...
    (borland.public.delphi.non-technical)
  • Re: pygame and python 2.5
    ... Ben> systems that require you to recompile all the extensions when you ... You'd then silently get errors if the API ... the language's compiler agree on what you get when you dereference ... is not to make assumptions about the structure of complex types across ...
    (comp.lang.python)
  • Re: Multi-threaded C++ programs: wrought with peril?
    ... Another example (which doesn't require the optimizer to reorder OS API calls): ... if the compiler could prove that lock and unlock don't access count. ... The faith is really about the fact that you have to "assume" that the compiler isn't smart/stupid enough to see that lock and unlock don't affect count, and thus reorder things. ... Fortunately, the compiler is generally unable to do any hoisting of potentially thread shared variables at all through OS API calls, due to the fact it can't see into the API calls and therefore doesn't know whether they might access those variables, or whether the API calls contain any observable behaviour. ...
    (microsoft.public.win32.programmer.kernel)