c - মেমক্যাম্প কেন(ক, খ, ৪) কেবল কখনও কখনও uint32 তুলনাতে অনুকূলিত হয়?




gcc clang (3)

এই কোড দেওয়া:

#include <string.h>

int equal4(const char* a, const char* b)
{
    return memcmp(a, b, 4) == 0;
}

int less4(const char* a, const char* b)
{
    return memcmp(a, b, 4) < 0;
}

X86_64-তে জিসিসি 7 প্রথম মামলার জন্য একটি অপ্টিমাইজেশন প্রবর্তন করেছে (ঝনঝন এটি দীর্ঘকাল ধরে করেছে):

    mov     eax, DWORD PTR [rsi]
    cmp     DWORD PTR [rdi], eax
    sete    al
    movzx   eax, al

তবে দ্বিতীয় memcmp() এখনও memcmp() কল memcmp() :

    sub     rsp, 8
    mov     edx, 4
    call    memcmp
    add     rsp, 8
    shr     eax, 31

দ্বিতীয় ক্ষেত্রেও কি একই রকম অপটিমাইজেশন প্রয়োগ করা যেতে পারে? এর জন্য সেরা সমাবেশটি কী, এবং এটি কেন করা হচ্ছে না তার কোনও স্পষ্ট কারণ আছে (জিসিসি বা কলং দ্বারা)?

গডবোল্টের সংকলক এক্সপ্লোরার এ এটি দেখুন: https://godbolt.org/g/jv8fcf


অন্যান্য উত্তর / মন্তব্যে যেমন আলোচনা করা হয়েছে, memcmp(a,b,4) < 0 করা বড়-এন্ডিয়ান পূর্ণসংখ্যার মধ্যে unsigned তুলনা করার সমতুল্য। এটি লিটল-এন্ডিয়ান x86 তে == 0 মতো দক্ষতার সাথে ইনলাইন করতে পারে না।

আরও গুরুত্বপূর্ণ বিষয়, gcc7 / 8 এ এই আচরণের বর্তমান সংস্করণ কেবল memcmp() == 0 বা != 0 । এমনকি একটি বড়-এন্ডিয়ান লক্ষ্য যেখানে এই < বা > জন্য কেবল দক্ষতার সাথে ইনলাইন করতে পারে, জিসিসি এটি করবে না। (গডবোল্টের নতুন বিগ-এন্ডিয়ান সংকলকগুলি পাওয়ারপিসি g৪ জিসিসি 6.৩, এবং এমআইপিএস / এমআইপিএস 64৪ জিসিসি ৫.৪। mips বিগ-এন্ডিয়ান এমআইপিএস, অন্যদিকে mipsel ছোট-এন্ডিয়ান এমআইপিএস হয়।) যদি ভবিষ্যতের a = __builtin_assume_align(a, 4) সাথে এটি পরীক্ষা করে নেওয়া হয় তবে a = __builtin_assume_align(a, 4) এটি নিশ্চিত করতে যে জিসিসি-কে অ-x86-এ আন-সাইনড-লোড পারফরম্যান্স / নির্ভুলতা সম্পর্কে চিন্তা করতে হবে না। (অথবা const int32_t* const char* পরিবর্তে const int32_t* ব্যবহার করুন))

যদি / যখন memcmp / এনই ব্যতীত অন্যান্য ক্ষেত্রে memcmp ইনলাইন করতে শেখে, সম্ভবত memcmp এটি লিটল এন্ডিয়ান x86 এ করবে যখন এর হিউরিস্টিকস এটি বলবে যে অতিরিক্ত কোডের আকারটি উপযুক্ত হবে। উদাহরণস্বরূপ -fprofile-use (প্রোফাইল- -fprofile-use অপ্টিমাইজেশন) দিয়ে সংকলন করার সময় একটি গরম লুপে।

