পাইথনে সাবক্লাসিং কেন জিনিসগুলিকে এত মন্থর করে?


13

আমি একটি সহজ বর্গ যে প্রসারিত করে কাজ ছিল 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


5
আমি উত্স কোডের সুনির্দিষ্ট অংশটি সন্ধান করতে পারছি না, তবে আমি বিশ্বাস করি যে সি বাস্তবায়নে একটি দ্রুত পথ রয়েছে যা পরীক্ষা করে যে বস্তুটি একটি dictএবং যদি তাই হয় তবে __getitem__পদ্ধতিটি সন্ধানের পরিবর্তে সরাসরি সি বাস্তবায়নকে কল করে অবজেক্টের ক্লাস সুতরাং আপনার কোডটি দু'দিকের অনুসন্ধান করে যা প্রথম '__getitem__'শ্রেণীর Aসদস্যদের অভিধানের চাবির জন্য , সুতরাং এটি প্রায় দ্বিগুণ হতে পারে বলে আশা করা যায়। pickleব্যাখ্যা সম্ভবত বেশ অনুরূপ।
কেয়া 3

@ কেয়া 3: তবে যদি এটি হয় তবে len()উদাহরণস্বরূপ, 2x ধীর গতির নয় তবে একই গতিও কেন?
মার্কো সুলা

আমি এ ব্যাপারে নিশ্চিত নই; আমার মনে lenহয়েছে বিল্ট-ইন সিকোয়েন্স প্রকারগুলির জন্য একটি দ্রুত পথ হওয়া উচিত। আমি মনে করি না যে আমি আপনার প্রশ্নের যথাযথ উত্তর দিতে সক্ষম হয়েছি, তবে এটি একটি ভাল উত্তর, তাই আশা করি আমার চেয়ে পাইথন ইন্টার্নাল সম্পর্কে আরও জ্ঞানী কেউ এর উত্তর দেবেন।
কেয়া 3

আমি কিছু তদন্ত করেছি এবং প্রশ্ন আপডেট করেছি।
মার্কো সুলা

1
...উহু. এখন বুঝতে পারছি. সুস্পষ্ট __contains__বাস্তবায়ন উত্তরাধিকার সূত্রে প্রাপ্ত যুক্তিটিকে অবরুদ্ধ করছে sq_contains
ব্যবহারকারী 2357112

উত্তর:


7

ইন্ডেক্স এবং inধীর হয় dictএকটি মধ্যে একটি খারাপ মিথস্ক্রিয়া কারণ উপশ্রেণী dictঅপ্টিমাইজেশান এবং যুক্তিবিজ্ঞান উপশ্রেণী উত্তরাধিকারী সি স্লট ব্যবহার করুন। এটি স্থিরযোগ্য হওয়া উচিত, যদিও আপনার শেষ থেকে নয়।

সিপিথন বাস্তবায়নে অপারেটর ওভারলোডগুলির জন্য দুটি সেট হুক রয়েছে। পাইথন-স্তরের পদ্ধতিগুলি রয়েছে __contains__এবং এর মতো __getitem__, তবে টাইপ অবজেক্টের মেমরি বিন্যাসে সি ফাংশন পয়েন্টারগুলির জন্য আলাদা আলাদা স্লট রয়েছে। সাধারণত, হয় পাইথন পদ্ধতিটি সি বাস্তবায়নের চারপাশে একটি মোড়ক হবে, বা সি স্লটে একটি ফাংশন থাকবে যা পাইথন পদ্ধতিটি অনুসন্ধান করে এবং কল করে। সি স্লটটির পক্ষে সরাসরি অপারেশনটি বাস্তবায়নের জন্য আরও দক্ষ, কারণ সি স্লটটি পাইথন আসলে অ্যাক্সেস করে।

সি-তে লিখিত ম্যাপিংগুলি সি স্লটগুলি সরবরাহ করে sq_containsএবং mp_subscriptসরবরাহ inএবং ইনডেক্সিং করে। সাধারণত, পাইথন-স্তর __contains__এবং __getitem__পদ্ধতিগুলি স্বয়ংক্রিয়ভাবে সি ফাংশনগুলির চারপাশে মোড়ক হিসাবে উত্পন্ন হবে, তবে dictশ্রেণীর স্পষ্ট বাস্তবায়ন রয়েছে __contains__এবং __getitem__কারণ স্পষ্টত বাস্তবায়ন উত্পন্ন র‌্যাপারগুলির চেয়ে কিছুটা দ্রুত are

