मैं एक साधारण वर्ग कि फैली हुई है पर काम कर रहा था dict
, और मुझे लगता है कि कुंजी देखने और उपयोग एहसास हुआ की pickle
हैं बहुत धीमी गति से।
मुझे लगा कि यह मेरी कक्षा के लिए एक समस्या है, इसलिए मैंने कुछ तुच्छ मानदंड किए:
(venv) marco@buzz:~/sources/python-frozendict/test$ python --version
Python 3.9.0a0
(venv) marco@buzz:~/sources/python-frozendict/test$ sudo pyperf system tune --affinity 3
[sudo] password for marco:
Tune the system configuration to run benchmarks
Actions
=======
CPU Frequency: Minimum frequency of CPU 3 set to the maximum frequency
System state
============
CPU: use 1 logical CPUs: 3
Perf event: Maximum sample rate: 1 per second
ASLR: Full randomization
Linux scheduler: No CPU is isolated
CPU Frequency: 0-3=min=max=2600 MHz
CPU scaling governor (intel_pstate): performance
Turbo Boost (intel_pstate): Turbo Boost disabled
IRQ affinity: irqbalance service: inactive
IRQ affinity: Default IRQ affinity: CPU 0-2
IRQ affinity: IRQ affinity: IRQ 0,2=CPU 0-3; IRQ 1,3-17,51,67,120-131=CPU 0-2
Power supply: the power cable is plugged
Advices
=======
Linux scheduler: Use isolcpus=<cpu list> kernel parameter to isolate CPUs
Linux scheduler: Use rcu_nocbs=<cpu list> kernel parameter (with isolcpus) to not schedule RCU on isolated CPUs
(venv) marco@buzz:~/sources/python-frozendict/test$ python -m pyperf timeit --rigorous --affinity 3 -s '
x = {0:0, 1:1, 2:2, 3:3, 4:4}
' 'x[4]'
.........................................
Mean +- std dev: 35.2 ns +- 1.8 ns
(venv) marco@buzz:~/sources/python-frozendict/test$ python -m pyperf timeit --rigorous --affinity 3 -s '
class A(dict):
pass
x = A({0:0, 1:1, 2:2, 3:3, 4:4})
' 'x[4]'
.........................................
Mean +- std dev: 60.1 ns +- 2.5 ns
(venv) marco@buzz:~/sources/python-frozendict/test$ python -m pyperf timeit --rigorous --affinity 3 -s '
x = {0:0, 1:1, 2:2, 3:3, 4:4}
' '5 in x'
.........................................
Mean +- std dev: 31.9 ns +- 1.4 ns
(venv) marco@buzz:~/sources/python-frozendict/test$ python -m pyperf timeit --rigorous --affinity 3 -s '
class A(dict):
pass
x = A({0:0, 1:1, 2:2, 3:3, 4:4})
' '5 in x'
.........................................
Mean +- std dev: 64.7 ns +- 5.4 ns
(venv) marco@buzz:~/sources/python-frozendict/test$ python
Python 3.9.0a0 (heads/master-dirty:d8ca2354ed, Oct 30 2019, 20:25:01)
[GCC 9.2.1 20190909] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from timeit import timeit
>>> class A(dict):
... def __reduce__(self):
... return (A, (dict(self), ))
...
>>> timeit("dumps(x)", """
... from pickle import dumps
... x = {0:0, 1:1, 2:2, 3:3, 4:4}
... """, number=10000000)
6.70694484282285
>>> timeit("dumps(x)", """
... from pickle import dumps
... x = A({0:0, 1:1, 2:2, 3:3, 4:4})
... """, number=10000000, globals={"A": A})
31.277778962627053
>>> timeit("loads(x)", """
... from pickle import dumps, loads
... x = dumps({0:0, 1:1, 2:2, 3:3, 4:4})
... """, number=10000000)
5.767975459806621
>>> timeit("loads(x)", """
... from pickle import dumps, loads
... x = dumps(A({0:0, 1:1, 2:2, 3:3, 4:4}))
... """, number=10000000, globals={"A": A})
22.611666693352163
परिणाम वास्तव में एक आश्चर्य की बात है। जबकि कुंजी देखने धीमी 2x है, pickle
है 5x धीमी।
यह कैसे हो सकता है? अन्य विधियाँ, जैसे get()
, __eq__()
और __init__()
, और पुनरावृति keys()
, values()
और items()
जैसे ही तेज़ हैं dict
।
EDIT : मैंने पायथन 3.9 के स्रोत कोड पर एक नज़र डाली, और Objects/dictobject.c
ऐसा लगता है कि __getitem__()
विधि द्वारा लागू किया गया है dict_subscript()
। और dict_subscript()
उपवर्गों को धीमा कर देता है यदि कुंजी गायब है, तो उपवर्ग लागू कर सकता है __missing__()
और यह देखने की कोशिश करता है कि क्या यह मौजूद है। लेकिन बेंचमार्क एक मौजूदा कुंजी के साथ था।
लेकिन मैंने कुछ देखा: __getitem__()
झंडे के साथ परिभाषित किया गया है METH_COEXIST
। और यह भी __contains__()
, दूसरी विधि जो 2x धीमी है, में एक ही झंडा है। से आधिकारिक दस्तावेज :
मौजूदा परिभाषाओं के स्थान पर विधि लोड की जाएगी। METH_COEXIST के बिना, डिफ़ॉल्ट दोहराई गई परिभाषाओं को छोड़ना है। चूँकि स्लॉट रैपर को विधि तालिका से पहले लोड किया जाता है, उदाहरण के लिए, sq_contains स्लॉट का अस्तित्व, इसमें लिपटे हुए विधि को उत्पन्न करेगा () और इसी नाम के साथ संबंधित PyCFunction के लोडिंग को रोक देगा। परिभाषित ध्वज के साथ, PyCFunction को रैपर ऑब्जेक्ट के स्थान पर लोड किया जाएगा और स्लॉट के साथ सह-अस्तित्व में होगा। यह मददगार है क्योंकि PyCFunctions के कॉल रैपर ऑब्जेक्ट कॉल से अधिक अनुकूलित हैं।
इसलिए अगर मुझे सही तरीके से समझ में आया है, तो सिद्धांत रूप में METH_COEXIST
चीजों को गति देना चाहिए, लेकिन इसका विपरीत प्रभाव पड़ता है। क्यों?
EDIT 2 : मैंने कुछ और खोज की।
__getitem__()
और के __contains()__
रूप में चिह्नित किए METH_COEXIST
जाते हैं, क्योंकि वे PyDict_Type में दो बार घोषित किए जाते हैं ।
वे दोनों मौजूद हैं, एक बार, स्लॉट में tp_methods
, जहां उन्हें स्पष्ट रूप से घोषित किया गया है __getitem__()
और __contains()__
। लेकिन आधिकारिक दस्तावेज का कहना है कि उपवर्गों को विरासत tp_methods
में नहीं मिला है।
तो एक उपवर्ग dict
कॉल नहीं करता है __getitem__()
, लेकिन उप- कॉल को कॉल करता है mp_subscript
। वास्तव mp_subscript
में, स्लॉट में निहित है tp_as_mapping
, जो एक उपवर्ग को अपने सबलेट्स को प्राप्त करने की अनुमति देता है।
समस्या यह है कि दोनों है __getitem__()
और mp_subscript
उपयोग करने के लिए एक ही , समारोह dict_subscript
। क्या यह संभव है कि यह केवल वह तरीका है जो विरासत में मिला था जो इसे धीमा कर देता है?
len()
उदाहरण के लिए, 2x धीमा क्यों नहीं है, लेकिन समान गति है?
len
अंतर्निहित अनुक्रम प्रकारों के लिए एक तेज़ पथ होना चाहिए। मुझे नहीं लगता कि मैं आपके प्रश्न का उचित उत्तर देने में सक्षम हूं, लेकिन यह एक अच्छा है, इसलिए उम्मीद है कि कोई और मेरे बारे में पायथन इंटर्न के बारे में अधिक जानकार होगा।
__contains__
कार्यान्वयन विरासत के लिए उपयोग किए जाने वाले तर्क को रोक रहा है sq_contains
।
dict
और यदि ऐसा है, तो__getitem__
विधि को देखने के बजाय सी कार्यान्वयन को सीधे कॉल करता है । वस्तु का वर्ग। इसलिए आपका कोड दो प्रतिष्ठित लुकअप करता है,'__getitem__'
वर्गA
के सदस्यों के शब्दकोश में कुंजी के लिए पहला एक है , इसलिए यह लगभग दो बार धीमा होने की उम्मीद की जा सकती है।pickle
विवरण शायद काफी समान है।