hoffstadt / DearPyGui

Dear PyGui: A fast and powerful Graphical User Interface Toolkit for Python with minimal dependencies
https://dearpygui.readthedocs.io/en/latest/
MIT License
12.94k stars 676 forks source link

Iteraction w/`generate_uuid` and item creation adds substantial overhead to `generate_uuid` #2028

Open Atlamillias opened 1 year ago

Atlamillias commented 1 year ago

DearPyGui v1.8.0 Python 3.11.1 on Windows 10, 11

This is going to be a bit of a scroll, apologies.

Calling generate_uuid by itself takes very little time. You need to call it around fifty-thousand times to even get a profile reading.

(I've removed unrelated profile outputs in all cases below to shorten this)

import cProfile
import dearpygui.dearpygui as dpg

dpg.create_context()
dpg.create_viewport()
dpg.setup_dearpygui()

profiler = cProfile.Profile()

def testcase1():
    with profiler:
        for _ in range(100_000):
            dpg.add_window(show=False)
    profiler.print_stats()

testcase1()
# ncalls  tottime  percall  cumtime  percall filename:lineno(function)
# 100000    0.016    0.000    0.026    0.000 dearpygui.py:8123(generate_uuid)
# 100000    0.009    0.000    0.009    0.000 {built-in method dearpygui._dearpygui.generate_uuid}

It takes longer to create just as many items, but it's not unreasonable by any means;

def testcase2():
    with profiler:
        for _ in range(100_000):
            dpg.add_window(show=False)
    profiler.print_stats()

testcase2()
#  ncalls  tottime  percall  cumtime  percall filename:lineno(function)
#  100000    0.152    0.000    1.094    0.000 dearpygui.py:7354(add_window)
#  100000    0.935    0.000    0.935    0.000 {built-in method dearpygui._dearpygui.add_window}

The below example calls both generate_uuid and add_window. Because I'm doubling the function calls per iteration, I've reduced my total iterations by a generous 75%;

def testcase3():
    with profiler:
        for _ in range(25_000):
            dpg.generate_uuid()
            dpg.add_window(show=False)
    profiler.print_stats()

testcase3()
# ncalls  tottime  percall  cumtime  percall filename:lineno(function)
# 25000    0.061    0.000    0.335    0.000 dearpygui.py:7354(add_window)
# 25000    0.011    0.000    3.516    0.000 dearpygui.py:8123(generate_uuid)
# 25000    0.270    0.000    0.270    0.000 {built-in method dearpygui._dearpygui.add_window}
# 25000    3.506    0.000    3.506    0.000 {built-in method dearpygui._dearpygui.generate_uuid}

It takes ~350% longer to create 1/4th as many items as testcase2. You can see that the holdup is coming from generate_uuid and not add_window.

The below is with 100,000 iterations. Twice as many calls as cases 1 and 2. Does not take twice as long...

def testcase4():
    # get ready to take a nap
    with profiler:
        for _ in range(100_000):
            dpg.generate_uuid()
            dpg.add_window(show=False)
    profiler.print_stats()

testcase4()
# ncalls  tottime  percall  cumtime  percall filename:lineno(function)
# 100000    0.569    0.000    2.405    0.000 dearpygui.py:7354(add_window)
# 100000    0.116    0.000  119.127    0.001 dearpygui.py:8123(generate_uuid)
# 100000    1.809    0.000    1.809    0.000 {built-in method dearpygui._dearpygui.add_window}
# 100000  119.011    0.001  119.011    0.001 {built-in method dearpygui._dearpygui.generate_uuid}

My work laptop ain't great, but that took two friggin' minutes. That's kinda nuts.

Calling testcase1 and testcase2 in sequence (or vice-versa) takes about as long as you'd expect. The overhead from generate_uuid can only be reproduced by calling it immediately after creating an item.

The amount of iterations I used may seem excessive, but I do not find it unrealistic needing to create thousands of items in some cases, especially when using tables or value items.

Below is the code from the above examples.

import cProfile
import dearpygui.dearpygui as dpg

dpg.create_context()
dpg.create_viewport()
dpg.setup_dearpygui()

profiler = cProfile.Profile()

def testcase1():
    with profiler:
        for _ in range(100_000):
            dpg.generate_uuid()
    profiler.print_stats()

def testcase2():
    with profiler:
        for _ in range(100_000):
            dpg.add_window(show=False)
    profiler.print_stats()

def testcase3():
    with profiler:
        for _ in range(25_000):
            dpg.generate_uuid()
            dpg.add_window(show=False)
    profiler.print_stats()

def testcase4():
    # get ready to take a nap
    with profiler:
        for _ in range(100_000):
            dpg.generate_uuid()
            dpg.add_window(show=False)
    profiler.print_stats()
Conquerix commented 1 year ago

I get the same effect when using the tag keyword at item generation. For now I only use dpg.lastitem and the output of dpg.add* functions

I use dpg 1.9.1 on python 3.8.6