-
QuestionHello, I'm trying to make a nicegui interface that will launch heavy computation on multiple processors. I'm not sure to understand how multiprocessors and nicegui work together. I made a basic exemple with a button launching a 1s run on 10 proc : the "calculation" is done in about 5s. import multiprocessing
import time
def calcul(duration):
time.sleep(duration)
return duration
def launch_multi(num_processes, duration):
beg = time.perf_counter()
with multiprocessing.Pool(processes=num_processes) as pool:
results = pool.map(calcul, [duration] * num_processes)
print(f'All done in {time.perf_counter()-beg} s')
if __name__ in {"__main__", "__mp_main__"}:
num_processes = 10
duration = 1.0
from nicegui import ui
@ui.page('/')
def index():
with ui.row():
ui.button('launch', on_click=lambda np=num_processes, tps=duration: launch_multi(np, tps))
ui.run() When I do the same thing, without nicegui, it's done in about 1.2s. import multiprocessing
import time
def calcul(duration):
time.sleep(duration)
return duration
def launch_multi(num_processes, duration):
beg = time.perf_counter()
with multiprocessing.Pool(processes=num_processes) as pool:
results = pool.map(calcul, [duration] * num_processes)
print(f'All done in {time.perf_counter()-beg} s')
if __name__ =="__main__":
num_processes = 10
duration = 1.0
launch_multi(num_processes, duration) I don't really understand what is causing this difference ? Looks like each process has a lot of initialisation to do ? If any knows how it works and have some guidelines to do such a thing, i'll be glad ! |
Beta Was this translation helpful? Give feedback.
Replies: 3 comments 2 replies
-
Thanks for pointing out this discrepancy @chupins. This is super crucial and quite fundamental when using NiceGUI with multiprocessing. If you add a print statement inside the main guard (where you init nicegui) you will see that it is called for every subprocess. This is due to the fact that each process is starting a fresh Python interpreter — which means it re-imports the main module. A standard main guard with if __name__ == "__main__":
num_processes = 10
duration = 1.0
from nicegui import ui
ui.button('launch', on_click=lambda np=num_processes, tps=duration: launch_multi(np, tps))
ui.run(reload=False) If you want auto-reloading, an additional environment variable can be used to indicate whether the UI should be initalized or not. Here is a boiled down example: import multiprocessing
import os
import time
def launch() -> None:
# NOTE:environment variables are passed to the subprocess and serve as additional main guard
os.environ['IS_WORKER'] = '1'
start = time.perf_counter()
with multiprocessing.Pool(processes=10) as pool:
pool.map(time.sleep, [1] * 10)
print(f'All done in {time.perf_counter()-start} s')
if __name__ in {"__main__", "__mp_main__"} and not os.environ.get('IS_WORKER'):
from nicegui import ui
ui.button('launch', on_click=launch)
ui.run() This should be part of the documentation. I just unsure where... |
Beta Was this translation helpful? Give feedback.
-
@rodja Our import asyncio
import time
from nicegui import run, ui
def wait():
time.sleep(1.0)
async def launch():
start = time.perf_counter()
await asyncio.gather(*[run.cpu_bound(wait) for _ in range(10)])
print(f'All done in {time.perf_counter() - start:.2f} s')
if __name__ in {'__main__', '__mp_main__'}:
print('running main')
ui.button('launch', on_click=launch)
ui.run() This prints another 10 "running main" after NiceGUI started. Then the first run is much slower than 1s, all subsequent runs are as fast as expected:
It's like in the following simpler example. The first run takes notecibly longer: from nicegui import run, ui
def f() -> None:
print('f')
ui.button('run f', on_click=lambda: run.cpu_bound(f))
if __name__ in {'__main__', '__mp_main__'}:
print('running main')
ui.run() |
Beta Was this translation helpful? Give feedback.
-
While I'm still looking for a way to avoid such imports in a multiprocessing case, I quickly measured to find the most critical ones:
So it is mainly nicegui (creating an |
Beta Was this translation helpful? Give feedback.
Thanks for pointing out this discrepancy @chupins. This is super crucial and quite fundamental when using NiceGUI with multiprocessing. If you add a print statement inside the main guard (where you init nicegui) you will see that it is called for every subprocess. This is due to the fact that each process is starting a fresh Python interpreter — which means it re-imports the main module. A standard main guard with
__name__ == '__main__'
protects code from being run in such spawned sub-processes. But with NiceGUI the default is auto-reload (eg.ui.run(reload=True)
). This means the main process__main__
already starts the code in a sub-process which then identifies as__name__ == '__mp_mai…