যদি আপনি এই ক্ষেত্রে uint32_t একটি ভাল কাজ করতে চান , আপনার সম্ভবত একটি uint32_t নিয়োগ করা উচিত এবং ntohl মতো একটি এন্ডিয়ান-রূপান্তর ফাংশন ব্যবহার করা ntohl । তবে নিশ্চিত হয়ে নিন যে আপনি এমন একটি বেছে নিয়েছেন যা আসলে ইনলাইন করতে পারে; দৃশ্যত উইন্ডোজের একটি ntohl যা একটি ডিএলএল কলকে সংকলন করে । কিছু পোর্টেবল-এন্ডিয়ান স্টাফের জন্য এই প্রশ্নের অন্যান্য উত্তরগুলি দেখুন এবং কোনও পোর্টেবল_েন্ডিয়ান এইচ.এর কারও অপূর্ণ প্রচেষ্টা এবং এটির এই কাঁটাচামচ দেখুন । আমি কিছুক্ষণের জন্য একটি সংস্করণে কাজ করছি, তবে এটি কখনই শেষ / পরীক্ষিত বা পোস্ট করেনি।

আপনি কীভাবে বাইট লিখেছেন এবং চরটি কী char* নির্দেশ করেছে তার উপর নির্ভর করে পয়েন্টার-কাস্টিং অপরিজ্ঞাত আচরণ হতে পারে। আপনি যদি কড়া- abytes এবং / অথবা প্রান্তিককরণ সম্পর্কে নিশ্চিত না হন তবে abytes । বেশিরভাগ সংকলক সংক্ষিপ্ত স্থির আকারের memcpy অপ্টিমাইজ করতে ভাল।

// I know the question just wonders why gcc does what it does,
// not asking for how to write it differently.
// Beware of alignment performance or even fault issues outside of x86.

#include <endian.h>
#include <stdint.h>

int equal4_optim(const char* a, const char* b) {
    uint32_t abytes = *(const uint32_t*)a;
    uint32_t bbytes = *(const uint32_t*)b;

    return abytes == bbytes;
}


int less4_optim(const char* a, const char* b) {
    uint32_t a_native = be32toh(*(const uint32_t*)a);
    uint32_t b_native = be32toh(*(const uint32_t*)b);

    return a_native < b_native;
}

আমি গডবোল্টে চেক করেছি এবং সেগুলি কার্যকর কোডের সাথে সংকলিত হয়েছে (মূলত আমি নীচে asm তে যা লিখেছি তার সাথে সমান) বিশেষত বড়জোর এন্ডিয়ান প্ল্যাটফর্মগুলিতে এমনকি পুরানো জিসিসি সহ। এটি আইসিসি memcmp চেয়েও অনেক ভাল কোড তৈরি করে, যা মেমপ্যাম্পকে memcmp তবে কেবল বাইট-তুলনা লুপকে (এমনকি == 0 কেসের ক্ষেত্রেও)।

আমি মনে করি যে এই হস্ত- less4() (x86-64 সিস্টেমভি কলিং কনভেনশনের জন্য, যেমন প্রশ্নে ব্যবহৃত, কনস্ট const char *a rdi আর rdi b less4() একটি অনুকূল বাস্তবায়ন less4()

less4:
    mov   edi, [rdi]
    mov   esi, [rsi]
    bswap edi
    bswap esi
    # data loaded and byte-swapped to native unsigned integers
    xor   eax,eax    # solves the same problem as gcc's movzx, see below
    cmp   edi, esi
    setb  al         # eax=1 if *a was Below(unsigned) *b, else 0
    ret

সেগুলি কে 8 এবং কোর 2 ( agner.org/optimize ) থেকে ইন্টেল এবং এএমডি সিপিইউগুলিতে সমস্ত একক-উওপ নির্দেশনা।

উভয় অপারেশনকে ব্লগআপ করার জন্য অতিরিক্ত কোড-আকারের দাম বনাম রয়েছে == 0 কেস: আমরা লোডগুলির মধ্যে একটিও মেমরি অপারেন্ডে সিএমপি-র জন্য ভাঁজ করতে পারি না। (এটি কোডের আকার সংরক্ষণ করে এবং মাইক্রো-ফিউশনকে ধন্যবাদ উওফস)) অতিরিক্ত দুটি bswap নির্দেশিকা এটি শীর্ষে।

movbe সমর্থন করে এমন movbe এটি কোডের আকারটি সংরক্ষণ করতে পারে: movbe ecx, [rsi] একটি লোড + বিএসওয়্যাপ। হাসওলে এটি 2 টি উওপ, সুতরাং সম্ভবত এটি মওভ mov ecx, [rsi] bswap ecx mov ecx, [rsi] / bswap ecx মতো একই bswap ecx । এটম / সিলভারমন্টে, এটি লোড পোর্টগুলিতে ডানদিকে পরিচালিত হয়, সুতরাং এটি কম উওপগুলির পাশাপাশি ছোট কোড-আকারের।

