Jelle Zijlstra added the comment:

I benchmarked some common namedtuple operations with the following script:

#!/bin/bash
echo 'namedtuple creation'
./python -m timeit -s 'from collections import namedtuple' 'x = namedtuple("x", 
["a", "b", "c"])'

echo 'namedtuple instantiation'
./python -m timeit -s 'from collections import namedtuple; x = namedtuple("x", 
["a", "b", "c"])' 'x(1, 2, 3)'

echo 'namedtuple attribute access'
./python -m timeit -s 'from collections import namedtuple; x = namedtuple("x", 
["a", "b", "c"]); i = x(1, 2, 3)' 'i.a'

echo 'namedtuple _make'
./python -m timeit -s 'from collections import namedtuple; x = namedtuple("x", 
["a", "b", "c"])' 'x._make((1, 2, 3))'


--------------------------------------
With my patch as it stands now I get:

$ ./ntbenchmark.sh 
namedtuple creation
2000 loops, best of 5: 101 usec per loop
namedtuple instantiation
500000 loops, best of 5: 477 nsec per loop
namedtuple attribute access
5000000 loops, best of 5: 59.9 nsec per loop
namedtuple _make
500000 loops, best of 5: 430 nsec per loop


--------------------------------------
With unpatched CPython master I get:

$ ./ntbenchmark.sh 
namedtuple creation
500 loops, best of 5: 409 usec per loop
namedtuple instantiation
500000 loops, best of 5: 476 nsec per loop
namedtuple attribute access
5000000 loops, best of 5: 60 nsec per loop
namedtuple _make
1000000 loops, best of 5: 389 nsec per loop


So creating a class is about 4x faster (similar to the benchmarks various other 
people have run) and calling _make() is 10% slower. That's probably because of 
the line "if len(result) != cls._num_fields:" in my implementation, which would 
have been something like "if len(result) != 3" in the exec-based implementation.

I also cProfiled class creation with my patch. These are results for creating 
10000 3-element namedtuple classes:

         390005 function calls in 2.793 seconds

   Ordered by: cumulative time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
    10000    0.053    0.000    2.826    0.000 
<ipython-input-5-c37fa4922f0a>:1(make_nt)
    10000    1.099    0.000    2.773    0.000 
/home/jelle/qython/cpython/Lib/collections/__init__.py:380(namedtuple)
    10000    0.948    0.000    0.981    0.000 {built-in method builtins.exec}
   100000    0.316    0.000    0.316    0.000 {method 'format' of 'str' objects}
    10000    0.069    0.000    0.220    0.000 {method 'join' of 'str' objects}
    40000    0.071    0.000    0.152    0.000 
/home/jelle/qython/cpython/Lib/collections/__init__.py:439(<genexpr>)
    10000    0.044    0.000    0.044    0.000 {built-in method builtins.repr}
    30000    0.033    0.000    0.033    0.000 {method 'startswith' of 'str' 
objects}
    40000    0.031    0.000    0.031    0.000 {method 'isidentifier' of 'str' 
objects}
    40000    0.025    0.000    0.025    0.000 {method '__contains__' of 
'frozenset' objects}
    10000    0.022    0.000    0.022    0.000 {method 'replace' of 'str' 
objects}
    10000    0.022    0.000    0.022    0.000 {built-in method sys._getframe}
    30000    0.020    0.000    0.020    0.000 {method 'add' of 'set' objects}
    20000    0.018    0.000    0.018    0.000 {built-in method builtins.len}
    10000    0.013    0.000    0.013    0.000 {built-in method 
builtins.isinstance}
    10000    0.009    0.000    0.009    0.000 {method 'get' of 'dict' objects}

So about 35% of time is still spent in the exec() call to create __new__. 
Another 10% is in .format() calls, so using f-strings instead of .format() 
might also be worth it.

----------

_______________________________________
Python tracker <rep...@bugs.python.org>
<http://bugs.python.org/issue28638>
_______________________________________
_______________________________________________
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com

Reply via email to