Python - vytvořit seznam s počáteční kapacitou

hlasů
145

Kód takhle často stává:

l = []
while foo:
    #baz
    l.append(bar)
    #qux

To je opravdu pomalé, pokud se chystáte připojit tisíce prvků do svého seznamu, protože seznam bude muset být neustále upravována tak, aby odpovídala nové prvky.

V Java, můžete vytvořit ArrayList s počáteční kapacitou. Pokud máte nějakou představu, jak velká je vaše seznam bude, to bude mnohem efektivnější.

Chápu, že kód, jako to často může být znovu zapracovány do seznamu chápání. V případě, že pro / while je velice komplikovaná, i když je to neproveditelné. Existuje nějaký ekvivalent pro nás Python programátory?

Položena 22/11/2008 v 21:56
zdroj uživatelem
V jiných jazycích...                            


9 odpovědí

hlasů
109

def doAppend( size=10000 ):
    result = []
    for i in range(size):
        message= "some unique object %d" % ( i, )
        result.append(message)
    return result

def doAllocate( size=10000 ):
    result=size*[None]
    for i in range(size):
        message= "some unique object %d" % ( i, )
        result[i]= message
    return result

Výsledky . (hodnotit každou funkci, 144-krát a průměr trvání)

simple append 0.0102
pre-allocate  0.0098

Závěr . To sotva záleží.

Předčasné optimalizace je kořenem všeho zla.

Odpovězeno 22/11/2008 v 23:02
zdroj uživatelem

hlasů
60

Seznamy Python nemají žádné zabudované v pre-alokace. Pokud opravdu potřebujete, aby se seznam, a je třeba se vyhnout režii připojením (a vy byste měli ověřit, že ano), můžete to udělat:

l = [None] * 1000 # Make a list of 1000 None's
for i in xrange(1000):
    # baz
    l[i] = bar
    # qux

Možná byste mohl vyhnout seznamu pomocí generátoru místo:

def my_things():
    while foo:
        #baz
        yield bar
        #qux

for thing in my_things():
    # do something with thing

Tímto způsobem, je seznam ne každý uloženy všechny v paměti vůbec, jen generovány podle potřeby.

Odpovězeno 22/11/2008 v 22:07
zdroj uživatelem

hlasů
34

Krátká verze: Použití

pre_allocated_list = [None] * size

pre-přidělit seznam (to je, aby bylo možné reagovat na ‚velikost‘ prvky seznamu místo postupně formování seznamu připojením). Tato operace je velmi rychlý, a to i na velkých seznamů. Přidělování nových objektů, které budou později přiděleny do seznamu prvků bude trvat mnohem déle a bude překážkou ve svém programu, výkon-moudrý.

Dlouhá verze:

Myslím, že čas inicializace by měla být vzata v úvahu. Vzhledem k tomu, v pythonu všechno je odkaz, nezáleží na tom, zda jste nastavili každý prvek na Žádný nebo nějakého řetězce - buď způsobem, je to pouze odkaz. I když to bude trvat déle, pokud chcete vytvořit nový objekt pro každý prvek odkazovat.

Pro Python 3.2:

import time
import copy

def print_timing (func):
  def wrapper (*arg):
    t1 = time.time ()
    res = func (*arg)
    t2 = time.time ()
    print ("{} took {} ms".format (func.__name__, (t2 - t1) * 1000.0))
    return res

  return wrapper

@print_timing
def prealloc_array (size, init = None, cp = True, cpmethod=copy.deepcopy, cpargs=(), use_num = False):
  result = [None] * size
  if init is not None:
    if cp:
      for i in range (size):
          result[i] = init
    else:
      if use_num:
        for i in range (size):
            result[i] = cpmethod (i)
      else:
        for i in range (size):
            result[i] = cpmethod (cpargs)
  return result

@print_timing
def prealloc_array_by_appending (size):
  result = []
  for i in range (size):
    result.append (None)
  return result

@print_timing
def prealloc_array_by_extending (size):
  result = []
  none_list = [None]
  for i in range (size):
    result.extend (none_list)
  return result

