A.insert (0,0) কেন [0: 0] = [0] এর চেয়ে অনেক ধীর?


61

একটি তালিকা ব্যবহার করে insertস্লাইস অ্যাসাইনমেন্ট ব্যবহার করে একই প্রভাব অর্জনের চেয়ে ফাংশনটি করা খুব ধীর:

> python -m timeit -n 100000 -s "a=[]" "a.insert(0,0)"
100000 loops, best of 5: 19.2 usec per loop

> python -m timeit -n 100000 -s "a=[]" "a[0:0]=[0]"
100000 loops, best of 5: 6.78 usec per loop

(মনে রাখবেন যে a=[] এটি কেবলমাত্র সেটআপ, তাই aখালি শুরু হয় তবে তারপরে 100,000 উপাদানগুলিতে বেড়ে যায়))

প্রথমে আমি ভেবেছিলাম সম্ভবত এটি এট্রিবিউট লুকআপ বা ফাংশন কল ওভারহেড বা তাই, তবে শেষের নিকটে সন্নিবেশ করানো দেখায় যে এটি তুচ্ছ:

> python -m timeit -n 100000 -s "a=[]" "a.insert(-1,0)"
100000 loops, best of 5: 79.1 nsec per loop

কেন সম্ভবত সম্ভবত উত্সর্গীকৃত "একক উপাদান সন্নিবেশ" ফাংশন এত ধীর?

আমি এটি repl.it এ পুনরুত্পাদন করতে পারি :

from timeit import repeat

for _ in range(3):
  for stmt in 'a.insert(0,0)', 'a[0:0]=[0]', 'a.insert(-1,0)':
    t = min(repeat(stmt, 'a=[]', number=10**5))
    print('%.6f' % t, stmt)
  print()

# Example output:
#
# 4.803514 a.insert(0,0)
# 1.807832 a[0:0]=[0]
# 0.012533 a.insert(-1,0)
#
# 4.967313 a.insert(0,0)
# 1.821665 a[0:0]=[0]
# 0.012738 a.insert(-1,0)
#
# 5.694100 a.insert(0,0)
# 1.899940 a[0:0]=[0]
# 0.012664 a.insert(-1,0)

আমি উইন্ডোজ 10 64-বিটে পাইথন 3.8.1 32-বিট ব্যবহার করি।
repl.it লিনাক্স 64৪-বিটে পাইথন ৩.৮.১৪৪-বিট ব্যবহার করে।


আকর্ষণীয় যে a=[]; a[0:0]=[0]এটি একই কাজ করেa=[]; a[100:200]=[0]
smac89

আপনি খালি তালিকা দিয়ে এটি পরীক্ষা করার কোনও কারণ আছে কি?
মিস্টারমিয়াগি

@ মিস্টারমিয়াগি ঠিক আছে, আমাকে কিছু দিয়ে শুরু করতে হবে । মনে রাখবেন যে এটি প্রথম সন্নিবেশের আগেই খালি এবং বেঞ্চমার্কের সময় 100,000 উপাদানগুলিতে বেড়ে যায়।
গল ওভারফ্লো

@ smac89 a=[1,2,3];a[100:200]=[4]সংযোজন করা হয় 4তালিকার শেষে aআকর্ষণীয়।
Ch3steR

1
@ স্ম্যাক ৮৯ সত্য হলেও এটি প্রশ্নের সাথে আসলেই কিছু করার নেই এবং আমি আশঙ্কা করি যে এটি কাউকে ভেবে ভ্রান্ত করতে পারে যে আমি বেঞ্চমার্ক করছি a=[]; a[0:0]=[0]বা a[0:0]=[0]তার মতোই a[100:200]=[0]...
হিপ ওভারফ্লো

উত্তর:


57

আমার মনে হয় এটা সম্ভবত ঠিক যে তারা ব্যবহার করতে ভুলে গেছি memmoveমধ্যে list.insert। আপনি যদি উপাদানগুলিকে স্থানান্তর করতে কোডটি list.insert ব্যবহার করেন তবে আপনি এটি দেখতে পারেন এটি কেবল একটি ম্যানুয়াল লুপ:

for (i = n; --i >= where; )
    items[i+1] = items[i];

যখন list.__setitem__ফালি নিয়োগ পথে ব্যবহারসমূহmemmove :

memmove(&item[ihigh+d], &item[ihigh],
    (k - ihigh)*sizeof(PyObject *));

