dynamism

Steven Shaw steven_shaw at adc.com
Thu Sep 12 02:33:36 CEST 2002


Duncan Booth <duncan at NOSPAMrcp.co.uk> wrote in message news:<Xns9286606D44C35duncanrcpcouk at 127.0.0.1>...
> Duncan Booth <duncan at NOSPAMrcp.co.uk> wrote in 
> news:Xns92865CE1C916Aduncanrcpcouk at 127.0.0.1:
> > The code below gives these timings on my machine:
> > 3.84s: 1000000 x New()
> > 3.89s: 1000000 x New2()
> > 2.39s: 1000000 x Old()
> > 5.19s: 10000 x attributes(<class '__main__.New'>)
> > 5.81s: 10000 x attributes(<class '__main__.New2'>)
> > 4.17s: 10000 x attributes(__main__.Old)
> 
> Sorry about the bad form of following up to my own post, but I thought I 
> had better add that the above timings are on Python 2.2.1 and I forgot to 
> specify -O.
> 
> The optimised times for 2.2.1 are:
> 3.77s: 1000000 x New()
> 3.81s: 1000000 x New2()
> 2.33s: 1000000 x Old()
> 4.94s: 10000 x attributes(<class '__main__.New'>)
> 5.56s: 10000 x attributes(<class '__main__.New2'>)
> 3.92s: 10000 x attributes(__main__.Old)
> 
>  Using the latest CVS build I get rather different timings:
> 
> 0.94s: 1000000 x New()
> 1.05s: 1000000 x New2()
> 1.39s: 1000000 x Old()
> 3.52s: 10000 x attributes(<class '__main__.New'>)
> 4.36s: 10000 x attributes(<class '__main__.New2'>)
> 3.05s: 10000 x attributes(__main__.Old)
> 
> So it appears that creating object-derived classes is now faster than 
> creating old-style classes, although both tests are much faster than in 
> 2.2. Setting attributes is still somewhat slower. Oh, and the -O flag on 
> the CVS build has no measurable effect.

Hi Duncan,

Thanks alot for your time on this. I hadn't read about the __slots__
mechanism before. I feel that there must be something wrong, though,
because the Old object layout is winning! Maybe the implementation is
finding the index of the variable in the __slots__ array and then
using returning __values__[index] in order to implement the attribute
access. I made up the "__values__" array - there must be a values
array somewhere. Array-based object layout amounts to the same thing
as record-based-layout in Python because all primitives are the same
size (right?). There needs to be a way for Python to remember the
indexes... Hmmm. I think your test code doesn't allow the compiler to
remember the indexes since it creates an instance anonymously (using
klass). I tried the code I "attached" at the end of the post:

$ python f.py
2.2.1 (#4, Aug 21 2002, 14:22:21) 
[GCC 3.2]
0.14s: 10000 x New()
0.12s: 10000 x New2()
0.09s: 10000 x Old()
13.03s: 10000 x attributes(<class '__main__.New'>)
14.04s: 10000 x attributes(<class '__main__.New2'>)
8.97s: 10000 x attributes(__main__.Old)
13.13s: 10000 x attributesNew()
14.12s: 10000 x attributesNew2()
8.32s: 10000 x attributesOld()

$ python -O f.py
2.2.1 (#4, Aug 21 2002, 14:22:21) 
[GCC 3.2]
0.14s: 10000 x New()
0.11s: 10000 x New2()
0.10s: 10000 x Old()
12.18s: 10000 x attributes(<class '__main__.New'>)
12.66s: 10000 x attributes(<class '__main__.New2'>)
7.94s: 10000 x attributes(__main__.Old)
12.26s: 10000 x attributesNew()
12.78s: 10000 x attributesNew2()
7.99s: 10000 x attributesOld()

It made no difference. I checked the bytecode and it is the same for
accessing a slot as for accessing a normal attribute so optimisations
aren't currently possible at that level...

BTW your machine seems very fast. What is it?

Cheers, Steve.

---------------
class Old: pass

class New(object):
    __slots__ = ['a', 'b', 'c', 'd']

# New-style, but not using slots.
class New2(object): pass

def timeit(repeat, fn, *args, **kw):
    import time
    start = time.time()
    for i in xrange(repeat):
        fn(*args, **kw)
    end = time.time()
    # Format arguments for printout
    argstring = ', '.join(
        [str(a) for a in args] +
        [ '%s=%s' % (k, v) for k, v in kw.items()])
    if len(argstring) > 60:
        argstring = argstring[:57] + '...'
    print "%.2fs: %d x %s(%s)" % \
          ((end-start), repeat, fn.__name__, argstring)

def attributes(klass):
    inst = klass()
    inst.a = 1
    for i in xrange(100):
        inst.b = inst.a+1
        inst.c = inst.a+2
        inst.d = inst.a+4
        inst.a += 5

def attributesNew():
    inst = New()
    inst.a = 1
    for i in xrange(100):
        inst.b = inst.a+1
        inst.c = inst.a+2
        inst.d = inst.a+4
        inst.a += 5

def attributesNew2():
    inst = New2()
    inst.a = 1
    for i in xrange(100):
        inst.b = inst.a+1
        inst.c = inst.a+2
        inst.d = inst.a+4
        inst.a += 5

def attributesOld():
    inst = Old()
    inst.a = 1
    for i in xrange(100):
        inst.b = inst.a+1
        inst.c = inst.a+2
        inst.d = inst.a+4
        inst.a += 5

def main():
    import sys
    print sys.version
    timeit(10000, New)
    timeit(10000, New2)
    timeit(10000, Old)
    timeit(10000, attributes, New)
    timeit(10000, attributes, New2)
    timeit(10000, attributes, Old)
    timeit(10000, attributesNew)
    timeit(10000, attributesNew2)
    timeit(10000, attributesOld)

if __name__ == "__main__": main()



More information about the Python-list mailing list