Teljesítmény¶
Profilozás¶
A profilozás a program végrehajtásának elemzéséről és az összesített adatok méréséről szól. Ezek az adatok lehetnek az egyes függvények eltelt ideje, a végrehajtott SQL lekérdezések…
Bár a profilozás önmagában nem javítja a program teljesítményét, nagyon hasznos lehet a teljesítményproblémák felderítésében és annak azonosításában, hogy a program mely része felelős ezekért.
Az Odoo egy integrált profilozó eszközt biztosít, amely lehetővé teszi az összes végrehajtott lekérdezés és veremnyomkövetés rögzítését a végrehajtás során. Használható egy felhasználói munkamenet kéréseinek készletének vagy egy adott kódrészlet profilozására. A profilozási eredmények megtekinthetők az integrált speedscope nyílt forráskódú alkalmazás, amely lehetővé teszi a lánggrafikon megjelenítését nézetben, vagy egyedi eszközökkel elemezhetők, ha először JSON fájlba vagy az adatbázisba mentjük őket.
A profilozó engedélyezése¶
A profilozó engedélyezhető a felhasználói felületről, ami a legegyszerűbb módja, de csak webes kérések profilozását teszi lehetővé, vagy Python kódból, ami lehetővé teszi bármely kódrészlet, beleértve a teszteket is, profilozását.
Mielőtt egy profilozási munkamenetet elindítanánk, a profilozót globálisan engedélyezni kell az adatbázison. Ez kétféleképpen történhet:
Open the developer mode tools, then toggle the Enable profiling button. A wizard suggests a set of expiry times for the profiling. Click on ENABLE PROFILING to enable the profiler globally.
Menjen a Beállítások –> Általános beállítások –> Teljesítmény menüpontra, és állítsa be a kívánt időt a Profilozás engedélyezése eddig mezőben.
After the profiler is enabled on the database, users can enable it on their session. To do so, toggle the Enable profiling button in the developer mode tools again. By default, the recommended options Record sql and Record traces are enabled. To learn more about the different options, head over to Gyűjtők.
Amikor a profilozó engedélyezve van, az összes szerverhez intézett kérés profilozásra kerül és elmentésre kerül egy ir.profile rekordba. Ezek a rekordok a jelenlegi profilozási munkamenetbe vannak csoportosítva, amely a profilozó engedélyezésétől annak letiltásáig tart.
Megjegyzés
Az Odoo Online adatbázisok nem profilozhatók.
A profilozó manuális indítása kényelmes lehet egy adott metódus vagy a kód egy részének profilozásához. Ez a kód lehet egy teszt, egy számítási metódus, az egész betöltés stb.
A profilozó indításához Python kódból, hívja meg azt kontextuskezelőként. Meghatározhatja, hogy mit szeretne rögzíteni a paraméterek segítségével. Egy gyorsbillentyű elérhető a tesztosztályok profilozásához: self.profile(). További információért a collectors paraméterről lásd: Gyűjtők.
Example
with Profiler():
do_stuff()
Example
with Profiler(collectors=['sql', PeriodicCollector(interval=0.1)]):
do_stuff()
Example
with self.profile():
with self.assertQueryCount(__system__=1211):
do_stuff()
Megjegyzés
A profilozó az assertQueryCount kívül van meghívva annak érdekében, hogy elkapja a kontextuskezelőből való kilépéskor végrehajtott lekérdezéseket (pl. flush).
Amikor a profilozó engedélyezve van, a teszt módszer minden végrehajtása profilozva van, és elmentésre kerül egy ir.profile rekordba. Az ilyen rekordok egyetlen profilozási ülésbe vannak csoportosítva. Ez különösen hasznos a @warmup és @users dekorátorok használatakor.
Javaslat
Bonyolult lehet egy módszer profilozási eredményeinek elemzése, amelyet többször hívtak meg, mert az összes hívás együtt van csoportosítva a verem nyomvonalban. Adjon hozzá egy végrehajtási kontextust kontextuskezelőként, hogy az eredményeket több keretre bontsa.
Example
for index in range(max_index):
with ExecutionContext(current_index=index): # Identify each call in speedscope results.
do_stuff()
Az eredmények elemzése¶
To browse the profiling results, make sure that the profiler is enabled globally on the
database, then open the developer mode tools and click on the button in the top-right corner of the profiling
section. A list view of the ir.profile records grouped by profiling session opens.
Minden rekordnak van egy kattintható linkje, amely új lapon nyitja meg a speedscope eredményeket.
A Speedscope nem tartozik ennek a dokumentációnak a hatáskörébe, de sok eszköz áll rendelkezésre kipróbálásra: keresés, hasonló keretek kiemelése, nagyítás a kereten, idővonal, bal oldali nehéz, szendvics nézet…
A bekapcsolt profilozási opcióktól függően az Odoo különböző nézetmódokat generál, amelyekhez a felső menüből férhet hozzá.
A Kombinált nézet az összes SQL lekérdezést és nyomkövetést egyesítve mutatja.
A Kombinált kontextus nélkül nézet ugyanazt az eredményt mutatja, de figyelmen kívül hagyja a mentett végrehajtási kontextust <performance/profiling/enable>`.
A sql (nincs rés) nézet az összes SQL lekérdezést úgy mutatja, mintha egymás után hajtották volna végre, bármilyen Python logika nélkül. Ez hasznos lehet csak az SQL optimalizálásához.
A sql (sűrűség) nézet csak az összes SQL lekérdezést mutatja, közöttük rést hagyva. Ez hasznos lehet annak megállapítására, hogy az SQL vagy a Python kód a probléma, és azonosítani azokat a zónákat, ahol sok kis lekérdezést lehetne csoportosítani.
A keretek nézet csak a periodikus gyűjtő eredményeit mutatja.
Fontos
Bár a profilozót úgy tervezték, hogy a lehető legkönnyebb legyen, még mindig hatással lehet a teljesítményre, különösen a Sync gyűjtő használatakor. Ezt tartsa szem előtt a speedscope eredmények elemzésekor.
Gyűjtők¶
Míg a profilozó a profilozás mikor-járól szól, addig a gyűjtők a mit-ről gondoskodnak.
Each collector specializes in collecting profiling data in its own format and manner. They can be individually enabled from the user interface through their dedicated toggle button in the developer mode tools, or from Python code through their key or class.
Jelenleg négy gyűjtő érhető el az Odoo-ban:
Név |
Kapcsoló gomb |
Python kulcs |
Python osztály |
|---|---|---|---|
Rekord sql |
|
|
|
Nyomkövetések rögzítése |
|
|
|
Qweb rögzítése |
|
|
|
Nem |
|
|
Alapértelmezés szerint a profilozó engedélyezi az SQL és a Periodikus gyűjtőket. Mind a felhasználói felületről, mind a Python kódból történő engedélyezés esetén.
SQL gyűjtő¶
Az SQL gyűjtő elmenti az adatbázisba a jelenlegi szálon végrehajtott összes SQL lekérdezést (minden kurzorra), valamint a verem nyomvonalát. A gyűjtő többletterhelése hozzáadódik az elemzett szálhoz minden lekérdezésnél, ami azt jelenti, hogy sok kis lekérdezés használata befolyásolhatja a végrehajtási időt és más profilozókat.
Különösen hasznos a lekérdezésszámok hibakeresésére, vagy információk hozzáadására a Periodikus gyűjtő kombinált speedscope nézetben.
Periodikus gyűjtő¶
Ez a gyűjtő külön szálon fut, és elmenti az elemzett szál verem nyomvonalát minden intervallumban. Az intervallum (alapértelmezés szerint 10 ms) meghatározható a felhasználói felületen az Intervallum opcióval, vagy a interval paraméterrel a Python kódban.
Figyelem
Ha az intervallum nagyon alacsony értékre van állítva, a hosszú kérések profilozása memória problémákat okozhat. Ha az intervallum nagyon magas értékre van állítva, az információk a rövid függvényvégrehajtásokról elvesznek.
Ez az egyik legjobb módja a teljesítmény elemzésének, mivel külön szálának köszönhetően nagyon alacsony hatással van a végrehajtási időre.
QWeb gyűjtő¶
Ez a gyűjtő elmenti az összes direktíva Python végrehajtási idejét és lekérdezéseit. Ahogy az SQL gyűjtő, a többletterhelés jelentős lehet sok kis direktíva végrehajtásakor. Az eredmények eltérnek más gyűjtőktől az összegyűjtött adatok tekintetében, és az ir.profile űrlap nézetből egyedi widget segítségével elemezhetők.
Elsősorban nézetek optimalizálására hasznos.
Szinkron gyűjtő¶
Ez a gyűjtő elmenti a verem minden függvényhívás és visszatérés esetén, és ugyanazon a szálon fut, ami jelentősen befolyásolja a teljesítményt.
Hasznos lehet a bonyolult folyamatok hibakeresésére és megértésére, valamint azok végrehajtásának követésére a kódban. Azonban nem ajánlott teljesítmény elemzésére, mert a többletterhelés magas.
Teljesítménybeli buktatók¶
Legyen óvatos a véletlenszerűséggel. Többszöri végrehajtás különböző eredményekhez vezethet. Például, ha a szemétgyűjtő aktiválódik a végrehajtás során.
Legyen óvatos a blokkoló hívásokkal. Bizonyos esetekben a külső
c_callidőt vehet igénybe, mielőtt elengedi a GIL-t, ami váratlanul hosszú keretekhez vezethet a Időszakos gyűjtő esetén. Ezt a profilozónak észlelnie kell, és figyelmeztetést kell adnia. Lehetőség van a profilozó manuális aktiválására az ilyen hívások előtt, ha szükséges.Figyeljen a gyorsítótárra. A profilozás előtt, hogy a
view/assets/… a gyorsítótárban van, különböző eredményekhez vezethet.Legyen tisztában a profilozó többletterhelésével. A SQL gyűjtő többletterhelése jelentős lehet, amikor sok kis lekérdezés kerül végrehajtásra. A profilozás hasznos lehet egy probléma észlelésére, de előfordulhat, hogy le szeretné tiltani a profilozót, hogy mérje a kódváltoztatás valódi hatását.
A profilozási eredmények memóriaigényesek lehetnek. Bizonyos esetekben (pl. egy telepítés vagy hosszú kérés profilozása) előfordulhat, hogy eléri a memóriahatárt, különösen a speedscope eredmények megjelenítésekor, ami HTTP 500 hibához vezethet. Ebben az esetben előfordulhat, hogy a szervert magasabb memóriahatárral kell indítania:
--limit-memory-hard $((8*1024**3)).
Jó gyakorlatok¶
Tömeges műveletek¶
Amikor rekordhalmazokkal dolgozik, szinte mindig jobb a tömeges műveletek alkalmazása.
Example
Ne hívjon meg olyan metódust, amely SQL lekérdezéseket futtat, miközben egy rekordhalmazon iterál, mert ezt minden egyes rekordnál megteszi.
def _compute_count(self):
for record in self:
domain = [('related_id', '=', record.id)]
record.count = other_model.search_count(domain)
Ehelyett cserélje le a search_count-ot egy _read_group-ra, hogy egyetlen SQL lekérdezést hajtson végre az egész rekordhalmazra.
def _compute_count(self):
domain = [('related_id', 'in', self.ids)]
counts_data = other_model._read_group(domain, ['related_id'], ['__count'])
mapped_data = dict(counts_data)
for record in self:
record.count = mapped_data.get(record, 0)
Megjegyzés
Ez a példa nem optimális és nem helyes minden esetben. Csak a search_count helyettesítésére szolgál. Egy másik megoldás lehet az előzetes betöltés és az inverz One2many mező számlálása.
Example
Ne hozzon létre rekordokat egymás után.
for name in ['foo', 'bar']:
model.create({'name': name})
Ehelyett halmozza fel a létrehozási értékeket, és hívja meg a create metódust a tömegre. Ennek többnyire nincs hatása, és segíti a keretrendszert a mezők számításának optimalizálásában.
create_values = []
for name in ['foo', 'bar']:
create_values.append({'name': name})
records = model.create(create_values)
Example
Nem sikerült előtölteni a rekordhalmaz mezőit, miközben egyetlen rekordot böngészünk egy ciklusban.
for record_id in record_ids:
model.browse(record_id)
record.foo # One query is executed per record.
Ehelyett először böngéssze át az egész rekordhalmazt.
records = model.browse(record_ids)
for record in records:
record.foo # One query is executed for the entire recordset.
Ellenőrizhetjük, hogy a rekordok előtöltése csoportosan történik-e, ha elolvassuk a prefetch_ids mezőt, amely tartalmazza az egyes rekordazonosítókat. az összes rekord együttes böngészése nem praktikus,
Szükség esetén a with_prefetch metódus használható a csoportos előtöltés letiltására:
for values in values_list:
message = self.browse(values['id']).with_prefetch(self.ids)
Csökkentse az algoritmikus komplexitást¶
Az algoritmikus komplexitás annak mértéke, hogy egy algoritmus mennyi idő alatt fejeződik be a bemenet n méretéhez viszonyítva. Ha a komplexitás magas, a végrehajtási idő gyorsan növekedhet, ahogy a bemenet nagyobb lesz. Bizonyos esetekben az algoritmikus komplexitás csökkenthető a bemenet adatok helyes előkészítésével.
Example
Egy adott probléma esetén vegyünk figyelembe egy naiv algoritmust, amelyet két beágyazott ciklussal készítettek, és amelynek komplexitása O(n²).
for record in self:
for result in results:
if results['id'] == record.id:
record.foo = results['foo']
break
Feltételezve, hogy minden eredménynek különböző azonosítója van, előkészíthetjük az adatokat a komplexitás csökkentése érdekében.
mapped_result = {result['id']: result['foo'] for result in results}
for record in self:
record.foo = mapped_result.get(record.id)
Example
A rossz adatstruktúra kiválasztása a bemenet tárolására kvadratikus komplexitáshoz vezethet.
invalid_ids = self.search(domain).ids
for record in self:
if record.id in invalid_ids:
...
Ha az invalid_ids egy lista-szerű adatstruktúra, az algoritmus komplexitása kvadratikus lehet.
Ehelyett inkább használjon halmazműveleteket, például az invalid_ids halmazzá alakítását.
invalid_ids = set(invalid_ids)
for record in self:
if record.id in invalid_ids:
...
A bemenettől függően rekordhalmaz műveletek is használhatók.
invalid_ids = self.search(domain)
for record in self - invalid_ids:
...
Használjon indexeket¶
Az adatbázis indexek segíthetnek a keresési műveletek gyorsításában, legyen az keresés az vagy a felhasználói felületen keresztül.
name = fields.Char(string="Name", index=True)
Figyelem
Ügyeljen arra, hogy ne indexeljen minden mezőt, mivel az indexek helyet foglalnak és hatással vannak a teljesítményre az INSERT, UPDATE és DELETE végrehajtásakor.