memmove সাধারণত এটিতে অনেকগুলি অপ্টিমাইজেশন থাকে যেমন এসএসই / এভিএক্স নির্দেশাবলীর সুবিধা নেওয়া।


5
ধন্যবাদ। এটি উল্লেখ করে একটি সমস্যা তৈরি করেছে ।
স্তূপ ওভারফ্লো 17

7
যদি -O3দোভাষীটি স্ব-ভেক্টরাইজেশন সক্ষম করে তৈরি করা হয়, তবে সেই ম্যানুয়াল লুপটি দক্ষতার সাথে সংকলন করতে পারে। তবে যতক্ষণ না সংকলক লুপটিকে একটি মেমোভ হিসাবে স্বীকৃতি দেয় এবং এটিকে একটি প্রকৃত কলটিতে সংকলন করে memmove, এটি কেবল সংকলনের সময় সক্ষম নির্দেশ-সেট এক্সটেনশনের সুবিধা নিতে পারে। (আপনি যদি নিজের সাথে নিজের -march=nativeতৈরি করেন তবে ভাল, বেসলাইন দিয়ে নির্মিত ডিস্ট্রো বাইনারিগুলির জন্য এত বেশি নয়)। এবং আপনি যদি পিজিও ( -fprofile-generate/ রান / ...-use) না ব্যবহার করেন তবে জিসিসি ডিফল্টরূপে লুপগুলি তালিকাভুক্ত করবে না
পিটার কর্ডেস

@ পিটারকর্ডস আমি কি আপনাকে সঠিকভাবে বুঝতে পারি যে সংকলক যদি এটি একটি আসল memmoveকলটিতে সংকলন করে , তবে তা কার্যকর করার সময় উপস্থিত সমস্ত বর্ধনের সুবিধা নিতে পারে?
2-2: 24

1
@ হিপওভারফ্লো: হ্যাঁ উদাহরণস্বরূপ, জিএনইউ / লিনাক্সে, গ্লিবসি ওভারলোডগুলি ডায়নামিক লিঙ্কার প্রতীক রেজোলিউশন একটি ফাংশন যা এই মেশিনের জন্য সংরক্ষিত সিপিইউ-সনাক্তকরণের ফলাফলের উপর ভিত্তি করে মেমোমোভের সেরা হাতের লিখিত এএসএম সংস্করণটিকে বেছে নিয়েছে। (উদাহরণস্বরূপ x86 এ, একটি গ্লিবসি আর ডি ফাংশন ব্যবহার করে cpuid)। অন্যান্য বেশ কয়েকটি স্মৃতি / ক্রিয়াকলাপের জন্য একই। সুতরাং ডিস্ট্রোস কেবল -O2রান-কোথাও বাইনারি তৈরির জন্য সংকলন করতে পারে তবে কমপক্ষে কমপক্ষে / মেমোমোভের জন্য নির্দেশনা অনুসারে একটি অনিবন্ধিত এভিএক্স লুপ লোডিং / স্টোর 32 বাইট ব্যবহার করুন। (অথবা এমন কয়েকটি সিপিইউগুলিতে এমনকি এভিএক্স 512 যেখানে এটি একটি ভাল ধারণা; আমি মনে করি কেবল জিয়ন ফাই।)
পিটার কর্ডেস

1
@ হিপ ওভারফ্লো: না, বেশ কয়েকটি memmoveসংস্করণ সেখানে ভাগ করা লাইব্রেরি libc.so এ বসে আছে। প্রতিটি ফাংশনের জন্য, প্রতীক রেজোলিউশন চলাকালীন একবার প্রেরণ ঘটে (প্রারম্ভিক বাঁধাই বা callতিহ্যবাহী অলস বাঁধার সাথে প্রথম কলটিতে)। যেমনটি আমি বলেছিলাম, এটি কেবল গতিবদ্ধ লিঙ্কিং কীভাবে ঘটে তা ওভারলোড / হুক করে না, ফাংশনটি নিজেই মোড়ানো দ্বারা। (বিশেষত জিসিসির আইফুনক প্রক্রিয়া: কোড . woboq.org/userspace/glibc/sysdeps/x86_64/multarch/… এর মাধ্যমে )। সম্পর্কিত: আধুনিক সিপিইউগুলিতে সাধারণ পছন্দটি স্মরণ করার জন্য __memset_avx2_unaligned_erms এই প্রশ্নোত্তরটি দেখুন
পিটার কর্ডেস
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.