def main ():
  n = 1000000
  x = prealloc_array_by_appending(n)
  y = prealloc_array_by_extending(n)
  a = prealloc_array(n, None)
  b = prealloc_array(n, "content", True)
  c = prealloc_array(n, "content", False, "some object {}".format, ("blah"), False)
  d = prealloc_array(n, "content", False, "some object {}".format, None, True)
  e = prealloc_array(n, "content", False, copy.deepcopy, "a", False)
  f = prealloc_array(n, "content", False, copy.deepcopy, (), False)
  g = prealloc_array(n, "content", False, copy.deepcopy, [], False)

  print ("x[5] = {}".format (x[5]))
  print ("y[5] = {}".format (y[5]))
  print ("a[5] = {}".format (a[5]))
  print ("b[5] = {}".format (b[5]))
  print ("c[5] = {}".format (c[5]))
  print ("d[5] = {}".format (d[5]))
  print ("e[5] = {}".format (e[5]))
  print ("f[5] = {}".format (f[5]))
  print ("g[5] = {}".format (g[5]))

if __name__ == '__main__':
  main()

vyhodnocení:

prealloc_array_by_appending took 118.00003051757812 ms
prealloc_array_by_extending took 102.99992561340332 ms
prealloc_array took 3.000020980834961 ms
prealloc_array took 49.00002479553223 ms
prealloc_array took 316.9999122619629 ms
prealloc_array took 473.00004959106445 ms
prealloc_array took 1677.9999732971191 ms
prealloc_array took 2729.999780654907 ms
prealloc_array took 3001.999855041504 ms
x[5] = None
y[5] = None
a[5] = None
b[5] = content
c[5] = some object blah
d[5] = some object 5
e[5] = a
f[5] = []
g[5] = ()

Jak můžete vidět, jen aby to velký seznam odkazů na stejné Žádné objektu trvá jen velmi málo času.

Prepending nebo prodloužení trvá déle (i ne průměrný nic, ale po spuštění to několikrát mohu vám říci, že prodloužení a připojením trvat zhruba ve stejnou dobu).

Přidělení nového objektu pro každý prvek - to je to, co zabere nejvíce času. A S.Lott odpověď dělá - formátuje pokaždé nový řetězec. Který není nezbytně nutné - chcete-li pre-přidělit nějaký prostor, stačí vytvořit seznam Žádný, pak přiřazení dat do seznamu prvků na vůli. Ať tak či onak to zabere více času pro generování dat, než se připojit / rozšířit seznam, ať už jej generovat při vytváření seznamu, nebo po tom. Ale pokud chcete řídce obydlených seznamu, pak začíná se seznamem Žádný je určitě rychlejší.

Odpovězeno 04/04/2011 v 01:48
zdroj uživatelem

hlasů
12

Pythonic způsob, jak to je:

x = [None] * numElements

nebo cokoliv výchozí hodnota, kterou chcete prepop s, např

bottles = [Beer()] * 99
sea = [Fish()] * many
vegetarianPizzas = [None] * peopleOrderingPizzaNotQuiche

Výchozí přístup Python může být velmi efektivní, když se efektivita se rozpadá, jak zvýšit počet prvků.

porovnat

import time

class Timer(object):
    def __enter__(self):
        self.start = time.time()
        return self

    def __exit__(self, *args):
        end = time.time()
        secs = end - self.start
        msecs = secs * 1000  # millisecs
        print('%fms' % msecs)

Elements   = 100000
Iterations = 144

print('Elements: %d, Iterations: %d' % (Elements, Iterations))


def doAppend():
    result = []
    i = 0
    while i < Elements:
        result.append(i)
        i += 1

def doAllocate():
    result = [None] * Elements
    i = 0
    while i < Elements:
        result[i] = i
        i += 1

def doGenerator():
    return list(i for i in range(Elements))


def test(name, fn):
    print("%s: " % name, end="")
    with Timer() as t:
        x = 0
        while x < Iterations:
            fn()
            x += 1


test('doAppend', doAppend)
test('doAllocate', doAllocate)
test('doGenerator', doGenerator)

s

#include <vector>
typedef std::vector<unsigned int> Vec;

static const unsigned int Elements = 100000;
static const unsigned int Iterations = 144;

void doAppend()
{
    Vec v;
    for (unsigned int i = 0; i < Elements; ++i) {
        v.push_back(i);
    }
}

void doReserve()
{
    Vec v;
    v.reserve(Elements);
    for (unsigned int i = 0; i < Elements; ++i) {
        v.push_back(i);
    }
}

