Donald Knuth กล่าวว่า “ การเพิ่มประสิทธิภาพก่อนเวลาอันควรเป็นรากเหง้าของความชั่วร้ายทั้งหมด” แต่มีบางครั้งที่มักเกิดขึ้นในโครงการที่เติบโตเต็มที่ซึ่งมีภาระงานสูงเมื่อมีความจำเป็นที่จะต้องเพิ่มประสิทธิภาพอย่างหลีกเลี่ยงไม่ได้ ในบทความนี้ผมจะพูดถึง ห้าวิธีทั่วไปในการเพิ่มประสิทธิภาพโค้ดของโครงการเว็บของคุณ ฉันจะใช้ Django แต่หลักการควรคล้ายกันสำหรับเฟรมเวิร์กและภาษาอื่น ๆ ในบทความนี้ฉันจะใช้วิธีการเหล่านี้เพื่อลดเวลาตอบสนองของแบบสอบถามจาก 77 เหลือ 3.7 วินาที
โค้ดตัวอย่างนี้ดัดแปลงมาจากโปรเจ็กต์จริงที่ฉันได้ทำงานด้วยและแสดงให้เห็นถึงเทคนิคการเพิ่มประสิทธิภาพ ในกรณีที่คุณต้องการติดตามและดูผลลัพธ์ด้วยตัวเองคุณสามารถคว้ารหัสได้ในสถานะเริ่มต้นในวันที่ GitHub และทำการเปลี่ยนแปลงที่เกี่ยวข้องในขณะที่ติดตามบทความ ฉันจะใช้ Python 2 เนื่องจากแพ็คเกจของบุคคลที่สามบางแพ็คเกจยังไม่พร้อมใช้งานสำหรับ Python 3
โครงการเว็บของเราติดตามข้อเสนออสังหาริมทรัพย์ในแต่ละประเทศ ดังนั้นจึงมีเพียงสองรุ่น:
# houses/models.py from utils.hash import Hasher class HashableModel(models.Model): '''Provide a hash property for models.''' class Meta: abstract = True @property def hash(self): return Hasher.from_model(self) class Country(HashableModel): '''Represent a country in which the house is positioned.''' name = models.CharField(max_length=30) def __unicode__(self): return self.name class House(HashableModel): '''Represent a house with its characteristics.''' # Relations country = models.ForeignKey(Country, related_name='houses') # Attributes address = models.CharField(max_length=255) sq_meters = models.PositiveIntegerField() kitchen_sq_meters = models.PositiveSmallIntegerField() nr_bedrooms = models.PositiveSmallIntegerField() nr_bathrooms = models.PositiveSmallIntegerField() nr_floors = models.PositiveSmallIntegerField(default=1) year_built = models.PositiveIntegerField(null=True, blank=True) house_color_outside = models.CharField(max_length=20) distance_to_nearest_kindergarten = models.PositiveIntegerField(null=True, blank=True) distance_to_nearest_school = models.PositiveIntegerField(null=True, blank=True) distance_to_nearest_hospital = models.PositiveIntegerField(null=True, blank=True) has_cellar = models.BooleanField(default=False) has_pool = models.BooleanField(default=False) has_garage = models.BooleanField(default=False) price = models.PositiveIntegerField() def __unicode__(self): return '{} {}'.format(self.country, self.address)
บทคัดย่อ HashableModel
ให้โมเดลใด ๆ ที่สืบทอดจากมัน a hash
คุณสมบัติที่มีคีย์หลักของอินสแตนซ์และประเภทเนื้อหาของโมเดล ซึ่งจะซ่อนข้อมูลที่ละเอียดอ่อนเช่นรหัสอินสแตนซ์โดยแทนที่ด้วยแฮช นอกจากนี้ยังอาจมีประโยชน์ในกรณีที่โปรเจ็กต์ของคุณมีหลายโมเดลและคุณต้องการสถานที่ส่วนกลางที่เลิกแฮชและตัดสินใจว่าจะทำอย่างไรกับอินสแตนซ์โมเดลที่แตกต่างกันของคลาสต่างๆ โปรดทราบว่าสำหรับโปรเจ็กต์ขนาดเล็กของเราการแฮชไม่จำเป็นจริงๆเนื่องจากเราสามารถจัดการได้หากไม่มีมัน แต่จะช่วยสาธิตเทคนิคการเพิ่มประสิทธิภาพบางอย่างดังนั้นฉันจะเก็บไว้ที่นั่น
นี่คือ Hasher
ชั้น:
# utils/hash.py import basehash class Hasher(object): @classmethod def from_model(cls, obj, klass=None): if obj.pk is None: return None return cls.make_hash(obj.pk, klass if klass is not None else obj) @classmethod def make_hash(cls, object_pk, klass): base36 = basehash.base36() content_type = ContentType.objects.get_for_model(klass, for_concrete_model=False) return base36.hash('%(contenttype_pk)03d%(object_pk)06d' % { 'contenttype_pk': content_type.pk, 'object_pk': object_pk }) @classmethod def parse_hash(cls, obj_hash): base36 = basehash.base36() unhashed = '%09d' % base36.unhash(obj_hash) contenttype_pk = int(unhashed[:-6]) object_pk = int(unhashed[-6:]) return contenttype_pk, object_pk @classmethod def to_object_pk(cls, obj_hash): return cls.parse_hash(obj_hash)[1]
เนื่องจากเราต้องการให้บริการข้อมูลนี้ผ่านปลายทาง API เราจึงติดตั้ง Django REST Framework และกำหนด serializers และมุมมองต่อไปนี้:
# houses/serializers.py class HouseSerializer(serializers.ModelSerializer): '''Serialize a `houses.House` instance.''' id = serializers.ReadOnlyField(source='hash') country = serializers.ReadOnlyField(source='country.hash') class Meta: model = House fields = ( 'id', 'address', 'country', 'sq_meters', 'price' )
# houses/views.py class HouseListAPIView(ListAPIView): model = House serializer_class = HouseSerializer country = None def get_queryset(self): country = get_object_or_404(Country, pk=self.country) queryset = self.model.objects.filter(country=country) return queryset def list(self, request, *args, **kwargs): # Skipping validation code for brevity country = self.request.GET.get('country') self.country = Hasher.to_object_pk(country) queryset = self.get_queryset() serializer = self.serializer_class(queryset, many=True) return Response(serializer.data)
ตอนนี้เราเติมฐานข้อมูลของเราด้วยข้อมูลบางส่วน (อินสแตนซ์เฮาส์ 100,000 อินสแตนซ์ที่สร้างขึ้นโดยใช้ factory-boy
: 50,000 สำหรับหนึ่งประเทศ 40,000 สำหรับอีกประเทศหนึ่งและ 10,000 สำหรับประเทศที่สาม) และพร้อมที่จะทดสอบประสิทธิภาพของแอปของเรา .
มีหลายสิ่งที่เราสามารถวัดได้ในโครงการ:
แต่ไม่ใช่ทั้งหมดที่เกี่ยวข้องในการวัดประสิทธิภาพของโครงการของเรา โดยทั่วไปมีสองเมตริกหลักที่สำคัญที่สุด: ระยะเวลาดำเนินการบางอย่างและหน่วยความจำที่ต้องการ
javascript รับเวลาปัจจุบันในหน่วยมิลลิวินาที
ในโครงการเว็บ เวลาตอบสนอง (เวลาที่เซิร์ฟเวอร์ต้องใช้ในการรับคำขอที่สร้างขึ้นจากการกระทำของผู้ใช้บางรายประมวลผลและส่งผลลัพธ์กลับมา) มักจะเป็นเมตริกที่สำคัญที่สุดเนื่องจากจะไม่ทำให้ผู้ใช้รู้สึกเบื่อขณะรอการตอบกลับและเปลี่ยนไปใช้รายการอื่น แท็บในเบราว์เซอร์
ในการเขียนโปรแกรมเรียกว่าการวิเคราะห์ประสิทธิภาพของโครงการ การทำโปรไฟล์ . ในการกำหนดรายละเอียดประสิทธิภาพของปลายทาง API ของเราเราจะใช้ไฟล์ ผ้าไหม แพ็คเกจ หลังจากติดตั้งและสร้าง /api/v1/houses/?country=5T22RI
ของเรา โทร (แฮชที่ตรงกับประเทศที่มีรายการบ้าน 50,000 รายการ) เราจะได้รับสิ่งนี้:
200 รับ
/ api / v1 / บ้าน /
77292 มิลลิวินาทีโดยรวม
15854 มิลลิวินาทีในแบบสอบถาม
50004แบบสอบถาม
เวลาตอบสนองโดยรวมคือ 77 วินาทีซึ่งใช้เวลา 16 วินาทีในการสืบค้นข้อมูลในฐานข้อมูลซึ่งมีการสืบค้นทั้งหมด 50,000 รายการ ด้วยจำนวนที่มากเช่นนี้จึงมีช่องว่างมากมายสำหรับการปรับปรุงดังนั้นมาเริ่มกันเลย
หนึ่งในเคล็ดลับที่พบบ่อยที่สุดในการเพิ่มประสิทธิภาพคือการตรวจสอบให้แน่ใจว่าการสืบค้นฐานข้อมูลของคุณได้รับการปรับให้เหมาะสม กรณีนี้ไม่มีข้อยกเว้น ยิ่งไปกว่านั้นเราสามารถทำหลายสิ่งเกี่ยวกับคำถามของเราเพื่อเพิ่มประสิทธิภาพเวลาตอบสนอง
เมื่อพิจารณาอย่างละเอียดยิ่งขึ้นว่าคำค้นหา 50,000 รายการเหล่านี้คืออะไรคุณจะเห็นได้ว่าสิ่งเหล่านี้เป็นคำค้นหาที่ซ้ำซ้อนทั้งหมดของ houses_country
โต๊ะ:
200 รับ
/ api / v1 / บ้าน /
77292 มิลลิวินาทีโดยรวม
15854 มิลลิวินาทีในแบบสอบถาม
50004แบบสอบถาม
ที่ | ตาราง | เข้าร่วม | เวลาดำเนินการ (มิลลิวินาที) |
---|---|---|---|
+0: 01: 15.874374 | 'บ้าน _ คันทรี่' | 0 | 0.176 |
+0: 01: 15.873304 | 'บ้าน _ คันทรี่' | 0 | 0.218 |
+0: 01: 15.872225 | 'บ้าน _ คันทรี่' | 0 | 0.218 |
+0: 01: 15.871155 | 'บ้าน _ คันทรี่' | 0 | 0.198 |
+0: 01: 15.870099 | 'บ้าน _ คันทรี่' | 0 | 0.173 |
+0: 01: 15.869050 | 'บ้าน _ คันทรี่' | 0 | 0.197 |
+0: 01: 15.867877 | 'บ้าน _ คันทรี่' | 0 | 0.221 |
+0: 01: 15.866807 | 'บ้าน _ คันทรี่' | 0 | 0.203 |
+0: 01: 15.865646 | 'บ้าน _ คันทรี่' | 0 | 0.211 |
+0: 01: 15.864562 | 'บ้าน _ คันทรี่' | 0 | 0.209 |
+0: 01: 15.863511 | 'บ้าน _ คันทรี่' | 0 | 0.181 |
+0: 01: 15.862435 | 'บ้าน _ คันทรี่' | 0 | 0.228 |
+0: 01: 15.861413 | 'บ้าน _ คันทรี่' | 0 | 0.174 |
ที่มาของปัญหานี้คือความจริงที่ว่าใน Django ชุดแบบสอบถามคือ ขี้เกียจ . ซึ่งหมายความว่าชุดแบบสอบถามไม่ได้รับการประเมินและจะไม่เข้าสู่ฐานข้อมูลจนกว่าคุณจะต้องได้รับข้อมูลจริง ในขณะเดียวกันก็รับเฉพาะข้อมูลที่คุณแจ้งให้ส่งคำขอในภายหลังหากต้องการข้อมูลเพิ่มเติม
นั่นคือสิ่งที่เกิดขึ้นในกรณีของเรา เมื่อได้รับการตั้งค่าแบบสอบถามผ่าน House.objects.filter(country=country)
Django จะได้รับรายชื่อบ้านทั้งหมดในประเทศที่กำหนด อย่างไรก็ตามเมื่อทำให้เป็นอนุกรม house
อินสแตนซ์, HouseSerializer
ต้องใช้ country
อินสแตนซ์ของบ้านสำหรับการคำนวณ country
ของ serializer ฟิลด์ เนื่องจากไม่มีข้อมูลประเทศอยู่ในชุดข้อความค้นหา django จึงส่งคำขอเพิ่มเติมเพื่อรับข้อมูลดังกล่าว และสำหรับบ้านทุกหลังในชุดข้อความค้นหานั่นคือ 50,000 เท่า
วิธีแก้ปัญหานั้นง่ายมากแม้ว่า ในการดึงข้อมูลที่จำเป็นทั้งหมดสำหรับการทำให้เป็นอนุกรมคุณสามารถใช้ไฟล์ select_related()
วิธีการในชุดแบบสอบถาม ดังนั้น get_queryset
ของเรา จะมีลักษณะดังนี้:
def get_queryset(self): country = get_object_or_404(Country, pk=self.country) queryset = self.model.objects.filter(country=country).select_related('country') return queryset
มาดูกันว่าสิ่งนี้ส่งผลต่อประสิทธิภาพอย่างไร:
200 รับ
/ api / v1 / บ้าน /
35979 มิลลิวินาทีโดยรวม
102 มิลลิวินาทีในแบบสอบถาม
4แบบสอบถาม
เวลาตอบสนองโดยรวมลดลงเหลือ 36 วินาทีและเวลาที่ใช้ในฐานข้อมูลคือ ~ 100ms โดยมีเพียง 4 คำค้นหาเท่านั้น! ถือเป็นข่าวดี แต่เราสามารถทำอะไรได้มากกว่านี้
ตามค่าเริ่มต้น Django จะแยกฟิลด์ทั้งหมดออกจากฐานข้อมูล อย่างไรก็ตามเมื่อคุณมีตารางขนาดใหญ่ที่มีคอลัมน์และแถวจำนวนมากคุณควรบอก Django ว่าจะแยกช่องใดเพื่อที่จะได้ไม่ต้องเสียเวลาไปกับการรับข้อมูลที่จะไม่นำไปใช้เลย ในกรณีของเราเราต้องการเพียงห้าฟิลด์สำหรับการทำให้เป็นอนุกรม แต่เรามี 17 ฟิลด์ การระบุฟิลด์ที่จะดึงออกจากฐานข้อมูลเป็นเรื่องที่สมเหตุสมผลเพื่อที่เราจะได้ลดเวลาตอบสนองลง
Django มีไฟล์ defer()
และ only()
วิธีการตั้งค่าแบบสอบถามสำหรับการทำสิ่งนี้ อันแรกระบุฟิลด์อะไร ไม่ เพื่อโหลดและช่องที่สองระบุว่าจะโหลดช่องใด เท่านั้น .
def get_queryset(self): country = get_object_or_404(Country, pk=self.country) queryset = self.model.objects.filter(country=country) .select_related('country') .only('id', 'address', 'country', 'sq_meters', 'price') return queryset
วิธีนี้ลดเวลาที่ใช้ในการสืบค้นลงครึ่งหนึ่งซึ่งเป็นสิ่งที่ดี แต่ 50ms นั้นไม่มากนัก เวลาโดยรวมลดลงเล็กน้อยเช่นกัน แต่มีพื้นที่ให้ตัดได้มากขึ้น
200 รับ
/ api / v1 / บ้าน /
33111 มิลลิวินาทีโดยรวม
52 มิลลิวินาทีในแบบสอบถาม
4แบบสอบถาม
คุณไม่สามารถเพิ่มประสิทธิภาพการสืบค้นฐานข้อมูลได้อย่างไม่มีที่สิ้นสุดและผลลัพธ์สุดท้ายของเราก็แสดงให้เห็นว่า แม้ว่าเราจะลดเวลาที่ใช้ในการสืบค้นลงโดยสมมุติฐานเป็น 0 แต่เราก็ยังคงต้องเผชิญกับความเป็นจริงของการรอครึ่งนาทีเพื่อรับคำตอบ ถึงเวลาเปลี่ยนไปใช้การเพิ่มประสิทธิภาพอีกระดับ: ตรรกะทางธุรกิจ .
บางครั้งแพ็คเกจของบุคคลที่สามมักมีค่าใช้จ่ายจำนวนมากสำหรับงานง่ายๆ ตัวอย่างหนึ่งคือภารกิจของเราในการส่งคืนอินสแตนซ์เฮาส์แบบอนุกรม
Django REST Framework นั้นยอดเยี่ยมพร้อมคุณสมบัติที่มีประโยชน์มากมายนอกกรอบ อย่างไรก็ตามเป้าหมายหลักของเราในตอนนี้คือการลดเวลาในการตอบสนองดังนั้นจึงเป็นตัวเลือกที่ยอดเยี่ยมสำหรับการเพิ่มประสิทธิภาพโดยเฉพาะอย่างยิ่งวัตถุที่ทำให้เป็นอนุกรมนั้นค่อนข้างง่าย
มาเขียน serializer ที่กำหนดเองเพื่อจุดประสงค์นี้ เพื่อให้ง่ายเราจะมีวิธีการคงที่เดียวที่ได้ผล ในความเป็นจริงคุณอาจต้องการมีลายเซ็นคลาสและวิธีการเดียวกันเพื่อให้สามารถใช้ serializers แทนกันได้:
# houses/serializers.py class HousePlainSerializer(object): ''' Serializes a House queryset consisting of dicts with the following keys: 'id', 'address', 'country', 'sq_meters', 'price'. ''' @staticmethod def serialize_data(queryset): ''' Return a list of hashed objects from the given queryset. ''' return [ { 'id': Hasher.from_pk_and_class(entry['id'], House), 'address': entry['address'], 'country': Hasher.from_pk_and_class(entry['country'], Country), 'sq_meters': entry['sq_meters'], 'price': entry['price'] } for entry in queryset ] # houses/views.py class HouseListAPIView(ListAPIView): model = House serializer_class = HouseSerializer plain_serializer_class = HousePlainSerializer # <-- added custom serializer country = None def get_queryset(self): country = get_object_or_404(Country, pk=self.country) queryset = self.model.objects.filter(country=country) return queryset def list(self, request, *args, **kwargs): # Skipping validation code for brevity country = self.request.GET.get('country') self.country = Hasher.to_object_pk(country) queryset = self.get_queryset() data = self.plain_serializer_class.serialize_data(queryset) # <-- serialize return Response(data)
200 รับ
/ api / v1 / บ้าน /
17312 มิลลิวินาทีโดยรวม
38 มิลลิวินาทีในแบบสอบถาม
4แบบสอบถาม
ตอนนี้ดูดีขึ้น เวลาในการตอบกลับลดลงเกือบครึ่งหนึ่งเนื่องจากเราไม่ได้ใช้โค้ด DRF serializers
ผลลัพธ์ที่วัดได้อีกอย่างหนึ่งคือจำนวนการเรียกใช้ฟังก์ชันทั้งหมดที่เกิดขึ้นระหว่างรอบการร้องขอ / การตอบกลับลดลงจาก 15,859,427 การโทร (จากคำขอในหัวข้อ 1.2 ด้านบน) เหลือ 9,257,469 สาย ซึ่งหมายความว่ามีการเรียกใช้ฟังก์ชันประมาณ 1/3 ของ Django REST Framework
เทคนิคการเพิ่มประสิทธิภาพที่อธิบายไว้ข้างต้นเป็นเทคนิคที่พบบ่อยที่สุดซึ่งคุณสามารถทำได้โดยไม่ต้องวิเคราะห์และไตร่ตรองอย่างถี่ถ้วน อย่างไรก็ตาม 17 วินาทียังคงค่อนข้างนาน ในการลดจำนวนนี้เราจะต้องเจาะลึกลงไปในโค้ดของเราและวิเคราะห์สิ่งที่เกิดขึ้นภายใต้ประทุน กล่าวอีกนัยหนึ่งเราจะต้องกำหนดโปรไฟล์รหัสของเรา
ข้อใดต่อไปนี้เป็นจริงของการซ้อนคอนโทรลภายในเลเบล
คุณสามารถสร้างโปรไฟล์ด้วยตัวเองโดยใช้ Python profiler ในตัวหรือคุณสามารถใช้แพ็คเกจของบุคคลที่สามได้ (ซึ่งใช้ Python profiler ในตัว) ในขณะที่เราใช้ silk
มันสามารถสร้างโปรไฟล์รหัสและสร้างไฟล์โปรไฟล์ไบนารีซึ่งเราสามารถมองเห็นภาพเพิ่มเติมได้ มีแพ็คเกจการแสดงภาพหลายแบบที่เปลี่ยนโปรไฟล์ไบนารีเป็นการแสดงภาพเชิงลึกบางอย่าง ฉันจะใช้ไฟล์ snakeviz
แพ็คเกจ
นี่คือภาพของโปรไฟล์ไบนารีของคำขอสุดท้ายจากด้านบนที่เชื่อมโยงกับวิธีการจัดส่งของมุมมอง:
จากบนลงล่างคือ call stack ที่แสดงชื่อไฟล์ชื่อเมธอด / ฟังก์ชันพร้อมหมายเลขบรรทัดและเวลาสะสมที่เกี่ยวข้องที่ใช้ในเมธอดนั้น ตอนนี้มันง่ายกว่าที่จะเห็นว่าส่วนแบ่งเวลาของสิงโตทุ่มเทให้กับการคำนวณแฮช (__init__.py
และ primes.py
สี่เหลี่ยมสีม่วง)
ปัจจุบันนี่เป็นปัญหาคอขวดด้านประสิทธิภาพหลักในโค้ดของเรา แต่ในขณะเดียวกันก็ไม่ได้เป็นเช่นนั้น ของเรา รหัส - เป็นแพ็คเกจของบุคคลที่สาม
เพิ่มตัวฟังเหตุการณ์การคลิกบนปุ่ม ส่งฟังก์ชันว่างเป็นพารามิเตอร์ที่สอง
ในสถานการณ์เช่นนี้มีบางสิ่งที่เราสามารถทำได้:
โชคดีสำหรับฉันมี basehash
เวอร์ชันใหม่กว่า แพคเกจที่รับผิดชอบในการแฮช รหัสใช้ v.2.1.0 แต่มี v.3.0.4 สถานการณ์เช่นนี้เมื่อคุณสามารถอัปเดตแพ็กเกจเป็นเวอร์ชันที่ใหม่กว่ามีความเป็นไปได้มากขึ้นเมื่อคุณกำลังทำงานกับโปรเจ็กต์ที่มีอยู่
เมื่อตรวจสอบบันทึกประจำรุ่นสำหรับ v.3 มีประโยคเฉพาะนี้ที่ฟังดูมีแนวโน้มมาก:
การยกเครื่องครั้งใหญ่ทำด้วยอัลกอริทึมดั้งเดิม รวมถึงการสนับสนุน (sic) สำหรับ gmpy2 หากมี (sic) ในระบบสำหรับการเพิ่มขึ้นอีกมาก
มาหาคำตอบกัน!
pip install -U basehash gmpy2
200 รับ
/ api / v1 / บ้าน /
7738 มิลลิวินาทีโดยรวม
59 มิลลิวินาทีในแบบสอบถาม
4แบบสอบถาม
เราลดเวลาตอบสนองจาก 17 เหลือน้อยกว่า 8 วินาที ผลลัพธ์ที่ยอดเยี่ยม แต่ยังมีอีกสิ่งหนึ่งที่เราควรพิจารณา
จนถึงตอนนี้เราได้ปรับปรุงการสืบค้นของเราแทนที่รหัสที่ซับซ้อนและรหัสทั่วไปของบุคคลที่สามด้วยฟังก์ชันเฉพาะของเราเองและอัปเดตแพ็คเกจของบุคคลที่สาม แต่เราไม่ได้แตะต้องรหัสที่มีอยู่ของเรา แต่บางครั้งการปรับโครงสร้างโค้ดที่มีอยู่เพียงเล็กน้อยก็สามารถให้ผลลัพธ์ที่น่าประทับใจได้ แต่สำหรับสิ่งนี้เราต้องวิเคราะห์ผลการทำโปรไฟล์อีกครั้ง
เมื่อพิจารณาอย่างละเอียดคุณจะเห็นว่าการแฮชยังคงเป็นปัญหาอยู่ (ไม่น่าแปลกใจที่เราทำกับข้อมูลของเราเพียงอย่างเดียว) แม้ว่าเราจะปรับปรุงไปในทิศทางนั้นก็ตาม อย่างไรก็ตามสี่เหลี่ยมสีเขียวที่ระบุว่า __init__.py
กินเวลา 2.14 วินาทีรบกวนฉันพร้อมกับสีเทา __init__.py:54(hash)
ที่ตามมาทันที ซึ่งหมายความว่าการเริ่มต้นบางอย่างใช้เวลานาน
มาดูซอร์สโค้ดของ basehash
แพ็คเกจ
# basehash/__init__.py # Initialization of `base36` class initializes the parent, `base` class. class base36(base): def __init__(self, length=HASH_LENGTH, generator=GENERATOR): super(base36, self).__init__(BASE36, length, generator) class base(object): def __init__(self, alphabet, length=HASH_LENGTH, generator=GENERATOR): if len(set(alphabet)) != len(alphabet): raise ValueError('Supplied alphabet cannot contain duplicates.') self.alphabet = tuple(alphabet) self.base = len(alphabet) self.length = length self.generator = generator self.maximum = self.base ** self.length - 1 self.prime = next_prime(int((self.maximum + 1) * self.generator)) # `next_prime` call on each initialized instance
อย่างที่คุณเห็นการเริ่มต้นของ base
อินสแตนซ์ต้องการการเรียกของ next_prime
ฟังก์ชัน; ซึ่งค่อนข้างหนักอย่างที่เราเห็นในรูปสี่เหลี่ยมด้านล่างซ้ายของภาพด้านบน
มาดู Hash
ของฉัน ชั้นเรียนอีกครั้ง:
class Hasher(object): @classmethod def from_model(cls, obj, klass=None): if obj.pk is None: return None return cls.make_hash(obj.pk, klass if klass is not None else obj) @classmethod def make_hash(cls, object_pk, klass): base36 = basehash.base36() # <-- initializing on each method call content_type = ContentType.objects.get_for_model(klass, for_concrete_model=False) return base36.hash('%(contenttype_pk)03d%(object_pk)06d' % { 'contenttype_pk': content_type.pk, 'object_pk': object_pk }) @classmethod def parse_hash(cls, obj_hash): base36 = basehash.base36() # <-- initializing on each method call unhashed = '%09d' % base36.unhash(obj_hash) contenttype_pk = int(unhashed[:-6]) object_pk = int(unhashed[-6:]) return contenttype_pk, object_pk @classmethod def to_object_pk(cls, obj_hash): return cls.parse_hash(obj_hash)[1]
อย่างที่คุณเห็นฉันได้ระบุสองวิธีที่กำลังเริ่มต้น base36
อินสแตนซ์ในการเรียกแต่ละวิธีซึ่งไม่จำเป็นจริงๆ
เนื่องจากการแฮชเป็นขั้นตอนการกำหนดซึ่งหมายความว่าสำหรับค่าอินพุตที่กำหนดนั้นจะต้องสร้างค่าแฮชที่เหมือนกันเสมอเราจึงสามารถทำให้เป็นแอตทริบิวต์คลาสได้โดยไม่ต้องกลัวว่าจะทำให้บางอย่างแตกหัก มาดูกันว่าจะทำงานอย่างไร:
class Hasher(object): base36 = basehash.base36() # <-- initialize hasher only once @classmethod def from_model(cls, obj, klass=None): if obj.pk is None: return None return cls.make_hash(obj.pk, klass if klass is not None else obj) @classmethod def make_hash(cls, object_pk, klass): content_type = ContentType.objects.get_for_model(klass, for_concrete_model=False) return cls.base36.hash('%(contenttype_pk)03d%(object_pk)06d' % { 'contenttype_pk': content_type.pk, 'object_pk': object_pk }) @classmethod def parse_hash(cls, obj_hash): unhashed = '%09d' % cls.base36.unhash(obj_hash) contenttype_pk = int(unhashed[:-6]) object_pk = int(unhashed[-6:]) return contenttype_pk, object_pk @classmethod def to_object_pk(cls, obj_hash): return cls.parse_hash(obj_hash)[1]
200 รับ
/ api / v1 / บ้าน /
3766 มิลลิวินาทีโดยรวม
38 มิลลิวินาทีในแบบสอบถาม
4แบบสอบถาม
ผลลัพธ์สุดท้ายคือไม่เกินสี่วินาทีซึ่งน้อยกว่าที่เราเริ่มต้นมาก การเพิ่มประสิทธิภาพของเวลาตอบสนองเพิ่มเติมสามารถทำได้โดยใช้การแคช แต่ฉันจะไม่จัดการกับมันในบทความนี้
การเพิ่มประสิทธิภาพการทำงานเป็นกระบวนการวิเคราะห์และค้นพบ ไม่มีกฎเกณฑ์ที่ยากที่ใช้กับทุกกรณีเนื่องจากแต่ละโครงการมีโฟลว์และคอขวดของตัวเอง อย่างไรก็ตามสิ่งแรกที่คุณควรทำคือสร้างโปรไฟล์รหัสของคุณ และถ้าในตัวอย่างสั้น ๆ เช่นนี้ฉันสามารถลดเวลาตอบสนองจาก 77 วินาทีเหลือ 3.7 วินาทีโครงการขนาดใหญ่ก็มีศักยภาพในการเพิ่มประสิทธิภาพมากขึ้น
หากคุณสนใจอ่านบทความที่เกี่ยวข้องกับ Django เพิ่มเติมโปรดดู ข้อผิดพลาด 10 อันดับแรกที่นักพัฒนา Django ทำ โดยเพื่อน ApeeScape นักพัฒนา Django Alexandr Shurigin