From: Software Scavenger
Subject: speed of make-instance
Date: 
Message-ID: <a6789134.0201100638.544f0743@posting.google.com>
How long does the following code take to run in
different lisp environments?  In those where it
seems to take longer than it should, what is the
reason?  Does make-instance do a lot of work
behind the scenes?  Such as what?


(defclass my-test-class () (slot-1))

(defun speedtest (n k)
  (loop repeat n sum
    (length
      (loop repeat k collect
            (make-instance 'my-test-class)))))

(compile 'speedtest)

(time (speedtest 1000 1000))

From: Duane Rettig
Subject: Re: speed of make-instance
Date: 
Message-ID: <4u1tt5isk.fsf@beta.franz.com>
··········@mailandnews.com (Software Scavenger) writes:

> How long does the following code take to run in
> different lisp environments?

I posted an addendum to Bruce Hoult's list to include a couple of
Allegro CL times.

>  In those where it
> seems to take longer than it should, what is the
> reason?  Does make-instance do a lot of work
> behind the scenes?

Yes.

>  Such as what?

See section 7.1 of the ANSI spec, but especially section 7.1.7, "Definitions
of Make-instance and Initialize-instance".  You can find it here:

http://www.franz.com/support/documentation/6.1/ansicl/subsecti/definiti.htm

The real issue is not why some implementations perform your test slowly
(that should be evident from the spec) but what can be done to perform
such calls quickly.  When the class is known and constant in the
make-instance call (as well as constant initarg names), and when there
are not many encumberances on the implementation of make-instance (e.g.
an initialize-instance after-method might be normal and optimizable, but
a shared-initialize around method might be too much), then a constructor
function can be built and used and the bulk of the allocation can be done
almost as fast as a call to vector.   The constructor or its caller must
check for any changes that would invalidate it, and would also have to
call any initialize-instance and/or shared-initialize methods that it
has agreed to handle.  This combination can make make-instance calls
extremely fast for a large class of calls to it.

-- 
Duane Rettig          Franz Inc.            http://www.franz.com/ (www)
1995 University Ave Suite 275  Berkeley, CA 94704
Phone: (510) 548-3600; FAX: (510) 548-8253   ·····@Franz.COM (internet)
From: Software Scavenger
Subject: Re: speed of make-instance
Date: 
Message-ID: <a6789134.0202020703.3611001@posting.google.com>
Duane Rettig <·····@franz.com> wrote in message news:<·············@beta.franz.com>...

> The real issue is not why some implementations perform your test slowly
> (that should be evident from the spec) but what can be done to perform
> such calls quickly.  When the class is known and constant in the
> make-instance call (as well as constant initarg names), and when there
> are not many encumberances on the implementation of make-instance (e.g.
> an initialize-instance after-method might be normal and optimizable, but
> a shared-initialize around method might be too much), then a constructor

Is this about what the compiler can do to make such calls faster, or
what the programmer can do to work around the lack of such speedups?

> function can be built and used and the bulk of the allocation can be done
> almost as fast as a call to vector.   The constructor or its caller must
> check for any changes that would invalidate it, and would also have to
> call any initialize-instance and/or shared-initialize methods that it
> has agreed to handle.  This combination can make make-instance calls
> extremely fast for a large class of calls to it.

Assuming I use a compiler that doesn't do such things, can I and/or
should I attempt to do them myself?
From: Bill Clementson
Subject: Re: speed of make-instance
Date: 
Message-ID: <wk3d1c69wa.fsf@attbi.com>
··········@mailandnews.com (Software Scavenger) writes:

> How long does the following code take to run in
> different lisp environments?  In those where it
> seems to take longer than it should, what is the
> reason?  Does make-instance do a lot of work
> behind the scenes?  Such as what?
> 
> 
> (defclass my-test-class () (slot-1))
> 
> (defun speedtest (n k)
>   (loop repeat n sum
>     (length
>       (loop repeat k collect
>             (make-instance 'my-test-class)))))
> 
> (compile 'speedtest)
> 
> (time (speedtest 1000 1000))

On a 500MHz PIII HP 8660C running Win98:
CLISP 2.27:
Real time: 6.54 sec.
Run time: 6.54 sec.
Space: 24010040. Bytes
GC: 46., GC time: 0.59 sec.

ACL6.1 (trial):
; cpu time (non-gc) 580 msec user, 0 msec system
; cpu time (gc)     2,990 msec user, 0 msec system
; cpu time (total)  3,570 msec user, 0 msec system
; real time  3,570 msec

-- 
Bill Clementson
From: Bruce Hoult
Subject: Re: speed of make-instance
Date: 
Message-ID: <bruce-0B0B7E.20344311012002@news.paradise.net.nz>
In article <····························@posting.google.com>, 
··········@mailandnews.com (Software Scavenger) wrote:

> How long does the following code take to run in
> different lisp environments? 

700 MHz Athlon, Linux.

CMUCL        : 1.93
Gwydion Dylan: 2.38

So a native code compiler with custom GC beats out a via-C compiler 
using Boehm.

The Dylan also has speed hints in the form of sealing declarations on 
the class, and one variable type declaration.  Removing the declaration 
of k adds 0.50 seconds.  Removing the sealing declarations then takes it 
to 5.2 seconds.


Dylan source is:

module: makeinstance

define class <my-test-class> (<object>)
  slot slot-1;
end;

define sealed domain make(singleton(<my-test-class>));
define sealed domain initialize(<my-test-class>);

define function speedtest(n, k :: <integer>)
  let t = 0;
  for (i from 1 to n)
    let v = #();
    for (i from 1 to k) v := pair(make(<my-test-class>), v) end;
    t := t + v.size;
  end;
  t;
end;

format-out("total = %d\n", speedtest(1000, 1000));
From: Duane Rettig
Subject: Re: speed of make-instance
Date: 
Message-ID: <4y9j55kg6.fsf@beta.franz.com>
Bruce Hoult <·····@hoult.org> writes:

> In article <····························@posting.google.com>, 
> ··········@mailandnews.com (Software Scavenger) wrote:
> 
> > How long does the following code take to run in
> > different lisp environments? 
> 
> 700 MHz Athlon, Linux.
> 
> CMUCL        : 1.93
> Gwydion Dylan: 2.38

Allegro CL: 

500 Mhz AMD K6-2, Linux:   1.29 + .23 (gc) = 1.52
533 Mhz Celeron, FreeBSD:   .57 + .32 (gc) =  .89
1.4 Ghz AMD Athlon Thunderbird, Linux:
                            .24 + .12 (gc) =  .36

> So a native code compiler with custom GC beats out a via-C compiler 
> using Boehm.

The gc may be important, but it wasn't broken out in your timings.
Also, I think that the OP was more interested in the speed of the
make-instance calls themselves, judging by the questions that were
being asked.

> The Dylan also has speed hints in the form of sealing declarations on 
> the class, and one variable type declaration.  Removing the declaration 
> of k adds 0.50 seconds.  Removing the sealing declarations then takes it 
> to 5.2 seconds.

It shouldn't be necessary to seal in this case; you have a call to
make an instance of a known class name, so the system can create a
fast constructor for it.  What if the class changes?  No problem,
the constructor notices the change, invalidates itself, and is
possibly replaced by a new one.

-- 
Duane Rettig          Franz Inc.            http://www.franz.com/ (www)
1995 University Ave Suite 275  Berkeley, CA 94704
Phone: (510) 548-3600; FAX: (510) 548-8253   ·····@Franz.COM (internet)
From: Pierre R. Mai
Subject: Re: speed of make-instance
Date: 
Message-ID: <87ita97zt9.fsf@orion.bln.pmsf.de>
Duane Rettig <·····@franz.com> writes:

> Bruce Hoult <·····@hoult.org> writes:
> 
> > In article <····························@posting.google.com>, 
> > ··········@mailandnews.com (Software Scavenger) wrote:
> > 
> > > How long does the following code take to run in
> > > different lisp environments? 
> > 
> > 700 MHz Athlon, Linux.
> > 
> > CMUCL        : 1.93
> > Gwydion Dylan: 2.38
> 
> Allegro CL: 
> 
> 500 Mhz AMD K6-2, Linux:   1.29 + .23 (gc) = 1.52
> 533 Mhz Celeron, FreeBSD:   .57 + .32 (gc) =  .89
> 1.4 Ghz AMD Athlon Thunderbird, Linux:
>                             .24 + .12 (gc) =  .36

FWIW, CMU CL defconstructor-based constructors yield a speed
improvement over the normal make-instance mechanism.  I'll have to
look into the reasons that that mechanism isn't used for make-instance
calls with known class and arguments.

CMU CL (current development binary) on AMD K6-2/550, Linux:

make-instance:               3.73 + 0.65 (gc) = 4.38
defconstructor:              1.93 + 0.69 (gc) = 2.62

Regs, Pierre.

-- 
Pierre R. Mai <····@acm.org>                    http://www.pmsf.de/pmai/
 The most likely way for the world to be destroyed, most experts agree,
 is by accident. That's where we come in; we're computer professionals.
 We cause accidents.                           -- Nathaniel Borenstein
From: Julian St.
Subject: Re: speed of make-instance
Date: 
Message-ID: <5relkwdf60.fsf@blitz.comp.com>
"Pierre R. Mai" <····@acm.org> writes:


[...]

> > > 700 MHz Athlon, Linux.
> > > 
> > > CMUCL        : 1.93
> > > Gwydion Dylan: 2.38
> > 
> > Allegro CL: 
> > 
> > 500 Mhz AMD K6-2, Linux:   1.29 + .23 (gc) = 1.52
> > 533 Mhz Celeron, FreeBSD:   .57 + .32 (gc) =  .89
> > 1.4 Ghz AMD Athlon Thunderbird, Linux:
> >                             .24 + .12 (gc) =  .36
> 
> FWIW, CMU CL defconstructor-based constructors yield a speed
> improvement over the normal make-instance mechanism.  I'll have to
> look into the reasons that that mechanism isn't used for make-instance
> calls with known class and arguments.
> 
> CMU CL (current development binary) on AMD K6-2/550, Linux:
> 
> make-instance:               3.73 + 0.65 (gc) = 4.38
> defconstructor:              1.93 + 0.69 (gc) = 2.62

GNU CLISP 2.27 (released 2001-07-17)

Real time: 3.687614 sec.
Run time: 3.329834 sec.
Space: 24000000 Bytes
GC: 46, GC time: 0.276328 sec.

on Athlon 600 Mhz running FreeBSD 4.4

Quite impressive for quasi-interpreted.

Regards,
Julian St.
From: Bruce Hoult
Subject: Re: speed of make-instance
Date: 
Message-ID: <bruce-B781AA.23525411012002@news.paradise.net.nz>
In article <·············@beta.franz.com>, Duane Rettig 
<·····@franz.com> wrote:

> > The Dylan also has speed hints in the form of sealing declarations on 
> > the class, and one variable type declaration.  Removing the declaration 
> > of k adds 0.50 seconds.  Removing the sealing declarations then takes 
> > it to 5.2 seconds.
> 
> It shouldn't be necessary to seal in this case; you have a call to
> make an instance of a known class name, so the system can create a
> fast constructor for it.

If you don't seal make() on the class then the compiler has to allow for 
the possibility of another library (or some runtime code) defining 
make() on that class and doing something different -- rearranging 
arguments, returning a different class etc.  Which means that it has to 
do a full Generic Function call.

The same goes for initialize().

We're not providing our own implementations for these, so we have to 
tell the compiler that no one else is allowed to either so that it knows 
to use the default one on <object>.

-- Bruce
From: Thomas F. Burdick
Subject: Re: speed of make-instance
Date: 
Message-ID: <xcv7kqowuud.fsf@apocalypse.OCF.Berkeley.EDU>
Bruce Hoult <·····@hoult.org> writes:

> In article <·············@beta.franz.com>, Duane Rettig 
> <·····@franz.com> wrote:
> 
> > > The Dylan also has speed hints in the form of sealing declarations on 
> > > the class, and one variable type declaration.  Removing the declaration 
> > > of k adds 0.50 seconds.  Removing the sealing declarations then takes 
> > > it to 5.2 seconds.
> > 
> > It shouldn't be necessary to seal in this case; you have a call to
> > make an instance of a known class name, so the system can create a
> > fast constructor for it.
> 
> If you don't seal make() on the class then the compiler has to allow for 
> the possibility of another library (or some runtime code) defining 
> make() on that class and doing something different -- rearranging 
> arguments, returning a different class etc.

Yep, keeping things generic is great for reducing your chances of
being bitten in the ass later.

> Which means that it has to do a full Generic Function call.

No no no!  Did I mention "no!" ?  Seriously though, the system needs
to provide the *illusion* of doing this.  In the case of the Lisp
system I'm developing, the equivalent of this example would be done
with a simple construction call, not a full generic call.  If I were
to later load code into the system that required the full dynamic
mechanism, the simple function would be overwritten with one that
calls the full GF.  Something as radical as sealing shouldn't be
necessary in a case like this.

-- 
           /|_     .-----------------------.                        
         ,'  .\  / | No to Imperialist war |                        
     ,--'    _,'   | Wage class war!       |                        
    /       /      `-----------------------'                        
   (   -.  |                               
   |     ) |                               
  (`-.  '--.)                              
   `. )----'                               
From: Bruce Hoult
Subject: Re: speed of make-instance
Date: 
Message-ID: <bruce-795B30.11542012012002@news.paradise.net.nz>
In article <···············@apocalypse.OCF.Berkeley.EDU>, 
···@apocalypse.OCF.Berkeley.EDU (Thomas F. Burdick) wrote:

> > If you don't seal make() on the class then the compiler has to allow 
> > for 
> > the possibility of another library (or some runtime code) defining 
> > make() on that class and doing something different -- rearranging 
> > arguments, returning a different class etc.
> 
> Yep, keeping things generic is great for reducing your chances of
> being bitten in the ass later.
> 
> > Which means that it has to do a full Generic Function call.
> 
> No no no!  Did I mention "no!" ?  Seriously though, the system needs
> to provide the *illusion* of doing this.

I'd agree with that, except that as a matter of definitions I'd say that 
if the semantics are that of a generic call then that's what it is.  
Clever implementation techniques might make generic calls cheaper, on 
average, but they are still generic calls.


> In the case of the Lisp
> system I'm developing, the equivalent of this example would be done
> with a simple construction call, not a full generic call.  If I were
> to later load code into the system that required the full dynamic
> mechanism, the simple function would be overwritten with one that
> calls the full GF.

Then your GF call mechanism is more sophisticated than that currently in 
GD, which is quite frankly pretty crude and slow.  One of us (Erik Kidd) 
has been doing a thesis on better GF dispatch algorithms applicable to 
Dylan and the result sof that will hopefully get rolled in sometime.  
There's lots of good ideas coming out of the Cecil project, too.

Your approach is essentially a form of caching?  Is it applicable to GFs 
in general, or only to make-instance?


> Something as radical as sealing shouldn't be
> necessary in a case like this.

In theoretical terms, sealing will always allow slightly better 
performance in the limit because it needs at least one less indirection.  
In fact, it allows inlining of the contents of the method being called.

In practical terms, sealing (and other declarations) let you explicitly 
tell the compiler something that it might not (yet) be clever enough to 
figure out by itself.  It also communicates useful information to a 
maintainance programmer.

-- Bruce
From: Thomas F. Burdick
Subject: Re: speed of make-instance
Date: 
Message-ID: <xcvsn9cuzlw.fsf@apocalypse.OCF.Berkeley.EDU>
Bruce Hoult <·····@hoult.org> writes:

> In article <···············@apocalypse.OCF.Berkeley.EDU>, 
> ···@apocalypse.OCF.Berkeley.EDU (Thomas F. Burdick) wrote:
> 
> > > If you don't seal make() on the class then the compiler has to allow 
> > > for 
> > > the possibility of another library (or some runtime code) defining 
> > > make() on that class and doing something different -- rearranging 
> > > arguments, returning a different class etc.
> > 
> > Yep, keeping things generic is great for reducing your chances of
> > being bitten in the ass later.
> > 
> > > Which means that it has to do a full Generic Function call.
> > 
> > No no no!  Did I mention "no!" ?  Seriously though, the system needs
> > to provide the *illusion* of doing this.
> 
> I'd agree with that, except that as a matter of definitions I'd say that 
> if the semantics are that of a generic call then that's what it is.  
> Clever implementation techniques might make generic calls cheaper, on 
> average, but they are still generic calls.

Hmm, it's not taking power from the user.  But it's sort of like when
type inference allows the compiler to notice that (* x 8) can be
transformed into a shift.  The compiler was able to determine that the
user was only using a subset of the functionality, and so didn't solve
the full problem, but instead solved the special case.  In the case of
make-instance, it literally gets transformed into a call to a normal,
non-generic function that can't be inlined.  No indirection here.
During the (admittedly expensive) process of redefinition, the
specialized function is overwritten with a trampoline to the GF.  A
more sophisticated system could sometimes determine that the full GF
wasn't even needed here, and make the trampoline point to an
implementation of a different special case, when applicable.

The semantics aren't of a generic call, but such semantics can be
recovered and used when needed.

> > In the case of the Lisp
> > system I'm developing, the equivalent of this example would be done
> > with a simple construction call, not a full generic call.  If I were
> > to later load code into the system that required the full dynamic
> > mechanism, the simple function would be overwritten with one that
> > calls the full GF.
> 
> Then your GF call mechanism is more sophisticated than that currently in 
> GD, which is quite frankly pretty crude and slow.  One of us (Erik Kidd) 
> has been doing a thesis on better GF dispatch algorithms applicable to 
> Dylan and the result sof that will hopefully get rolled in sometime.  
> There's lots of good ideas coming out of the Cecil project, too.
> 
> Your approach is essentially a form of caching?  Is it applicable to GFs 
> in general, or only to make-instance?

I think this particular technique is only used here and there, mostly
for make-instance.  It's related to caching, in that it allows a call
to be turned into a caching approach.  I've got a whole toolbox full
of optimizations that I'm hoping to fit in :)

> > Something as radical as sealing shouldn't be
> > necessary in a case like this.
> 
> In theoretical terms, sealing will always allow slightly better 
> performance in the limit because it needs at least one less indirection.  
> In fact, it allows inlining of the contents of the method being called.

Of course, using the later-overwrite-with-a-trampoline technique, it
doesn't buy you anything when the method is too big to inline.  It
just loses you long-run flexibility.

> In practical terms, sealing (and other declarations) let you explicitly 
> tell the compiler something that it might not (yet) be clever enough to 
> figure out by itself.  It also communicates useful information to a 
> maintainance programmer.

Right, but I think you missed my point.  Sealing is probably not
correct here.  Using it to get a performance increase is dangerous,
because you're reducing your ability to change things later.  In a
sealed situation, you don't need to worry about GF calls, nor about
ever using them.  In a case when the compiler can see that you're not
using GF calls, it only needs to ensure that it can later turn these
simple function calls into GF calls.  Declarations should be very
infrequent, and used *only* when the programmer can prove they're
true.  In the case of sealing, I'd use that to mean that classes
should only be sealed when it can be proved that if they're redefined,
their all consumers will have needed to be redefined as well[*].  This
is something the compiler couldn't (reasonably) figure out for itself,
so declarations are needed.  I think of the (declare ...) form as the
Lisp equivalent of the confession booth -- it's a time for complete
honesty and not fudging things to make yourself look better than you
are, even on accident.  Of course, this is coming from someone working
on making his compiler insert dynamic-extent declarations so he
doesn't need to lie to it on accident :)

[*] This is of course for the 90-95% of the time when we're not
talking about performance-critical inner loops you found with your
profiler.  In that case, most rules are inverted as an engineering
tradeoff.  This is in the same realm as hand-transformed assembler,
GOTO, etc.

-- 
           /|_     .-----------------------.                        
         ,'  .\  / | No to Imperialist war |                        
     ,--'    _,'   | Wage class war!       |                        
    /       /      `-----------------------'                        
   (   -.  |                               
   |     ) |                               
  (`-.  '--.)                              
   `. )----'                               
From: Pierre R. Mai
Subject: Re: speed of make-instance
Date: 
Message-ID: <87elkx7zem.fsf@orion.bln.pmsf.de>
Bruce Hoult <·····@hoult.org> writes:

> In article <·············@beta.franz.com>, Duane Rettig 
> <·····@franz.com> wrote:
> 
> > > The Dylan also has speed hints in the form of sealing declarations on 
> > > the class, and one variable type declaration.  Removing the declaration 
> > > of k adds 0.50 seconds.  Removing the sealing declarations then takes 
> > > it to 5.2 seconds.
> > 
> > It shouldn't be necessary to seal in this case; you have a call to
> > make an instance of a known class name, so the system can create a
> > fast constructor for it.
> 
> If you don't seal make() on the class then the compiler has to allow for 
> the possibility of another library (or some runtime code) defining 
> make() on that class and doing something different -- rearranging 
> arguments, returning a different class etc.  Which means that it has to 
> do a full Generic Function call.

I don't think so.  It only means that the implementation has to keep track
whether something of the sort has happened (e.g. using a simple flag in the
class object).  The fast-case constructor then simply checks the flag,
and only calls out to a more general mechanism if something "evil" has
happened.

Sealing (and prohibiting class redefinition, etc.) will likely allow
you to elide even this simple check, but on the whole that check is
probably quite cheap compared to the time it takes to initialize an
instance.  You likely already have the address of the class-object in
a register at some time, so checking a fixed flag is likely just a
single memory reference, with a high possibility of a cache hit (since
the flag probably shares a cache-line with other accessed slots of the
class-object).

Regs, Pierre.

-- 
Pierre R. Mai <····@acm.org>                    http://www.pmsf.de/pmai/
 The most likely way for the world to be destroyed, most experts agree,
 is by accident. That's where we come in; we're computer professionals.
 We cause accidents.                           -- Nathaniel Borenstein
From: Bruce Hoult
Subject: Re: speed of make-instance
Date: 
Message-ID: <bruce-1BACBD.14215312012002@news.paradise.net.nz>
In article <··············@orion.bln.pmsf.de>, "Pierre R. Mai" 
<····@acm.org> wrote:

> Bruce Hoult <·····@hoult.org> writes:
> 
> > In article <·············@beta.franz.com>, Duane Rettig 
> > <·····@franz.com> wrote:
> > 
> > > > The Dylan also has speed hints in the form of sealing declarations 
> > > > on 
> > > > the class, and one variable type declaration.  Removing the 
> > > > declaration 
> > > > of k adds 0.50 seconds.  Removing the sealing declarations then 
> > > > takes 
> > > > it to 5.2 seconds.
> > > 
> > > It shouldn't be necessary to seal in this case; you have a call to
> > > make an instance of a known class name, so the system can create a
> > > fast constructor for it.
> > 
> > If you don't seal make() on the class then the compiler has to allow 
> > for 
> > the possibility of another library (or some runtime code) defining 
> > make() on that class and doing something different -- rearranging 
> > arguments, returning a different class etc.  Which means that it has to 
> > do a full Generic Function call.
> 
> I don't think so.  It only means that the implementation has to keep 
> track
> whether something of the sort has happened (e.g. using a simple flag in 
> the
> class object).  The fast-case constructor then simply checks the flag,
> and only calls out to a more general mechanism if something "evil" has
> happened.
> 
> Sealing (and prohibiting class redefinition, etc.) will likely allow
> you to elide even this simple check, but on the whole that check is
> probably quite cheap compared to the time it takes to initialize an
> instance.

That depends on how complex the object is.

In many cases you're quite correct, but for a lot of very simple objects 
the overhead could be prohibitive.

For example, in this years' ICFP contest we had a class representing the 
formatting state at a particular point of a marked-up text:

define functional class <attribute> (<object>)
  slot bold;
  slot emphasis;
  slot italic;
  slot strong;
  slot typewriter;
  slot underline;
  slot font-size;
  slot color;
end class <attribute>;

The first five attrbutes are boolean, the others are small integers (4 
levels of underline, 10 font sizes, 8 colors).

Initially we defined the class as above so that we could start working 
on other parts of the program.  Later on I came back and made it more 
efficient since creating and manipulating these things was a real 
bottleneck in the program.  I made the class hold a single integer slot, 
with the others being virtual slots whose getters did bit-banging on te 
integer value to extract the desired value.  Then by using sealing (and 
the "functional" declaration above) I was able to make it so that 
objects of this class could be stack-allocated (or even in registers) 
and passed around by value instead of by reference while retaining the 
SAME SEMANTICS and in fact the same syntax for the rest of the program 
that used this class.

So, in the end, somewhere in the program you might write...

  let a = b.set-italic;

... which, via set-italic being inlined, would expand to ...

  let a = make(<attribute>, value: logior(b.value, 4));

... which, via make() and initialize() being known and inlined and the 
class being stack-allocated translates to the following C...

  int a = b | 4;


Maybe ending up with a runtime test to see if assumptions had been 
invalidated wouldn't be very expensive, but I think it *would* in this 
case.  Even more than make-instance/make, I don't see how you could 
cheaply put in a test (and recovery) for the possibility that things had 
changed so much that you could no longer stack-alloocate the class.

-- Bruce
From: Pierre R. Mai
Subject: Re: speed of make-instance
Date: 
Message-ID: <87k7un606v.fsf@orion.bln.pmsf.de>
Bruce Hoult <·····@hoult.org> writes:

> > Sealing (and prohibiting class redefinition, etc.) will likely allow
> > you to elide even this simple check, but on the whole that check is
> > probably quite cheap compared to the time it takes to initialize an
> > instance.
> 
> That depends on how complex the object is.
>
> In many cases you're quite correct, but for a lot of very simple objects 
> the overhead could be prohibitive.

Agreed, it could.  Luckily it isn't an either/or situation, it is a "I
want both" situation.  I'm of the conviction that resorting to
declarations is always only the last resort.  An implementation should
IMHO strive to provide the best possibly levels of performance,
without restricting declarations, especially if those declarations
have pervasive effects.  It can then offer restrictive declarations
for those cases which can't be efficiently handled without that
knowledge.

If OTOH performance without declarations is generally not very good,
we get into a situation where the programmer is more or less forced to
declare lots of stuff, in order to get acceptable levels of
performance.  One of the strengths of dynamic languages is that they
don't require this, allowing you to keep your code as generic as
possible, yet still achieving acceptable levels of performance...

Regs, Pierre.

-- 
Pierre R. Mai <····@acm.org>                    http://www.pmsf.de/pmai/
 The most likely way for the world to be destroyed, most experts agree,
 is by accident. That's where we come in; we're computer professionals.
 We cause accidents.                           -- Nathaniel Borenstein
From: Bruce Hoult
Subject: Re: speed of make-instance
Date: 
Message-ID: <bruce-619B47.10441013012002@news.paradise.net.nz>
In article <··············@orion.bln.pmsf.de>, "Pierre R. Mai" 
<····@acm.org> wrote:

> Bruce Hoult <·····@hoult.org> writes:
> 
> > > Sealing (and prohibiting class redefinition, etc.) will likely allow
> > > you to elide even this simple check, but on the whole that check is
> > > probably quite cheap compared to the time it takes to initialize an
> > > instance.
> > 
> > That depends on how complex the object is.
> >
> > In many cases you're quite correct, but for a lot of very simple 
> > objects 
> > the overhead could be prohibitive.
> 
> Agreed, it could.  Luckily it isn't an either/or situation, it is a "I
> want both" situation.  I'm of the conviction that resorting to
> declarations is always only the last resort.  An implementation should
> IMHO strive to provide the best possibly levels of performance,
> without restricting declarations, especially if those declarations
> have pervasive effects.  It can then offer restrictive declarations
> for those cases which can't be efficiently handled without that
> knowledge.

Agreed.


> If OTOH performance without declarations is generally not very good,
> we get into a situation where the programmer is more or less forced to
> declare lots of stuff, in order to get acceptable levels of
> performance.

Only after profiling (or divine revelation, or examining the entrails 
... er ... generated code), and then not in very much of the code.

-- Bruce
From: Tim Bradshaw
Subject: Re: speed of make-instance
Date: 
Message-ID: <ey3vge852jx.fsf@cley.com>
* Bruce Hoult wrote:

> If you don't seal make() on the class then the compiler has to allow for 
> the possibility of another library (or some runtime code) defining 
> make() on that class and doing something different -- rearranging 
> arguments, returning a different class etc.  Which means that it has to 
> do a full Generic Function call.

No, it has to check a bit somewhere to see if the fast method (not in
the CLOS sense) is valid, and punt in the bad case.  Given a processor
that can do branch prediction and speculative execution this probably
has no overhead at all unless there's a lot of other things that some
of the execution units could be doing.  The bad case might be slower
than just compiling code that assumed the bad case (full call) in the
first place, but since you're kind of assuming the good case anyway
this is probably not too serious an issue.

The Symbolics compiler on Ivory was doing this kind of thing for
accessors 10 years ago (the bad case for them was non-primary methods
which caused a punt to slow code unless you declared the accessor
NOTINLINE). It probably did MAKE-INSTANCE too. I'm pretty sure that
other serious implementations do similar stuff - modern CPUs ought to
be God's gift to this kind of thing - you no longer need special
hardware to do parallel checking of things.

--tim
From: Bruce Hoult
Subject: Re: speed of make-instance
Date: 
Message-ID: <bruce-BB0BE9.13475712012002@news.paradise.net.nz>
In article <···············@cley.com>, Tim Bradshaw <···@cley.com> 
wrote:

> * Bruce Hoult wrote:
> 
> > If you don't seal make() on the class then the compiler has to allow 
> > for 
> > the possibility of another library (or some runtime code) defining 
> > make() on that class and doing something different -- rearranging 
> > arguments, returning a different class etc.  Which means that it has to 
> > do a full Generic Function call.
> 
> No, it has to check a bit somewhere to see if the fast method (not in
> the CLOS sense) is valid, and punt in the bad case.  Given a processor
> that can do branch prediction and speculative execution this probably
> has no overhead at all unless there's a lot of other things that some
> of the execution units could be doing.

This is true of many types of checking in modern processors.  Array 
bounds checks, nill pointer checks...

They're not *free* though, because of the additional code bulk which 
results in the instruction cache holding less useful code.

-- Bruce