void doAllocate()
{
    Vec v;
    v.resize(Elements);
    for (unsigned int i = 0; i < Elements; ++i) {
        v[i] = i;
    }
}

#include <iostream>
#include <chrono>
using namespace std;

void test(const char* name, void(*fn)(void))
{
    cout << name << ": ";

    auto start = chrono::high_resolution_clock::now();
    for (unsigned int i = 0; i < Iterations; ++i) {
        fn();
    }
    auto end = chrono::high_resolution_clock::now();

    auto elapsed = end - start;
    cout << chrono::duration<double, milli>(elapsed).count() << "ms\n";
}

int main()
{
    cout << "Elements: " << Elements << ", Iterations: " << Iterations << '\n';

    test("doAppend", doAppend);
    test("doReserve", doReserve);
    test("doAllocate", doAllocate);
}

Na můj Windows 7 i7, 64-bit Python dává

Elements: 100000, Iterations: 144
doAppend: 3587.204933ms
doAllocate: 2701.154947ms
doGenerator: 1721.098185ms

Zatímco C ++ dává (postavený s MSVC, 64-bit, optimalizace povoleno)

Elements: 100000, Iterations: 144
doAppend: 74.0042ms
doReserve: 27.0015ms
doAllocate: 5.0003ms

C ++ ladicí sestavení vyrábí:

Elements: 100000, Iterations: 144
doAppend: 2166.12ms
doReserve: 2082.12ms
doAllocate: 273.016ms

Pointa je, že s Python můžete dosáhnout zvýšení výkonu o 7-8%, a pokud si myslíte, že píšete aplikaci vysoce výkonné (nebo pokud píšete něco, co se používá v webové služby nebo něco podobného), pak že není třeba přičichl, ale možná budete muset přehodnotit svůj výběr jazyka.

Také, Python kód zde není opravdu Python kód. Přepnutí na skutečně Pythonesque kódu zde poskytuje lepší výkon:

import time

class Timer(object):
    def __enter__(self):
        self.start = time.time()
        return self

    def __exit__(self, *args):
        end = time.time()
        secs = end - self.start
        msecs = secs * 1000  # millisecs
        print('%fms' % msecs)

Elements   = 100000
Iterations = 144

print('Elements: %d, Iterations: %d' % (Elements, Iterations))


def doAppend():
    for x in range(Iterations):
        result = []
        for i in range(Elements):
            result.append(i)

def doAllocate():
    for x in range(Iterations):
        result = [None] * Elements
        for i in range(Elements):
            result[i] = i

def doGenerator():
    for x in range(Iterations):
        result = list(i for i in range(Elements))


def test(name, fn):
    print("%s: " % name, end="")
    with Timer() as t:
        fn()


test('doAppend', doAppend)
test('doAllocate', doAllocate)
test('doGenerator', doGenerator)

který dává

Elements: 100000, Iterations: 144
doAppend: 2153.122902ms
doAllocate: 1346.076965ms
doGenerator: 1614.092112ms

(V 32-bit doGenerator dělá lepší než doAllocate).

Zde je rozdíl mezi doAppend a doAllocate je podstatně větší.

Je zřejmé, že rozdíly zde opravdu platí pouze v případě, děláte to víc než hrst časů, nebo pokud děláte to v silně zatíženém systému, kde tato čísla se dostanou zmenšen ven řádů, nebo pokud máte co do činění s podstatně větší seznamy.

Pointa: Ještě jí pythonic cestu k dosažení nejlepšího výkonu.

