কি ঘটতে পারে বা কী ঘটতে পারে তা নিয়ে অনুমান করার পরিবর্তে আসুন আমরা কি দেখব? আমি সি ++ ব্যবহার করতে যেহেতু আমি একটি সি # কম্পাইলার কুশলী হবে না (যদিও থাকবে C # এর উদাহরণ দেখুন থেকে VisualMelon ), কিন্তু আমি নিশ্চিত একই নীতির নির্বিশেষে প্রযোজ্য নই।
সাক্ষাত্কারে আপনার দুটি মুখোমুখি বিকল্প আমরা অন্তর্ভুক্ত করব। আমরা এমন একটি সংস্করণ অন্তর্ভুক্ত করব abs
যা উত্তরগুলির কয়েকটি দ্বারা প্রস্তাবিত হিসাবে ব্যবহার করে।
#include <cstdlib>
bool IsSumInRangeWithVar(int a, int b)
{
int s = a + b;
if (s > 1000 || s < -1000) return false;
else return true;
}
bool IsSumInRangeWithoutVar(int a, int b)
{
if (a + b > 1000 || a + b < -1000) return false;
else return true;
}
bool IsSumInRangeSuperOptimized(int a, int b) {
return (abs(a + b) < 1000);
}
এখন এটি কোনও অপ্টিমাইজেশন ছাড়াই সংকলন করুন: g++ -c -o test.o test.cpp
এখন আমরা সুনির্দিষ্টভাবে দেখতে পাচ্ছি এটি কী উত্পন্ন করে: objdump -d test.o
0000000000000000 <_Z19IsSumInRangeWithVarii>:
0: 55 push %rbp # begin a call frame
1: 48 89 e5 mov %rsp,%rbp
4: 89 7d ec mov %edi,-0x14(%rbp) # save first argument (a) on stack
7: 89 75 e8 mov %esi,-0x18(%rbp) # save b on stack
a: 8b 55 ec mov -0x14(%rbp),%edx # load a and b into edx
d: 8b 45 e8 mov -0x18(%rbp),%eax # load b into eax
10: 01 d0 add %edx,%eax # add a and b
12: 89 45 fc mov %eax,-0x4(%rbp) # save result as s on stack
15: 81 7d fc e8 03 00 00 cmpl $0x3e8,-0x4(%rbp) # compare s to 1000
1c: 7f 09 jg 27 # jump to 27 if it's greater
1e: 81 7d fc 18 fc ff ff cmpl $0xfffffc18,-0x4(%rbp) # compare s to -1000
25: 7d 07 jge 2e # jump to 2e if it's greater or equal
27: b8 00 00 00 00 mov $0x0,%eax # put 0 (false) in eax, which will be the return value
2c: eb 05 jmp 33 <_Z19IsSumInRangeWithVarii+0x33>
2e: b8 01 00 00 00 mov $0x1,%eax # put 1 (true) in eax
33: 5d pop %rbp
34: c3 retq
0000000000000035 <_Z22IsSumInRangeWithoutVarii>:
35: 55 push %rbp
36: 48 89 e5 mov %rsp,%rbp
39: 89 7d fc mov %edi,-0x4(%rbp)
3c: 89 75 f8 mov %esi,-0x8(%rbp)
3f: 8b 55 fc mov -0x4(%rbp),%edx
42: 8b 45 f8 mov -0x8(%rbp),%eax # same as before
45: 01 d0 add %edx,%eax
# note: unlike other implementation, result is not saved
47: 3d e8 03 00 00 cmp $0x3e8,%eax # compare to 1000
4c: 7f 0f jg 5d <_Z22IsSumInRangeWithoutVarii+0x28>
4e: 8b 55 fc mov -0x4(%rbp),%edx # since s wasn't saved, load a and b from the stack again
51: 8b 45 f8 mov -0x8(%rbp),%eax
54: 01 d0 add %edx,%eax
56: 3d 18 fc ff ff cmp $0xfffffc18,%eax # compare to -1000
5b: 7d 07 jge 64 <_Z22IsSumInRangeWithoutVarii+0x2f>
5d: b8 00 00 00 00 mov $0x0,%eax
62: eb 05 jmp 69 <_Z22IsSumInRangeWithoutVarii+0x34>
64: b8 01 00 00 00 mov $0x1,%eax
69: 5d pop %rbp
6a: c3 retq
000000000000006b <_Z26IsSumInRangeSuperOptimizedii>:
6b: 55 push %rbp
6c: 48 89 e5 mov %rsp,%rbp
6f: 89 7d fc mov %edi,-0x4(%rbp)
72: 89 75 f8 mov %esi,-0x8(%rbp)
75: 8b 55 fc mov -0x4(%rbp),%edx
78: 8b 45 f8 mov -0x8(%rbp),%eax
7b: 01 d0 add %edx,%eax
7d: 3d 18 fc ff ff cmp $0xfffffc18,%eax
82: 7c 16 jl 9a <_Z26IsSumInRangeSuperOptimizedii+0x2f>
84: 8b 55 fc mov -0x4(%rbp),%edx
87: 8b 45 f8 mov -0x8(%rbp),%eax
8a: 01 d0 add %edx,%eax
8c: 3d e8 03 00 00 cmp $0x3e8,%eax
91: 7f 07 jg 9a <_Z26IsSumInRangeSuperOptimizedii+0x2f>
93: b8 01 00 00 00 mov $0x1,%eax
98: eb 05 jmp 9f <_Z26IsSumInRangeSuperOptimizedii+0x34>
9a: b8 00 00 00 00 mov $0x0,%eax
9f: 5d pop %rbp
a0: c3 retq
আমরা স্ট্যাকের ঠিকানাগুলি থেকে দেখতে পারি (উদাহরণস্বরূপ, -0x4
ইন mov %edi,-0x4(%rbp)
বনাম -0x14
ইন mov %edi,-0x14(%rbp)
) IsSumInRangeWithVar()
স্ট্যাকের মধ্যে 16 টি অতিরিক্ত বাইট ব্যবহার করে।
কারণ IsSumInRangeWithoutVar()
মধ্যবর্তী মান s
এটি পুনরায় গণনা করতে হয় তা সঞ্চয় করার জন্য স্ট্যাকের জন্য কোনও স্থান বরাদ্দ করে না , ফলস্বরূপ এই বাস্তবায়নটি 2 টি নির্দেশিকা আর দীর্ঘায়িত হয়।
মজার, IsSumInRangeSuperOptimized()
দেখতে IsSumInRangeWithoutVar()
এটি দেখতে অনেকটা ভালো লাগে , বাদে এটি প্রথম -1000 এবং 1000 সেকেন্ডের সাথে তুলনা করে।
এখন কেবল সবচেয়ে মৌলিক অপ্টিমাইজেশন সঙ্গে কম্পাইল যাক: g++ -O1 -c -o test.o test.cpp
। ফলাফল:
0000000000000000 <_Z19IsSumInRangeWithVarii>:
0: 8d 84 37 e8 03 00 00 lea 0x3e8(%rdi,%rsi,1),%eax
7: 3d d0 07 00 00 cmp $0x7d0,%eax
c: 0f 96 c0 setbe %al
f: c3 retq
0000000000000010 <_Z22IsSumInRangeWithoutVarii>:
10: 8d 84 37 e8 03 00 00 lea 0x3e8(%rdi,%rsi,1),%eax
17: 3d d0 07 00 00 cmp $0x7d0,%eax
1c: 0f 96 c0 setbe %al
1f: c3 retq
0000000000000020 <_Z26IsSumInRangeSuperOptimizedii>:
20: 8d 84 37 e8 03 00 00 lea 0x3e8(%rdi,%rsi,1),%eax
27: 3d d0 07 00 00 cmp $0x7d0,%eax
2c: 0f 96 c0 setbe %al
2f: c3 retq
আপনি কি এটি তাকান: প্রতিটি বৈকল্পিক অভিন্ন । সংকলকটি বেশ চতুর কিছু করতে সক্ষম: একটি স্বাক্ষরযুক্ত তুলনা abs(a + b) <= 1000
করা a + b + 1000 <= 2000
বিবেচনার সমতুল্য setbe
, তাই একটি নেতিবাচক সংখ্যাটি খুব বড় ধনাত্মক সংখ্যায় পরিণত হয়। lea
নির্দেশ আসলে এক নির্দেশ মধ্যে এই সব সংযোজন সঞ্চালন, এবং সমস্ত শর্তাধীন শাখা বাদ দিতে পারে।
আপনার প্রশ্নের উত্তরের জন্য, প্রায়শই সর্বদা অপ্টিমাইজ করার জিনিসটি স্মৃতি বা গতি নয়, তবে পঠনযোগ্যতা । পড়ার কোডটি লেখার চেয়ে অনেক বেশি শক্ত এবং যে কোডটি "অনুকূলিত করতে" মঙ্গল করা হয়েছে সেটি পড়ার কোডের চেয়ে অনেকটা শক্ত যা স্পষ্ট করে লেখা হয়েছে। প্রায়শই না, এই "অপ্টিমাইজেশানগুলি" তুচ্ছ বা এই ক্ষেত্রে যেমন পারফরম্যান্সের উপর সত্যিকারের প্রভাব শূন্য ।
প্রশ্ন অনুসরণ করুন, এই কোডটি সংকলনের পরিবর্তে কোনও অনুবাদিত ভাষায় আসলে কী পরিবর্তন হবে? তারপরে, অপ্টিমাইজেশনের বিষয়টি কী গুরুত্বপূর্ণ বা এর একই ফল রয়েছে?
আসুন পরিমাপ করা যাক! আমি উদাহরণগুলি পাইথনে প্রতিলিপি করেছি:
def IsSumInRangeWithVar(a, b):
s = a + b
if s > 1000 or s < -1000:
return False
else:
return True
def IsSumInRangeWithoutVar(a, b):
if a + b > 1000 or a + b < -1000:
return False
else:
return True
def IsSumInRangeSuperOptimized(a, b):
return abs(a + b) <= 1000
from dis import dis
print('IsSumInRangeWithVar')
dis(IsSumInRangeWithVar)
print('\nIsSumInRangeWithoutVar')
dis(IsSumInRangeWithoutVar)
print('\nIsSumInRangeSuperOptimized')
dis(IsSumInRangeSuperOptimized)
print('\nBenchmarking')
import timeit
print('IsSumInRangeWithVar: %fs' % (min(timeit.repeat(lambda: IsSumInRangeWithVar(42, 42), repeat=50, number=100000)),))
print('IsSumInRangeWithoutVar: %fs' % (min(timeit.repeat(lambda: IsSumInRangeWithoutVar(42, 42), repeat=50, number=100000)),))
print('IsSumInRangeSuperOptimized: %fs' % (min(timeit.repeat(lambda: IsSumInRangeSuperOptimized(42, 42), repeat=50, number=100000)),))
পাইথন ৩.৩.২ দিয়ে চালান, এটি আউটপুট উত্পাদন করে:
IsSumInRangeWithVar
2 0 LOAD_FAST 0 (a)
3 LOAD_FAST 1 (b)
6 BINARY_ADD
7 STORE_FAST 2 (s)
3 10 LOAD_FAST 2 (s)
13 LOAD_CONST 1 (1000)
16 COMPARE_OP 4 (>)
19 POP_JUMP_IF_TRUE 34
22 LOAD_FAST 2 (s)
25 LOAD_CONST 4 (-1000)
28 COMPARE_OP 0 (<)
31 POP_JUMP_IF_FALSE 38
4 >> 34 LOAD_CONST 2 (False)
37 RETURN_VALUE
6 >> 38 LOAD_CONST 3 (True)
41 RETURN_VALUE
42 LOAD_CONST 0 (None)
45 RETURN_VALUE
IsSumInRangeWithoutVar
9 0 LOAD_FAST 0 (a)
3 LOAD_FAST 1 (b)
6 BINARY_ADD
7 LOAD_CONST 1 (1000)
10 COMPARE_OP 4 (>)
13 POP_JUMP_IF_TRUE 32
16 LOAD_FAST 0 (a)
19 LOAD_FAST 1 (b)
22 BINARY_ADD
23 LOAD_CONST 4 (-1000)
26 COMPARE_OP 0 (<)
29 POP_JUMP_IF_FALSE 36
10 >> 32 LOAD_CONST 2 (False)
35 RETURN_VALUE
12 >> 36 LOAD_CONST 3 (True)
39 RETURN_VALUE
40 LOAD_CONST 0 (None)
43 RETURN_VALUE
IsSumInRangeSuperOptimized
15 0 LOAD_GLOBAL 0 (abs)
3 LOAD_FAST 0 (a)
6 LOAD_FAST 1 (b)
9 BINARY_ADD
10 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
13 LOAD_CONST 1 (1000)
16 COMPARE_OP 1 (<=)
19 RETURN_VALUE
Benchmarking
IsSumInRangeWithVar: 0.019361s
IsSumInRangeWithoutVar: 0.020917s
IsSumInRangeSuperOptimized: 0.020171s
পাইথনে বিচ্ছিন্নতা মারাত্মক আকর্ষণীয় নয়, যেহেতু বাইটকোড "সংকলক" অপ্টিমাইজেশনের পথে খুব বেশি কিছু করে না।
তিনটি ফাংশনের অভিনয় প্রায় একই রকম ident IsSumInRangeWithVar()
প্রান্তিক গতি লাভের কারণে আমরা এর সাথে যেতে প্রলুব্ধ হতে পারি । যদিও আমি বিভিন্ন পরামিতিগুলি চেষ্টা করার সময় যুক্ত করছিলাম timeit
, কখনও কখনও IsSumInRangeSuperOptimized()
দ্রুততমভাবে বেরিয়ে আসে, তাই আমি সন্দেহ করি যে এটি কোনও প্রয়োগের অভ্যন্তরীণ সুবিধার পরিবর্তে পার্থক্যের জন্য দায়ী বাহ্যিক কারণ হতে পারে।
এটি যদি সত্যই পারফরম্যান্সের সমালোচনামূলক কোড হয় তবে একটি দোভাষী ভাষা কেবল খুব খারাপ পছন্দ। পাইপি দিয়ে একই প্রোগ্রাম চালাচ্ছি, আমি পেয়েছি:
IsSumInRangeWithVar: 0.000180s
IsSumInRangeWithoutVar: 0.001175s
IsSumInRangeSuperOptimized: 0.001306s
কেবল পাইপি ব্যবহার করে, যা প্রচুর দোভাষী ওভারহেডকে মুছে ফেলার জন্য জেআইটি সংকলন ব্যবহার করে, 1 বা 2 অর্ডারের প্রস্থের পারফরম্যান্স উন্নতি পেয়েছে। আমি IsSumInRangeWithVar()
অন্যদের তুলনায় দ্রুততার একটি ক্রম দেখে খুব অবাক হয়েছিলাম । তাই আমি মানদণ্ডের ক্রম পরিবর্তন করে আবার দৌড়েছি:
IsSumInRangeSuperOptimized: 0.000191s
IsSumInRangeWithoutVar: 0.001174s
IsSumInRangeWithVar: 0.001265s
সুতরাং দেখে মনে হচ্ছে এটি বাস্তবায়ন সম্পর্কে আসলে কিছুই নয় যা এটি দ্রুত করে তোলে, বরং আমি যে আদেশে বেঞ্চমার্কিং করছি তা!
আমি এটি আরও গভীরভাবে খনন করতে চাই, কারণ সত্যিই আমি জানি না কেন এটি ঘটে। তবে আমি বিশ্বাস করি পয়েন্টটি তৈরি করা হয়েছে: মাইক্রো-অপ্টিমাইজেশানগুলি যেমন একটি মধ্যবর্তী মানকে ভেরিয়েবল হিসাবে ঘোষণা করবেন কিনা তা খুব কমই প্রাসঙ্গিক। বর্ণিত ভাষা বা উচ্চতর অনুকূলিতকরণকারী সংকলক সহ, প্রথম উদ্দেশ্যটি এখনও স্পষ্ট কোড লিখতে হবে।
যদি আরও অপ্টিমাইজেশনের প্রয়োজন হতে পারে তবে বেঞ্চমার্ক । মনে রাখবেন যে সেরা অপ্টিমাইজেশানগুলি অল্প বিবরণ থেকে আসে না তবে বড় অ্যালগরিদমিক ছবি: পাইপাই সিপিথনের চেয়ে একই ফাংশনটির পুনরাবৃত্তি মূল্যায়নের জন্য দ্রুততর আকারের ক্রম হতে চলেছে কারণ এটি মূল্যায়নের জন্য দ্রুত অ্যালগরিদম (জেআইটি সংকলক বনাম ব্যাখ্যা) ব্যবহার করে কার্যক্রম. এবং কোডেড অ্যালগরিদমটিও বিবেচনা করার জন্য রয়েছে: একটি বি-ট্রি মাধ্যমে অনুসন্ধান লিঙ্কযুক্ত তালিকার চেয়ে দ্রুত হবে।
আপনি কাজের জন্য সঠিক সরঞ্জাম এবং অ্যালগরিদম ব্যবহার করছেন তা নিশ্চিত করার পরে , সিস্টেমের বিশদটি গভীরভাবে ডুবতে প্রস্তুত থাকুন । ফলাফলগুলি খুব আশ্চর্যজনক হতে পারে এমনকি অভিজ্ঞ বিকাশকারীদের জন্যও, এবং এ কারণেই পরিবর্তনগুলি মাপার জন্য আপনার অবশ্যই একটি মানদণ্ড থাকতে হবে।