static PyMethodDef mapp_methods[] = {
    DICT___CONTAINS___METHODDEF
    {"__getitem__", (PyCFunction)(void(*)(void))dict_subscript,        METH_O | METH_COEXIST,
     getitem__doc__},
    ...

(প্রকৃতপক্ষে, স্পষ্টভাবে __getitem__বাস্তবায়নটি বাস্তবায়নের মতো একই ফাংশন mp_subscript, কেবল একটি ভিন্ন ধরণের মোড়কের সাথে))

আইনগতভাবে, একটি উপশ্রেণী মত সি-স্তর আঙ্গুলসমূহ তার বাবা-মার বাস্তবায়নের উত্তরাধিকারী হবে sq_containsএবং mp_subscriptএবং উপশ্রেণী ঠিক যেমন ফাস্ট সুপারক্লাস যেমন হবে। যাইহোক, যুক্তি update_one_slotএকটি এমআরও অনুসন্ধানের মাধ্যমে উত্পাদিত মোড়কের পদ্ধতিগুলি অনুসন্ধান করার চেষ্টা করে পিতামাতার বাস্তবায়নের সন্ধান করে।

dictনা আছে জন্য উত্পন্ন চাদরে sq_containsএবং mp_subscript, কারণ এটি স্পষ্ট প্রদান করে __contains__এবং __getitem__বাস্তবায়নের।

পরিবর্তে উত্তরাধিকার সূত্রে প্রাপ্ত করতে sq_containsএবং mp_subscript, update_one_slotউপশ্রেণী দান শেষ পর্যন্ত sq_containsএবং mp_subscriptবাস্তবায়নের যে সঞ্চালন একটি ম্রো অনুসন্ধান __contains__এবং __getitem__সেই কল। এটি সরাসরি সি স্লট উত্তরাধিকার সূচনার চেয়ে অনেক কম দক্ষ।

এটি স্থির করতে update_one_slotপ্রয়োগের পরিবর্তনগুলি প্রয়োজন ।


কি আমি উপরে বর্ণিত ছাড়াও dict_subscriptএছাড়াও আপ দেখায় __missing__অভি উপশ্রেণী জন্য, তাই স্লট উত্তরাধিকার এই সমস্যাটি সমাধানের জন্য সাথে সম্পূর্ণরূপে সমাবস্থা উপর উপশ্রেণী করা হবে না dictলুকআপ গতির জন্য নিজেই, কিন্তু এটা তাদের অনেক কাছাকাছি পাওয়া উচিত।


পিকিংয়ের dumpsদিক হিসাবে, পার্কে, আচারের প্রয়োগটি ডিক্টসের জন্য একটি নিবেদিত দ্রুত গতিপথ রয়েছে, যখন ডিক সাবক্লাসটি আরও একটি রাউন্ডবাউথ পাথ নেয় object.__reduce_ex__এবং save_reduce

উপর loadsদিকে, সময় পার্থক্য শুধু অতিরিক্ত opcodes এবং লুক-পুনরুদ্ধার করতে এবং instantiate থেকে বেশিরভাগই হয় __main__.A, বর্গ যখন dicts একটি নতুন অভি তৈরীর জন্য একটি ডেডিকেটেড জরান opcode আছে। যদি আমরা আচারের জন্য বিচ্ছিন্নতা তুলনা করি:

In [26]: pickletools.dis(pickle.dumps({0: 0, 1: 1, 2: 2, 3: 3, 4: 4}))                                                                                                                                                           
    0: \x80 PROTO      4
    2: \x95 FRAME      25
   11: }    EMPTY_DICT
   12: \x94 MEMOIZE    (as 0)
   13: (    MARK
   14: K        BININT1    0
   16: K        BININT1    0
   18: K        BININT1    1
   20: K        BININT1    1
   22: K        BININT1    2
   24: K        BININT1    2
   26: K        BININT1    3
   28: K        BININT1    3
   30: K        BININT1    4
   32: K        BININT1    4
   34: u        SETITEMS   (MARK at 13)
   35: .    STOP
highest protocol among opcodes = 4

In [27]: pickletools.dis(pickle.dumps(A({0: 0, 1: 1, 2: 2, 3: 3, 4: 4})))                                                                                                                                                        
    0: \x80 PROTO      4
    2: \x95 FRAME      43
   11: \x8c SHORT_BINUNICODE '__main__'
   21: \x94 MEMOIZE    (as 0)
   22: \x8c SHORT_BINUNICODE 'A'
   25: \x94 MEMOIZE    (as 1)
   26: \x93 STACK_GLOBAL
   27: \x94 MEMOIZE    (as 2)
   28: )    EMPTY_TUPLE
   29: \x81 NEWOBJ
   30: \x94 MEMOIZE    (as 3)
   31: (    MARK
   32: K        BININT1    0
   34: K        BININT1    0
   36: K        BININT1    1
   38: K        BININT1    1
   40: K        BININT1    2
   42: K        BININT1    2
   44: K        BININT1    3
   46: K        BININT1    3
   48: K        BININT1    4
   50: K        BININT1    4
   52: u        SETITEMS   (MARK at 31)
   53: .    STOP
highest protocol among opcodes = 4

আমরা দেখতে পাচ্ছি যে এই দুটিয়ের মধ্যে পার্থক্য হ'ল দ্বিতীয় আচারটি দেখতে __main__.Aএবং এটি ইনস্ট্যান্ট করার জন্য পুরো গুচ্ছ অপকডের প্রয়োজন হয় , যখন প্রথম আচারটি EMPTY_DICTখালি ডিকটি পেতে কেবল করে । এর পরে, উভয়ই আচারগুলি একই কী এবং মানগুলিকে আচারের অপেন্ডেন্ড স্ট্যাকের দিকে চাপ দেয় এবং রান করে SETITEMS


আপনাকে অনেক ধন্যবাদ! সিপিথন কেন এই অদ্ভুত উত্তরাধিকার পদ্ধতিটি ব্যবহার করে সে সম্পর্কে আপনার ধারণা আছে? মানে, ঘোষণা করার কোনও উপায় নেই __contains__()এবং __getitem()এমন উপায়ে সাবক্লাস দ্বারা উত্তরাধিকার সূত্রে প্রাপ্ত হতে পারে? এর অফিসিয়াল ডকুমেন্টেশনে tp_methods, এটি এটি লেখা আছে methods are inherited through a different mechanism, সুতরাং এটি সম্ভব বলে মনে হচ্ছে।
মার্কো সুল্লা

@MarcoSulla: __contains__এবং __getitem__ করছে উত্তরাধিকারসূত্রে কিন্তু সমস্যা হল sq_containsএবং mp_subscriptনয়।
ব্যবহারকারী 2357112

হ্যাঁ, ভাল .... একটি মুহূর্ত অপেক্ষা করুন। আমি ভেবেছিলাম এটি বিপরীত ছিল। __contains__এবং __getitem__স্লটে রয়েছে tp_methodsযে সরকারী দস্তাবেজের জন্য সাবক্লাস দ্বারা উত্তরাধিকার সূত্রে প্রাপ্ত হয় না। এবং যেমনটি আপনি বলেছেন, update_one_slotব্যবহার করে না sq_containsএবং mp_subscript
মার্কো সুলা

কথায় কথায় কথায় কথায় কথায় কথায় বলা যায়, containsএবং বাকীগুলি কেবল অন্য স্লটে সরানো যায় না, যা সাবক্লাস দ্বারা উত্তরাধিকার সূত্রে প্রাপ্ত?
মার্কো সুল্লা

@ মার্কোসুল্লা: tp_methodsউত্তরাধিকার সূত্রে প্রাপ্ত নয়, তবে এ থেকে উত্পন্ন পাইথন পদ্ধতি অবজেক্টগুলি এই অর্থে উত্তরাধিকার সূত্রে প্রাপ্ত যে বৈশিষ্ট্য অ্যাক্সেসের জন্য মানক এমআরও অনুসন্ধানগুলি তাদের সন্ধান করবে them
ব্যবহারকারী 2357112
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.