setdefault против производительности defaultdict

Я пишу код для приложения, где важна производительность. Мне интересно почему defaultdict кажется быстрее setdefault,

Я хотел бы иметь возможность использовать setdefaultВ основном потому, что мне не нравится вывод на печать вложенных defaultdict (см. реализацию ниже).

В моем коде мне нужно проверить, если element_id это уже ключ к диктату.

Вот две функции, которые я тестирую:

def defaultdictfunc(subcases,other_ids,element_ids):
    dict_name= defaultdict(lambda: defaultdict(lambda: defaultdict(dict)))
    for subcase in subcases:
        for other_id in other_ids:
            for element_id in element_ids: 
                if element_id in dict_name[subcase][other_id]:
                    # error duplicate element_id
                    pass
                else:
                    dict_name[subcase][other_id][element_id]=0
    return dict_name

def setdefaultfunc(subcases,other_ids,element_ids):
    dict_name={}
    for subcase in subcases:
        for other_id in other_ids:
            for element_id in element_ids: 
                if element_id in dict_name.setdefault(subcase,{}).setdefault(other_id,{}):
                    # error duplicate element_id
                    pass
                else:
                    dict_name[subcase][other_id][element_id]=0

    return dict_name

IPython ввода и вывода:

In [1]: from numpy.random import randint

In [2]: subcases,other_ids,element_ids=(randint(0,100,100),randint(0,100,100),randint(0,100,100))

In [5]: from collections import defaultdict

In [6]: defaultdictfunc(subcases,other_ids,element_ids)==setdefaultfunc(subcases,other_ids,element_ids)
Out[6]: True

In [7]: %timeit defaultdictfunc(subcases,other_ids,element_ids)
10 loops, best of 3: 177 ms per loop

In [8]: % timeit setdefaultfunc(subcases,other_ids,element_ids)
1 loops, best of 3: 351 ms per loop

Почему setdefaultfunc помедленнее. Я думал, что основной код будет таким же. Есть ли способ улучшить его скорость?

Спасибо

3 ответа

По словам пользователя aneroid:

Было бы понятно, что defaultdict быстрее, чем dict.setdefault() поскольку первый устанавливает значение по умолчанию для всего dict во время создания, тогда как setdefault() делает это для каждого элемента, когда он читается. Одна из причин использования setdefault заключается в том, что назначаемое вами значение по умолчанию основано на ключе (или чем-то еще), а не на общем значении по умолчанию для всего dict.

      val = 20_000_000


def defaultdict():
    """
    defaultdict 1000000: 0.4460279941558838
    defaultdict 10000000: 4.371468782424927
    defaultdict 20000000: 8.807381391525269
    """
    from collections import defaultdict
    import time
    a = defaultdict(list)
    t = time.time()
    for i in range(val):
        key = i % (val / 2)
        a[key].append(i)
    print(f'defaultdict {val}:', time.time() - t)


def setdefault():
    """
    setdefault 1000000: 0.3767530918121338
    setdefault 10000000: 4.230009078979492
    setdefault 20000000: 8.19938588142395
    """
    import time
    a = {}
    t = time.time()
    for i in range(val):
        key = i % (val / 2)
        a.setdefault(key, []).append(i)
    print(f'setdefault {val}:', time.time() - t)

Питон 3.10

В setdefaultfunc хуже, потому что вы вызываете конструктор dict несколько раз в цикле (поскольку {} эквивалентно dict()), пока defaultdict избежать этого по собственной инициативе.

С небольшим изменением вы можете легко улучшить setdefaultfunc:

def setdefaultfunc2(subcases,other_ids,element_ids):
    dict_name={}
    for subcase in subcases:
        subcase_dict = dict_name.setdefault(subcase,{})
        for other_id in other_ids:
            other_id_dict = subcase_dict.setdefault(other_id,{})
            for element_id in element_ids: 
                if element_id in other_id_dict:
                    # error duplicate element_id
                    pass
                else:
                    other_id_dict[element_id]=0
    return dict_name

С этим изменением результаты на моей машине были:

In [37]: defaultdictfunc(subcases,other_ids,element_ids)==setdefaultfunc2(subcases,other_ids,element_ids)
Out[37]: True

In [38]: %timeit defaultdictfunc(subcases,other_ids,element_ids)
286 ms ± 8.55 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [39]: %timeit setdefaultfunc(subcases,other_ids,element_ids)
434 ms ± 1.78 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [40]: %timeit setdefaultfunc2(subcases,other_ids,element_ids)
174 ms ± 348 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

ИМО, defaultdict не обеспечивает достаточного прироста производительности, чтобы его можно было использовать.

Другие вопросы по тегам