আমি কপি করছি N থেকে বাইট pSrc
থেকে pDest
। এটি একটি একক লুপে করা যেতে পারে:
for (int i = 0; i < N; i++)
*pDest++ = *pSrc++
কেন এই তুলনায় ধীর হয় memcpy
বা memmove
? এটির গতি বাড়ানোর জন্য তারা কোন কৌশল ব্যবহার করে?
আমি কপি করছি N থেকে বাইট pSrc
থেকে pDest
। এটি একটি একক লুপে করা যেতে পারে:
for (int i = 0; i < N; i++)
*pDest++ = *pSrc++
কেন এই তুলনায় ধীর হয় memcpy
বা memmove
? এটির গতি বাড়ানোর জন্য তারা কোন কৌশল ব্যবহার করে?
1
করার N
, এটা সবসময় থেকে 0
থেকে N-1
:-)
int
কাউন্টার হিসাবে ব্যবহার করা হয় যখন size_t
পরিবর্তে একটি স্বাক্ষরবিহীন ধরণের ব্যবহার করা উচিত।
memcpy
বা memmove
(পয়েন্টারগুলি উপনাম হতে পারে কিনা তা তারা বলতে পারে কিনা তার উপর নির্ভর করে)
উত্তর:
যেহেতু মেমকিপি বাইট পয়েন্টারগুলির পরিবর্তে শব্দ পয়েন্টার ব্যবহার করে, এছাড়াও মেমকি অ্যাপ্লিকেশনগুলি প্রায়শই সিমডের নির্দেশাবলী দ্বারা লিখিত হয় যা একসাথে 128 বিট স্থানান্তরিত করে তোলে।
সিমডি নির্দেশাবলী হ'ল সমাবেশ নির্দেশাবলী যা ভেক্টরে প্রতিটি উপাদানগুলিতে 16 বাইট দীর্ঘ দীর্ঘ ক্রিয়াকলাপ করতে পারে। এর মধ্যে লোড এবং স্টোর নির্দেশাবলী অন্তর্ভুক্ত।
-O3
, এটি লুপের জন্য সিমডি ব্যবহার করবে, যদি না জানা থাকে pDest
এবং তার pSrc
নাম না রাখে।
মেমোরি অনুলিপি রুটিনগুলি পয়েন্টারগুলির মাধ্যমে সাধারণ মেমরির অনুলির চেয়ে অনেক জটিল এবং দ্রুত হতে পারে:
void simple_memory_copy(void* dst, void* src, unsigned int bytes)
{
unsigned char* b_dst = (unsigned char*)dst;
unsigned char* b_src = (unsigned char*)src;
for (int i = 0; i < bytes; ++i)
*b_dst++ = *b_src++;
}
উন্নতি
প্রথমটি যে উন্নতি করতে পারে তা হ'ল একটি শব্দের সীমানায় বিন্দুগুলির বিন্যস্ত করা (শব্দের দ্বারা আমার অর্থ দেশীয় পূর্ণসংখ্যার আকার, সাধারণত 32 বিট / 4 বাইট, তবে নতুন স্থাপত্যগুলিতে 64 বিট / 8 বাইট হতে পারে) এবং শব্দ আকারের পদক্ষেপ ব্যবহার করা যেতে পারে / অনুলিপি নির্দেশাবলী। এটি পয়েন্টার সারিবদ্ধ না হওয়া পর্যন্ত বাইট কপি করতে বাইট ব্যবহার করা প্রয়োজন।
void aligned_memory_copy(void* dst, void* src, unsigned int bytes)
{
unsigned char* b_dst = (unsigned char*)dst;
unsigned char* b_src = (unsigned char*)src;
// Copy bytes to align source pointer
while ((b_src & 0x3) != 0)
{
*b_dst++ = *b_src++;
bytes--;
}
unsigned int* w_dst = (unsigned int*)b_dst;
unsigned int* w_src = (unsigned int*)b_src;
while (bytes >= 4)
{
*w_dst++ = *w_src++;
bytes -= 4;
}
// Copy trailing bytes
if (bytes > 0)
{
b_dst = (unsigned char*)w_dst;
b_src = (unsigned char*)w_src;
while (bytes > 0)
{
*b_dst++ = *b_src++;
bytes--;
}
}
}
উত্স বা গন্তব্য পয়েন্টারটি যথাযথভাবে সংযুক্ত করা হয়েছে তার ভিত্তিতে বিভিন্ন আর্কিটেকচার ভিন্নভাবে সম্পাদন করবে। উদাহরণস্বরূপ একটি এক্সস্কেল প্রসেসরের উপর উত্স পয়েন্টারের চেয়ে গন্তব্য পয়েন্টারটিকে সারিবদ্ধ করে আমি আরও ভাল পারফরম্যান্স পেয়েছি।
কর্মক্ষমতা আরও উন্নত করতে কিছু লুপ আনআরোলিং করা যায়, যাতে প্রসেসরের আরও অনেক রেজিস্টারগুলি ডেটা সহ লোড হয় এবং এর অর্থ লোড / স্টোর নির্দেশাবলী আন্তঃবিহীন হতে পারে এবং অতিরিক্ত নির্দেশাবলী (যেমন লুপ কাউন্টিং ইত্যাদি) দ্বারা তাদের প্রচ্ছন্নতা লুকানো থাকে। প্রসেসরের দ্বারা এটি যে সুবিধাটি নিয়ে আসে তা কিছুটা পরিবর্তিত হয়, যেহেতু লোড / স্টোরের নির্দেশের বিলম্বগুলি বেশ আলাদা হতে পারে।
এই পর্যায়ে কোডটি সি (বা সি ++) এর পরিবর্তে অ্যাসেমব্লিতে লেখা হচ্ছে, যেহেতু বিলম্বতা আড়ালকরণ এবং থ্রুপুটটির সর্বাধিক সুবিধা পেতে আপনাকে ম্যানুয়ালি লোড এবং স্টোরের নির্দেশাবলীর প্রয়োজন।
সাধারণত লিখিত অনিয়ন্ত্রিত লুপের এক পুনরাবৃত্তিতে ডেটাগুলির একটি সম্পূর্ণ ক্যাশে লাইন অনুলিপি করা উচিত।
যা আমাকে পূর্বের উন্নতিতে পরের উন্নতিতে নিয়ে আসে। এগুলি বিশেষ নির্দেশাবলী যা প্রসেসরের ক্যাশে সিস্টেমকে মেমরির নির্দিষ্ট অংশগুলি তার ক্যাশে লোড করতে বলে। যেহেতু নির্দেশ জারি করার এবং ক্যাশে লাইনটি পূরণের মধ্যে কোনও বিলম্ব রয়েছে তাই নির্দেশিকাগুলি এমনভাবে স্থাপন করা দরকার যাতে তথ্য অনুলিপি করার সময় উপস্থিত থাকে এবং যত তাড়াতাড়ি / পরে হয় না।
এর অর্থ ফাংশনটি শুরু করার সাথে সাথে প্রধান অনুলিপি লুপের ভিতরে প্রিফেচ নির্দেশাবলী রাখা putting কপি লুপের আনার ডেটার মাঝখানে প্রিফেক নির্দেশাবলী সহ যা বেশ কয়েকটি পুনরাবৃত্তির সময়ে অনুলিপি করা হবে।
আমি মনে করতে পারি না তবে গন্তব্যের ঠিকানাগুলির পাশাপাশি উত্সের ঠিকানাগুলি উপস্থাপন করাও উপকারী হতে পারে।
ফ্যাক্টর
দ্রুত মেমোরি কীভাবে অনুলিপি করা যায় তার প্রধান কারণগুলি:
সুতরাং আপনি যদি একটি দক্ষ এবং দ্রুত মেমোরি মোকাবেলার রুটিন লিখতে চান তবে আপনার প্রসেসর এবং আর্কিটেকচারের জন্য আপনি যে লেখার জন্য লিখছেন তা সম্পর্কে আপনাকে অনেক কিছু জানতে হবে। বলা বাহুল্য, আপনি যদি কিছু এম্বেড থাকা প্ল্যাটফর্মটিতে না লিখে থাকেন তবে কেবল বিল্ট ইন মেমরি কপির রুটিনগুলি ব্যবহার করা আরও সহজ।
b_src & 0x3
সংকলন করবে না কারণ আপনাকে পয়েন্টার ধরণের ক্ষেত্রে বিটওয়াইজ গাণিতিক করার অনুমতি নেই। আপনাকে অবশ্যই এটি (u)intptr_t
প্রথমে নিক্ষেপ করতে হবে
memcpy
কম্পিউটারের আর্কিটেকচারের উপর নির্ভর করে একবারে একাধিক বাইট অনুলিপি করতে পারে। বেশিরভাগ আধুনিক কম্পিউটার একক প্রসেসরের নির্দেশে 32 বিট বা আরও বেশি দিয়ে কাজ করতে পারে।
থেকে একটা উদাহরণ বাস্তবায়ন :
00026 * দ্রুত অনুলিপি করার জন্য, উভয় পয়েন্টার হিসাবে সাধারণ ক্ষেত্রে অনুকূলিত করুন 00027 * এবং দৈর্ঘ্যটি শব্দের সাথে সংযুক্ত থাকে এবং পরিবর্তে শব্দটি একবারে অনুলিপি করে বাইট-এ-এ-সময়ে 00028 *। অন্যথায়, বাইট দ্বারা অনুলিপি।
আপনি memcpy()
নীচের যে কোনও কৌশল ব্যবহার করে বাস্তবায়ন করতে পারেন , কিছু পারফরম্যান্স লাভের জন্য আপনার আর্কিটেকচারের উপর নির্ভর করে এবং সেগুলি আপনার কোডের চেয়ে অনেক দ্রুত হবে:
বৃহত্তর ইউনিটগুলি ব্যবহার করুন, যেমন বাইটের পরিবর্তে 32-বিট শব্দ। আপনি এখানে (এমনকি হতে পারে) প্রান্তিককরণের সাথেও ডিল করতে পারেন। আপনি কিছু প্ল্যাটফর্মে উদাহরণস্বরূপ বিজোড় মেমরির স্থানে 32-বিট শব্দটি পড়তে / লিখতে পারবেন না এবং অন্য প্ল্যাটফর্মে আপনি একটি বিশাল পারফরম্যান্স জরিমানা প্রদান করেন। এটির সমাধানের জন্য, ঠিকানাটি 4 দিয়ে বিভাজনযোগ্য একক হতে হবে আপনি 64 বিট সিপিইউগুলির জন্য এটি 64-বিট পর্যন্ত নিতে পারেন বা সিমডি (একক নির্দেশনা, একাধিক ডেটা) নির্দেশাবলী ( এমএমএক্স , এসএসই , ইত্যাদি) ব্যবহার করেও উচ্চতর করতে পারেন )
আপনি বিশেষ সিপিইউ নির্দেশাবলী ব্যবহার করতে পারেন যা আপনার সংকলক সি থেকে অপ্টিমাইজ করতে নাও পারে উদাহরণস্বরূপ, 80386-এ, আপনি "রেপ" উপসর্গ নির্দেশ + "মুভস্ব" নির্দেশ ব্যবহার করতে পারেন এন বাইটগুলি গণনাতে নির্ধারিত করার জন্য নিবন্ধন. ভাল সংকলক আপনার জন্য কেবল এটি করবে, তবে আপনি এমন প্ল্যাটফর্মে থাকতে পারেন যাতে ভাল সংকলক নেই। দ্রষ্টব্য, সেই উদাহরণটি গতির একটি খারাপ প্রদর্শন হতে পারে, তবে প্রান্তিককরণ + বৃহত্তর ইউনিট নির্দেশাবলীর সাথে মিলিত হয়ে এটি নির্দিষ্ট সিপিইউতে থাকা সমস্ত কিছুর চেয়ে দ্রুততর হতে পারে।
লুপ আনরোলিং - কয়েকটি সিপিইউগুলিতে শাখাগুলি বেশ ব্যয়বহুল হতে পারে, তাই লুপগুলি আনআরোলিং করা শাখার সংখ্যা কমিয়ে আনতে পারে। এটি সিমডি নির্দেশাবলী এবং খুব বড় আকারের ইউনিটগুলির সাথে একত্রিত করার জন্য একটি ভাল কৌশল।
উদাহরণস্বরূপ, http://www.agner.org/optimize/#asMLib এর একটি memcpy
বাস্তবায়ন রয়েছে যা সেখানে সবচেয়ে মারধর করে (খুব অল্প পরিমাণে)। আপনি যদি সোর্স কোডটি পড়েন তবে এটি এমন প্রচুর পরিমাণে ইনলাইনড অ্যাসেমব্লিং কোড হবে যা উপরের তিনটি কৌশলকে টানবে এবং আপনি কোন সিপিইউ চালাচ্ছেন তার উপর নির্ভর করে সেই কৌশলগুলির মধ্যে কোনটি বেছে নেবে।
দ্রষ্টব্য, এখানে অনুরূপ অপ্টিমাইজেশন রয়েছে যা বাফারেও বাইটগুলি সন্ধানের জন্য তৈরি করা যেতে পারে। strchr()
এবং বন্ধুরা প্রায়শই দ্রুত আপনার হাত ঘূর্ণিত সমতুল্যের চেয়ে দ্রুত হয়ে যায়। এটি নেট এবং জাভার ক্ষেত্রে বিশেষভাবে সত্য । উদাহরণস্বরূপ,। নেট, অন্তর্নির্মিত String.IndexOf()
এমনকি বায়ার – মুর স্ট্রিং অনুসন্ধানের চেয়ে অনেক দ্রুত , কারণ এটি উপরোক্ত অপ্টিমাইজেশান কৌশলগুলি ব্যবহার করে।
এটি বাস্তবের কোনও বাস্তব-বাস্তবায়নে বাস্তবে ব্যবহৃত হয়েছে কিনা তা আমি জানি না memcpy
, তবে আমি মনে করি যে ডাফের ডিভাইসটি এখানে উল্লেখ করার যোগ্য।
উইকিপিডিয়া থেকে :
send(to, from, count)
register short *to, *from;
register count;
{
register n = (count + 7) / 8;
switch(count % 8) {
case 0: do { *to = *from++;
case 7: *to = *from++;
case 6: *to = *from++;
case 5: *to = *from++;
case 4: *to = *from++;
case 3: *to = *from++;
case 2: *to = *from++;
case 1: *to = *from++;
} while(--n > 0);
}
}
মনে রাখবেন যে memcpy
উপরেরটি ইচ্ছাকৃতভাবে to
পয়েন্টারটিকে বাড়িয়ে তোলে না । এটি কিছুটা আলাদা অপারেশন প্রয়োগ করে: মেমরি-ম্যাপযুক্ত রেজিস্টারটিতে লেখা। বিস্তারিত জানার জন্য উইকিপিডিয়া নিবন্ধটি দেখুন।
*to
মেমরি- ম্যাপযুক্ত নিবন্ধকে বোঝায় এবং ইচ্ছাকৃতভাবে বৃদ্ধি করা হয় না - লিঙ্ক-টু নিবন্ধটি দেখুন)। আমি যেমন ভেবেছিলাম যে আমি পরিষ্কার করে দিয়েছি, আমার উত্তর কোনও দক্ষ সরবরাহ করার চেষ্টা করে না memcpy
, এটি কেবল একটি কৌতূহলযুক্ত কৌশলটির উল্লেখ করে।
অন্যদের মতো মেমপ্পির অনুলিপিগুলি 1-বাইট খণ্ডের চেয়ে বড়। শব্দ আকারের খণ্ডে অনুলিপি করা খুব দ্রুত। তবে বেশিরভাগ বাস্তবায়ন এটিকে আরও একধাপ এগিয়ে নিয়ে যায় এবং লুপিংয়ের আগে বেশ কয়েকটি এমওভি (শব্দ) নির্দেশনা চালায়। অনুলিপি করে বলার সুবিধা, লুপ প্রতি 8 টি শব্দ ব্লক হ'ল লুপ নিজেই ব্যয়বহুল। এই কৌশলটি 8 টির একটি ফ্যাক্টর দ্বারা কন্ডিশনাল শাখার সংখ্যা হ্রাস করে, দৈত্যিক ব্লকগুলির জন্য অনুলিপিটিকে অনুকূল করে।
উত্তরগুলি দুর্দান্ত, তবে আপনি যদি এখনও memcpy
নিজেই একটি দ্রুত প্রয়োগ করতে চান তবে দ্রুত মেমকিপি, সি-তে ফাস্ট মেমকি সম্পর্কিত একটি আকর্ষণীয় ব্লগ পোস্ট রয়েছে ।
void *memcpy(void* dest, const void* src, size_t count)
{
char* dst8 = (char*)dest;
char* src8 = (char*)src;
if (count & 1) {
dst8[0] = src8[0];
dst8 += 1;
src8 += 1;
}
count /= 2;
while (count--) {
dst8[0] = src8[0];
dst8[1] = src8[1];
dst8 += 2;
src8 += 2;
}
return dest;
}
এমনকি, মেমরি অ্যাক্সেসগুলি অনুকূলকরণের সাথে এটি আরও ভাল হতে পারে।
কারণ অনেক লাইব্রেরির রুটিনের মতো এটি আপনি যে আর্কিটেকচারটি চালাচ্ছেন তার জন্য এটি অনুকূলিত হয়েছে। অন্যরা বিভিন্ন কৌশল ব্যবহার করেছেন যা পোস্ট করেছে।
পছন্দ দেওয়া হয়েছে, আপনার নিজের রোল না করে লাইব্রেরির রুটিনগুলি ব্যবহার করুন। এটি ডিআরওয়াইতে একটি প্রকরণ যা আমি ডিআরও (অন্যদের পুনরাবৃত্তি করবেন না) বলি। এছাড়াও, লাইব্রেরির রুটিনগুলি আপনার নিজের প্রয়োগের চেয়ে কম ভুল হতে পারে।
আমি মেমরি অ্যাক্সেস চেকারদের মেমরি বা স্ট্রিং বাফারগুলিতে শব্দের আকারের একাধিক ছিল না এমন পাঠের সীমার বাইরে অভিযোগ করতে দেখেছি। এটি অপটিমাইজেশন ব্যবহারের ফলাফল।
আপনি মেমসেট, মেমকি এবং মেমমোভের ম্যাকোস বাস্তবায়ন দেখতে পারেন।
বুট করার সময়, ওএস নির্ধারণ করে যে এটি কোন প্রসেসরটি চলছে। এটি প্রতিটি সমর্থিত প্রসেসরের জন্য বিশেষত অপ্টিমাইজড কোড তৈরি করেছে এবং বুট করার সময় একটি নির্দিষ্ট পঠন / কেবলমাত্র স্থানে সঠিক কোডের জন্য একটি জ্যাম্প নির্দেশ সংরক্ষণ করে।
সি মেমসেট, মেমকি এবং মেমোমোভ বাস্তবায়নগুলি নির্দিষ্ট স্থানে কেবলমাত্র এক লাফ।
বাস্তবায়নগুলি মেমকি এবং মেমমোভের জন্য উত্স এবং গন্তব্যের প্রান্তিককরণের উপর নির্ভর করে বিভিন্ন কোড ব্যবহার করে। তারা স্পষ্টতই সমস্ত উপলব্ধ ভেক্টর ক্ষমতা ব্যবহার করে। আপনি যখন প্রচুর পরিমাণে ডেটা অনুলিপি করেন এবং পৃষ্ঠার টেবিলগুলির জন্য অপেক্ষা কমানোর নির্দেশনা থাকে তখন এগুলি নন-ক্যাশিং বৈকল্পগুলিও ব্যবহার করে। এটি কেবল এসেম্বলারের কোড নয়, এটি প্রতিটি প্রসেসরের আর্কিটেকচার সম্পর্কে অত্যন্ত ভাল জ্ঞানের সাথে কেউ লিখেছেন এসেম্বলার কোড।
ইন্টেল এসেম্বলারের নির্দেশাবলীও যুক্ত করেছে যা স্ট্রিং অপারেশনগুলিকে আরও দ্রুত করতে পারে। উদাহরণস্বরূপ স্ট্রাস্টারকে সমর্থন করার নির্দেশনা সহ যা 256 বাইট এক চক্রের সাথে তুলনা করে।