Ale pokud jste se obávají o obecně, na vysoké úrovni výkonnosti, Python je nesprávný jazyk. Nejzásadnější problém je, že Python volání funkce byla tradičně aľ 300x pomalejší než u jiných jazyků díky Python prvků jako dekorace atd ( https://wiki.python.org/moin/PythonSpeed/PerformanceTips#Data_Aggregation#Data_Aggregation ).

Odpovězeno 11/06/2014 v 23:21
zdroj uživatelem

hlasů
7

Jak již bylo zmíněno jiní, nejjednodušší způsob, jak pre-seed seznam s NoneTypeobjekty.

Jak již bylo řečeno, měli byste pochopit, jak uvádí Python ve skutečnosti pracovat před rozhodnutím, je to nutné. Při implementaci CPython seznamu, podkladové pole je vždy vytvořena s horním pokoji, v progresivně větších velikostech ( 4, 8, 16, 25, 35, 46, 58, 72, 88, 106, 126, 148, 173, 201, 233, 269, 309, 354, 405, 462, 526, 598, 679, 771, 874, 990, 1120, etc), takže změna velikosti seznamu nestává zdaleka tak často.

Z důvodu tohoto chování, většina list.append() funkcí je O(1)složitost pro připojení se, jen s větší složitost při překročení jedné z těchto hranic, na kterém místě složitost bude O(n). Toto chování je to, co vede k minimálnímu nárůstu doby provedení S. Lott je odpověď.

Zdroj: http://www.laurentluce.com/posts/python-list-implementation/

Odpovězeno 12/01/2017 v 18:20
zdroj uživatelem

hlasů
4

i běžel @ kódu s.lott a produkoval stejný 10% zvýšení perf předběžným přidělením. Zkoušel @ Jeremyho nápadu pomocí generátoru a byl schopen vidět perf o gen lepší než doAllocate. Pro mou proj otázek, 10% zlepšení, takže díky všem, protože to pomůže parta.

def doAppend( size=10000 ):
    result = []
    for i in range(size):
        message= "some unique object %d" % ( i, )
        result.append(message)
    return result

def doAllocate( size=10000 ):
    result=size*[None]
    for i in range(size):
        message= "some unique object %d" % ( i, )
        result[i]= message
    return result

def doGen( size=10000 ):
    return list("some unique object %d" % ( i, ) for i in xrange(size))

size=1000
@print_timing
def testAppend():
    for i in xrange(size):
        doAppend()

@print_timing
def testAlloc():
    for i in xrange(size):
        doAllocate()

@print_timing
def testGen():
    for i in xrange(size):
        doGen()


testAppend()
testAlloc()
testGen()

testAppend took 14440.000ms
testAlloc took 13580.000ms
testGen took 13430.000ms
Odpovězeno 21/10/2009 v 20:09
zdroj uživatelem

hlasů
3

Obavy z pre-alokace v Pythonu nastat, pokud pracujete s Numpy, který má více C-jako matice. V tomto případě pre-alokace obavy jsou o podobě dat a výchozí hodnotu.

Zvážit numpy pokud děláte numerické výpočty na masivní seznamy a chtějí výkon.

Odpovězeno 30/07/2014 v 03:22
zdroj uživatelem

hlasů
0

U některých aplikací, slovník může být to, co hledáte. Například v metodě find_totient, zjistil jsem, že je výhodnější použít slovník, protože jsem neměl nulový index.

def totient(n):
    totient = 0

    if n == 1:
        totient = 1
    else:
        for i in range(1, n):
            if math.gcd(i, n) == 1:
                totient += 1
    return totient

def find_totients(max):
    totients = dict()
    for i in range(1,max+1):
        totients[i] = totient(i)

    print('Totients:')
    for i in range(1,max+1):
        print(i,totients[i])

Tento problém by mohl být vyřešen pomocí preallocated seznamu:

def find_totients(max):
    totients = None*(max+1)
    for i in range(1,max+1):
        totients[i] = totient(i)

    print('Totients:')
    for i in range(1,max+1):
        print(i,totients[i])

Mám pocit, že to není tak elegantní a náchylné k chybám, protože jsem ukládání Žádné, které by mohly vyvolat výjimku, pokud bych náhodou používat je špatně, a proto, že musím začít přemýšlet o případy okrajových Mapa mi umožňuje vyhnout.

Je pravda, slovník nebude tak efektivní, ale jak již uvedl, malé rozdíly v rychlosti nejsou vždy stojí za významné nebezpečí pro údržbu.

Odpovězeno 27/10/2016 v 16:33
zdroj uživatelem

hlasů
0

Z toho, co vím, seznamy python jsou již dosti podobný ArrayLists. Ale pokud chcete vyladit ty parametry, našel jsem tento post na netu, které mohou být zajímavé (v podstatě stačí vytvořit si vlastní ScalableListrozšíření):

http://mail.python.org/pipermail/python-list/2000-May/035082.html

Odpovězeno 22/11/2008 v 22:07
zdroj uživatelem

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more