setcc / cmp / setcc (কোন ঝাঁকুনি ব্যবহার করে) কেন সিএমপি / সেটসিটিসি / মুভিজেক্স (জিসিসির জন্য আদর্শ) এর চেয়ে ভাল সে সম্পর্কে আরও জানতে আমার setcc - setcc উত্তরের setcc অংশটি দেখুন।

সাধারণ ক্ষেত্রে যেখানে ফলাফলের শাখাগুলি কোডের সাথে এই ইনলাইন করে, setcc + জিরো- jcc একটি jcc দিয়ে প্রতিস্থাপন করা হয়; সংকলক একটি রেজিস্টারে একটি বুলিয়ান রিটার্ন মান তৈরি করে অপ্টিমাইজ করে। এটি ইনলাইনিংয়ের আরও একটি সুবিধা: লাইব্রেরি memcmp একটি পূর্ণসংখ্যার বুলিয়ান রিটার্ন মান তৈরি করতে হবে যা কলার পরীক্ষা করে , কারণ কোনও x86 এবিআই / কলিং কনভেনশন পতাকাগুলিতে বুলিয়ান শর্ত ফিরিয়ে আনতে দেয় না। (আমি কোনও অ x x86 কলিং কনভেনশন জানি না যে এটি করে do বেশিরভাগ গ্রন্থাগার memcmp বাস্তবায়নের জন্য, দৈর্ঘ্যের উপর নির্ভর করে কৌশল বেছে নেওয়ার ক্ষেত্রে এবং সম্ভবত সারিবদ্ধতা পরীক্ষার ক্ষেত্রেও উল্লেখযোগ্য ওভারহেড রয়েছে। এটি বেশ সস্তা হতে পারে, তবে 4 মাপের জন্য এটি সমস্ত বাস্তব কাজের ব্যয়ের চেয়ে বেশি হতে চলেছে।


আপনি যদি সামান্য এন্ডিয়ান প্ল্যাটফর্মের জন্য কোড তৈরি করেন তবে একক DWORD তুলনায় memcmp জন্য memcmp -বাইট memcmp অপ্টিমাইজ করা অবৈধ।

যখন memcmp পৃথক বাইটের সাথে তুলনা করে তবে এটি প্ল্যাটফর্ম নির্বিশেষে লো-অ্যাড্রেসড বাইট থেকে উচ্চ-সম্বোধিত বাইটে যায়।

memcmp শূন্য ফিরিয়ে memcmp জন্য চারটি বাইট অবশ্যই অভিন্ন হতে হবে। অতএব, তুলনার ক্রমটি বিবেচনা করে না। অতএব, ডিডব্লর্ড অপ্টিমাইজেশন বৈধ, কারণ আপনি ফলাফলের সাইনটি উপেক্ষা করেন।

যাইহোক, যখন memcmp একটি ইতিবাচক সংখ্যা ফেরত দেয়, memcmp অর্ডার করে। অতএব, 32-বিট DWORD তুলনা ব্যবহার করে একই তুলনাটি প্রয়োগ করার জন্য একটি নির্দিষ্ট সমাপ্তি প্রয়োজন: প্ল্যাটফর্মটি অবশ্যই বড়-এন্ডিয়ান হতে হবে, অন্যথায় তুলনার ফলাফলটি ভুল হবে।


এন্ডিয়নেসনেস এখানে সমস্যা। এই ইনপুট বিবেচনা করুন:

a = 01 00 00 03
b = 02 00 00 02

যদি আপনি এই দুটি অ্যারেগুলি 32-বিট পূর্ণসংখ্যার হিসাবে বিবেচনা করে তুলনা করেন, তবে আপনি দেখতে পাবেন যে এটি আরও বড় (কারণ 0x03000001> 0x02000002)। একটি বড়-এন্ডিয়ান মেশিনে, এই পরীক্ষাটি সম্ভবত প্রত্যাশার মতো কাজ করবে।





compiler-optimization