Die Leistungsoptimierungsarbeit, die die Druckanweisung vorbereitet und die Ausführungszeit ausgibt, ist schmerzhaft, daher spreche ich davon, sie zu stoppen.
Verbesserungen sind einfach, wenn das Programm langsam laufende Logik erkennen kann. Wenn Sie den Profiler verwenden, können Sie die Ursache leicht identifizieren, sodass ich Ihnen zeigen werde, wie Sie ihn verwenden. Die erste Hälfte ist eine Methode zum Identifizieren der Logik für langsame Ausführung mithilfe von line_profiler, und die zweite Hälfte ist eine Beschleunigungstechnik in Python.
Verwenden Sie den Profiler in Ihrer lokalen Umgebung, um festzustellen, welche Zeile schwer ist. Es gibt verschiedene Profiler in Python, aber ich persönlich benutze line_profiler, weil es die notwendigen und ausreichenden Funktionen hat. Was wir hier spezifizieren, ist das "welche Zeile N-mal ausgeführt wurde und die Gesamtausführungszeit M% beträgt".
Ich habe einen Beispielcode geschrieben, dessen Ausführung ungefähr 10 Sekunden dauert. Bitte lesen Sie den Prozess von time.sleep () als DB-Zugriff. Es ist ein Programm, das die Daten zurückgibt, dass der Benutzer 1000 Karten und 3 Fähigkeiten für jede Karte mit json hat.
■ Der Profiler sagt Ihnen alles, sodass Sie den Code überspringen können
sample1.py
# -*- coding: utf-8 -*-
from __future__ import absolute_import, unicode_literals
import random
import time
import simplejson
class UserCardSkill(object):
def __init__(self, user_id, card_id):
self.id = random.randint(1, 1000), #Der SkillID-Bereich ist 1-Angenommen 999
self.user_id = user_id
self.card_id = card_id
@property
def name(self):
return "skill:{}".format(str(self.id))
@classmethod
def get_by_card(cls, user_id, card_id):
time.sleep(0.01)
return [cls(user_id, card_id) for x in xrange(3)] #Karte hat 3 Fähigkeiten
def to_dict(self):
return {
"name": self.name,
"skill_id": self.id,
"card_id": self.card_id,
}
class UserCard(object):
def __init__(self, user_id):
self.id = random.randint(1, 300) #Der Karten-ID-Bereich beträgt 1-Angenommen, 299
self.user_id = user_id
@property
def name(self):
return "CARD:{}".format(str(self.id))
@property
def skills(self):
return UserCardSkill.get_by_card(self.user_id, self.id)
@classmethod
def get_by_user(cls, user_id):
time.sleep(0.03)
return [cls(user_id) for x in range(1000)] #Angenommen, der Benutzer hat 1000 Karten
def to_dict(self):
"""
Konvertieren Sie Karteninformationen in Diktat und kehren Sie zurück
"""
return {
"name": self.name,
"skills": [skill.to_dict() for skill in self.skills],
}
def main(user_id):
"""
Antworten Sie mit json auf die Karteninformationen des Benutzers
"""
cards = UserCard.get_by_user(user_id)
result = {
"cards": [card.to_dict() for card in cards]
}
json = simplejson.dumps(result)
return json
user_id = "A0001"
main(user_id)
Lassen Sie uns nun das Profiler-Tool installieren und die schweren Stellen identifizieren.
install
pip install line_profiler
sample1_profiler.py
~~Kürzung~~
#Profiler-Instanziierung und Funktionsregistrierung
from line_profiler import LineProfiler
profiler = LineProfiler()
profiler.add_module(UserCard)
profiler.add_module(UserCardSkill)
profiler.add_function(main)
#Ausführung der registrierten Hauptfunktion
user_id = "A0001"
profiler.runcall(main, user_id)
#Ergebnisanzeige
profiler.print_stats()
Ausführungsergebnis
>>>python ./sample1_profiler.py
Timer unit: 1e-06 s
Total time: 0.102145 s
File: ./sample1_profiler.py
Function: __init__ at line 9
Line # Hits Time Per Hit % Time Line Contents
==============================================================
9 def __init__(self, user_id, card_id):
10 3000 92247 30.7 90.3 self.id = random.randint(1, 1000), #Der SkillID-Bereich ist 1-Angenommen 999
11 3000 5806 1.9 5.7 self.user_id = user_id
12 3000 4092 1.4 4.0 self.card_id = card_id
Total time: 0.085992 s
File: ./sample1_profiler.py
Function: to_dict at line 23
Line # Hits Time Per Hit % Time Line Contents
==============================================================
23 def to_dict(self):
24 3000 10026 3.3 11.7 return {
25 3000 66067 22.0 76.8 "name": self.name,
26 3000 6091 2.0 7.1 "skill_id": self.id,
27 3000 3808 1.3 4.4 "card_id": self.card_id,
28 }
Total time: 0.007384 s
File: ./sample1_profiler.py
Function: __init__ at line 32
Line # Hits Time Per Hit % Time Line Contents
==============================================================
32 def __init__(self, user_id):
33 1000 6719 6.7 91.0 self.id = random.randint(1, 300) #Der Karten-ID-Bereich beträgt 1-Angenommen, 299
34 1000 665 0.7 9.0 self.user_id = user_id
Total time: 11.0361 s
File: ./sample1_profiler.py
Function: to_dict at line 49
Line # Hits Time Per Hit % Time Line Contents
==============================================================
49 def to_dict(self):
50 """
51 Karteninformationen in Diktat konvertieren und zurückgeben
52 """
53 1000 1367 1.4 0.0 return {
54 1000 10362 10.4 0.1 "name": self.name,
55 4000 11024403 2756.1 99.9 "skills": [skill.to_dict() for skill in self.skills],
56 }
Total time: 11.1061 s
File: ./sample1_profiler.py
Function: main at line 59
Line # Hits Time Per Hit % Time Line Contents
==============================================================
59 def main(user_id):
60 """
61 Antworten Sie mit json auf die Karteninformationen des Benutzers
62 """
63 1 41318 41318.0 0.4 cards = UserCard.get_by_user(user_id)
64 1 1 1.0 0.0 result = {
65 1001 11049561 11038.5 99.5 "cards": [card.to_dict() for card in cards]
66 }
67 1 15258 15258.0 0.1 json = simplejson.dumps(result)
68 1 2 2.0 0.0 return json
■ Ich konnte eine dicke Linie mit dem Profiler identifizieren. Aus dem Ausführungsergebnis von line_profiler wurde festgestellt, dass die Verarbeitung der Zeilen 65 und 55 schwer war. Es scheint, dass der Benutzer über 1000 Karten verfügt. Aufgrund der 1000-maligen Abfrage von UserCardSkill für jede Karte dauerte die Ausführung mehr als 10 Sekunden.
Dies ist eine Technik zur Verbesserung der Ausführungsgeschwindigkeit eines bestimmten Programms. Wir werden das vom Profiler untersuchte Programm optimieren, indem wir Notizen mit dem Cache machen und nach Hash suchen, ohne die Codestruktur so weit wie möglich zu ändern. Ich möchte über Python sprechen, daher werde ich nicht über die Beschleunigung von SQL sprechen.
Reduzieren Sie die Anzahl der Skill-Abfragen für Benutzerkarten, ohne die Codestruktur so weit wie möglich zu ändern. Es ist ein Code, der UserCardSkill abruft, das dem Benutzer in einem Stapel zugeordnet ist, es im Speicher speichert und ab dem zweiten Mal den Wert aus den Daten im Speicher zurückgibt.
sample1_memoize.py
class UserCardSkill(object):
_USER_CACHE = {}
@classmethod
def get_by_card(cls, user_id, card_id):
#Funktion, um jedes Mal vor der Verbesserung auf die Datenbank zuzugreifen
time.sleep(0.01)
return [cls(user_id, card_id) for x in xrange(3)]
@classmethod
def get_by_card_from_cache(cls, user_id, card_id):
#Funktion zum erstmaligen Zugriff auf die Datenbank nach der Verbesserung
if user_id not in cls._USER_CACHE:
#Wenn sich keine Daten im Cache befinden, beziehen Sie alle mit dem Benutzer verbundenen Fähigkeiten aus der Datenbank
cls._USER_CACHE[user_id] = cls.get_all_by_user(user_id)
r = []
for skill in cls._USER_CACHE[user_id]:
if skill.card_id == card_id:
r.append(skill)
return r
@classmethod
def get_all_by_user(cls, user_id):
#Erwerben Sie alle mit dem Benutzer verbundenen Fähigkeiten gleichzeitig von der Datenbank
return list(cls.objects.filter(user_id=user_id))
from timeit import timeit
@timeit #Die Ausführungszeit wird gedruckt
def main(user_id):
Ausführungsergebnis
>>>sample1_memoize.py
func:'main' args:[(u'A0001',), {}] took: 0.6718 sec
Es ist mehr als 15-mal schneller als 11,1061 Sekunden vor der Verbesserung auf 0,6718 Sekunden. Der Grund für die Verbesserung der Ausführungsgeschwindigkeit ist, dass die Anzahl der Anfragen an UserCardSkill von 1000 auf 1 reduziert wurde.
Im Memorandum-Code wird die Liste "cls._USER_CACHE [user_id]" mit 3 * 1000 Elementen jedes Mal linear durchsucht (vollständiger Scan), um die Fertigkeit für jede Karte in der Funktion "get_by_card_from_cache" zu linearisieren. Da es ineffizient ist, jede Zeile zu durchsuchen, generieren Sie im Voraus ein Diktat mit card_id als Schlüssel und schreiben Sie es als Hash-Suche neu. In diesem Code beträgt der Rechenaufwand für die lineare Suche O (n) und der Rechenaufwand für die Hash-Suche O (1).
python
~~Kürzung~~
class UserCardSkill(object):
_USER_CACHE = {}
@classmethod
def get_by_card_from_cache(cls, user_id, card_id):
if user_id not in cls._USER_CACHE:
#Wenn sich keine Daten im Cache befinden, beziehen Sie alle mit dem Benutzer verbundenen Fähigkeiten aus der Datenbank
users_skill = cls.get_all_by_user(user_id)
# card_In Diktat mit ID als SCHLÜSSEL konvertieren
cardskill_dict = defaultdict(list)
for skill in users_skill:
cardskill_dict[skill.card_id].append(skill)
#Im Cache speichern
cls._USER_CACHE[user_id] = cardskill_dict
#Von der linearen Suche zur Hash-Suche umgeschrieben
return cls._USER_CACHE[user_id].get(card_id)
@classmethod
def get_all_by_user(cls, user_id):
#Erwerben Sie alle Fähigkeiten in Bezug auf Benutzer von DB
return list(cls.objects.filter(user_id=user_id))
Ausführungsergebnis
>>>sample1_hash.py
func:'main' args:[(u'A0001',), {}] took: 0.3840 sec
Vor der Verbesserung wurde die Liste mit 3000 Elementen vollständig auf 1000 Karten gescannt. "Skill.card_id == card_id:" wurde also 3 Millionen Mal aufgerufen. Da es durch Ersetzen durch Hash-Suche verschwunden ist, führt dies zu einer Verbesserung der Ausführungsgeschwindigkeit, selbst wenn die Kosten für die Generierung von Hash abgezogen werden.
お手軽なメモ化といえばcached_property
ではないでしょうか。インスタンスキャッシュにself.func.__name__
(サンプル実装であれば"skills")をKEYにして戻り値を保存しています。2回目以降の問い合わせではキャッシュから値を返却することで実行速度が改善します。実装は数行なのでコード読んだ方が早いかもしれません。cached_property.py#L12
cached_property.py
from cached_property import cached_property
class Card(object):
@cached_property
def skills(self):
return UserCardSkill.get_by_card(self.user_id, self.id)
@timeit
def main(user_id):
cards = Card.get_by_user(user_id)
for x in xrange(10):
cards[0].skills
Ausführungsergebnis
# cached_Vor dem Anwenden von Eigentum
>>>python ./cached_property.py
func:'main' args:[(u'A0001',), {}] took: 0.1443 sec
# cached_Nach dem Anwenden der Eigenschaft
>>> python ./sample1_cp.py
func:'main' args:[(u'A0001',), {}] took: 0.0451 sec
Es ist eine Geschichte unter der Annahme, dass der Webserver auf wsgi und Apache läuft.
Thread Local Storage (TLS) ist ein Mittel zum Zuweisen eines Speicherorts für eindeutige Daten für jeden Thread in einem bestimmten Multithread-Prozess. Wenn Sie einen Webserver mit wsgi und Apache ausführen und "MaxRequestsPerChild" in der Konfiguration auf einen Wert größer oder gleich 1 setzen, wird der untergeordnete Prozess nach "MaxRequestsPerChild" -Anforderungen beendet. Wenn Sie ein Programm schreiben, das TLS (Thread Local Storage) verwendet, können Sie den Cache für jeden untergeordneten Prozess speichern. Durch das Speichern von Daten, die allen Benutzern gemeinsam sind, wie z. B. Stammdaten, in TLS kann eine erhebliche Beschleunigung erwartet werden.
Ich habe ein Programm geschrieben, das Primzahlen aus ganzen Zahlen im Bereich von 0-500010 berechnet. Durch Aufzeichnen des Ergebnisses der Primzahlenberechnung in TLS werden die zweite und nachfolgende Primzahlenberechnung übersprungen.
tls.py
# -*- coding: utf-8 -*-
from __future__ import absolute_import, unicode_literals
import random
import threading
import time
threadLocal = threading.local()
def timeit(f):
def timed(*args, **kw):
# http://stackoverflow.com/questions/1622943/timeit-versus-timing-decorator
ts = time.time()
result = f(*args, **kw)
te = time.time()
print 'func:%r args:[%r, %r] took: %2.4f sec' % (f.__name__, args, kw, te-ts)
return result
return timed
@timeit
def worker():
initialized = getattr(threadLocal, 'initialized', None)
if initialized is None:
print "init start"
#TLS-Initialisierung
threadLocal.initialized = True
threadLocal.count = 0
threadLocal.prime = {}
return []
else:
print "loop:{}".format(threadLocal.count)
threadLocal.count += 1
return get_prime(random.randint(500000, 500010))
def get_prime(N):
"""
Geben Sie eine Liste mit Primzahlen zurück
:param N: int
:rtype : list of int
"""
#Wenn TLS Daten enthält, geben Sie diese aus dem Cache zurück
if N in threadLocal.prime:
return threadLocal.prime[N]
#Berechnen Sie eine Primzahl
table = list(range(N))
for i in range(2, int(N ** 0.5) + 1):
if table[i]:
for mult in range(i ** 2, N, i):
table[mult] = False
result = [p for p in table if p][1:]
#Ergebnisse in TLS aufzeichnen
threadLocal.prime[N] = result
return result
for x in xrange(100):
worker()
Ausführungsergebnis
>>>python tls.py
init start
func:'worker' args:[(), {}] took: 0.0000 sec
loop:0
func:'worker' args:[(), {}] took: 0.1715 sec
loop:1
func:'worker' args:[(), {}] took: 0.1862 sec
loop:2
func:'worker' args:[(), {}] took: 0.0000 sec
loop:3
func:'worker' args:[(), {}] took: 0.2403 sec
loop:4
func:'worker' args:[(), {}] took: 0.2669 sec
loop:5
func:'worker' args:[(), {}] took: 0.0001 sec
loop:6
func:'worker' args:[(), {}] took: 0.3130 sec
loop:7
func:'worker' args:[(), {}] took: 0.3456 sec
loop:8
func:'worker' args:[(), {}] took: 0.3224 sec
loop:9
func:'worker' args:[(), {}] took: 0.3208 sec
loop:10
func:'worker' args:[(), {}] took: 0.3196 sec
loop:11
func:'worker' args:[(), {}] took: 0.3282 sec
loop:12
func:'worker' args:[(), {}] took: 0.3257 sec
loop:13
func:'worker' args:[(), {}] took: 0.0000 sec
loop:14
func:'worker' args:[(), {}] took: 0.0000 sec
loop:15
func:'worker' args:[(), {}] took: 0.0000 sec
...
Der im TLS (Thread Local Storage) gespeicherte Cache wird für jeden untergeordneten Prozess von Apache gespeichert und bleibt bestehen, bis der untergeordnete Prozess beendet wird.
Durch die ordnungsgemäße Verwendung des Caches wird die Ausführungsgeschwindigkeit des Programms verbessert. Seien Sie jedoch vorsichtig, da es viele Fälle gibt, in denen Cache-spezifische Fehler auftreten, die als Nebenwirkungen bezeichnet werden. Wenn ich in der Vergangenheit etwas gesehen oder getan habe
■ Zeigen Sie den Fehler an, dass ein neuer Wert nicht erhalten werden kann, selbst wenn er aktualisiert wurde Dies ist ein Fehler, der auftritt, wenn Sie ihn verwenden, ohne das Design des Cache-Lebenszyklus zu kennen.
■ Fehler, dass Daten verschwinden Es ist ein tödlicher Kerl. 1. Werterfassung >> 2. In einem Programm, das den erfassten Wert addiert und den Wert aktualisiert, wird das Ergebnis des Werts 1 im Cache referenziert und nicht aktualisiert, z. B. 1234 + 100, 1234 + 200, 1234 Bei +50 gibt es einen Fehler, bei dem der Wert verschwindet.
■ So verhindern Sie Nebenwirkungen Jeder kann es sicher verwenden, indem er es wie einen "cached_property" -Dekorator verpackt und mit dem Cache aus einem gut getesteten Paket arbeitet. Sie können damit umgehen, ohne die Theorie zu kennen, aber wenn möglich, ist es besser, die Theorie über den Lebenszyklus des Caches zu kennen.
memo Das Veröffentlichungsdatum von line_profiler ist 2008
Recommended Posts