আমি একটি সহজ বর্গ যে প্রসারিত করে কাজ ছিল 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
।
সম্পাদনা : পাইথন ৩.৯-এর উত্স কোডটি আমি একবার দেখেছিলাম এবং Objects/dictobject.c
মনে হয় যে __getitem__()
পদ্ধতিটি প্রয়োগ করা হয়েছে dict_subscript()
। এবং dict_subscript()
কেবল কীটি অনুপস্থিত থাকলেই সাবক্লাসগুলি ধীর করে দেয়, যেহেতু সাবক্লাসটি বাস্তবায়ন করতে পারে __missing__()
এবং এটি বিদ্যমান কিনা তা দেখার চেষ্টা করে। তবে মানদণ্ডটি একটি বিদ্যমান কী দিয়ে ছিল।
তবে আমি কিছু লক্ষ্য করেছি: __getitem__()
পতাকা দিয়ে সংজ্ঞায়িত করা হয়েছে METH_COEXIST
। এবং __contains__()
অন্যান্য পদ্ধতিতে 2x ধীর গতিতে একই পতাকা রয়েছে। থেকে সরকারী ডকুমেন্টেশন :
বিদ্যমান সংজ্ঞাগুলির জায়গায় পদ্ধতিটি লোড হবে। METH_COEXIS ছাড়া ডিফল্টটি পুনরাবৃত্তি সংজ্ঞাগুলি এড়িয়ে যাওয়া। যেহেতু স্লট চাদরে পদ্ধতি টেবিল সামনে লোড হয়, একটি sq_contains স্লট অস্তিত্ব, উদাহরণস্বরূপ, উৎপন্ন নামে একজন আবৃত পদ্ধতি হবে রয়েছে () এবং একই নামের একটি সংশ্লিষ্ট PyCFunction লোড প্রতিরোধ। পতাকা সংজ্ঞায়িত করার সাথে, পাইসিফিউশনটি মোড়কের বস্তুর জায়গায় লোড হবে এবং স্লটের সাথে সহাবস্থান করবে। এটি সহায়ক কারণ পাইসিফিউশনগুলিতে কলগুলি র্যাপার অবজেক্ট কলগুলির চেয়ে বেশি অনুকূলিত।
সুতরাং যদি আমি সঠিকভাবে বুঝতে পারি, তত্ত্বের মধ্যে METH_COEXIST
বিষয়গুলিকে গতি বাড়ানো উচিত, তবে এটির বিপরীত প্রভাব রয়েছে বলে মনে হয়। কেন?
সম্পাদনা 2 : আমি আরও কিছু আবিষ্কার করেছি।
__getitem__()
এবং __contains()__
হিসাবে পতাকাঙ্কিত করা হয় METH_COEXIST
, কারণ এগুলি দুটি বার পাইডিক্টটাইপে ঘোষণা করা হয় ।
তারা উভয় উপস্থিত, এক সময়, স্লটে 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
ব্যাখ্যা সম্ভবত বেশ অনুরূপ।