c++ - অবজ - সি প্লাস প্লাস pdf




সি++মধ্যে দক্ষ পূর্ণসংখ্যা মেঝে ফাংশন (4)

Int ঢালাই কুখ্যাত ধীর।

হয়তো আপনি x86-64 থেকে একটি শিলা অধীনে বসবাস করা হয়েছে, অথবা অন্যথায় মিস এটা x86 একটি সময় জন্য সত্য ছিল না যে। :)

এসএসই / এসএসই 2 এর ট্রানকেশন (ডিফল্ট গোলাকার মোড পরিবর্তে) রূপান্তর করার জন্য একটি নির্দেশ রয়েছে। আইএসএটি কার্যকরীভাবে এই অপারেশনকে সমর্থন করে কারণ সি সিমন্টিক্সের সাথে রূপান্তর প্রকৃত কোডবেসে বিরল নয়। x86-64 কোডটি এসএএস / এসএসই 2 এক্সএমএম রেজিস্ট্রারগুলিকে স্কলার FP গণিতের জন্য ব্যবহার করে, x87 নয়, কারণ এটি এবং অন্যান্য জিনিস যা এটি আরও দক্ষ করে তোলে। এমনকি আধুনিক 32 বিট কোড স্কলার গণিতের জন্য এক্সএমএম নিবন্ধক ব্যবহার করে।

fisttp ( fisttp ) কম্পাইলারগুলির জন্য x87 বৃত্তাকার মোডটি ট্রানকেশন, FP স্টোর থেকে মেমরিতে পরিবর্তন করতে হবে, তারপরে আবার বৃত্তাকার মোডটি আবার পরিবর্তন করুন। (এবং তারপরে মেমরি থেকে পূর্ণসংখ্যাটি পুনরায় লোড করুন, সাধারণত স্ট্যাকে একটি স্থানীয় থেকে, যদি এর সাথে আরও কিছু করা হয়।) X87 এটির জন্য ভয়ানক

হ্যাঁ যেটি খুব ধীরে ধীরে ছিল , উদাহরণস্বরূপ ২006 সালে যখন @ কিরিজেনের উত্তরটি লিঙ্কে লিখিত ছিল, যদি আপনার 32-বিট CPU থাকে বা 32-বিট কোড চালানোর জন্য একটি x86-64 CPU ব্যবহার করা হয়।

roundps বা ডিফল্ট (নিকটতম) ব্যতীত গোলাকার মোডের সাথে রূপান্তর সরাসরি সরাসরি সমর্থিত নয় এবং roundps 4.1 roundps / roundpd পর্যন্ত আপনার সেরা বেটাটি কিজেনের উত্তর থেকে ২006 লিঙ্কের মতো জাদু-সংখ্যা কৌশল ছিল।

সেখানে কিছু চমৎকার কৌশল, কিন্তু শুধুমাত্র double -> 32-বিট পূর্ণসংখ্যা জন্য। আপনি float আছে double প্রসারিত মূল্য হতে অসম্ভাব্য।

বা আরো বেশি, কেবল বৃত্তাকার ট্রিগার করার জন্য একটি বৃহত-পরিমাপ নম্বর যুক্ত করুন, তারপরে মূল পরিসরতে ফিরে আসার জন্য এটি আবার বিয়োগ করুন। এটি double ব্যতীত float জন্য কাজ করতে পারে, তবে আমি নিশ্চিত নই যে floor কাজ করা কত সহজ।

যাইহোক, এখানে সুস্পষ্ট সমাধান _mm256_floor_ps() এবং _mm256_cvtps_epi32 ( vroundps এবং vcvtps2dq )। এর একটি অ-AVX সংস্করণ SSE4.1 দিয়ে কাজ করতে পারে।

আমি আরো ভাল করতে পারি কিনা তা নিশ্চিত নই; যদি আপনার কাছে প্রসেস করার জন্য একটি বিশাল অ্যারে ছিল (এবং অন্যান্য কাজের সাথে এই কাজটি হস্তান্তর করতে পারে না ), আপনি MXCSR বৃত্তাকার মোডটি "প্রতি-ইফ" (মেঝে) এ সেট করতে পারেন এবং কেবলমাত্র vcvtps2dq ব্যবহার করতে vcvtps2dq (যা বর্তমান গোলাকার মোড ব্যবহার করে) )। তারপর ফিরে সেট। তবে সম্ভবত আপনার রূপান্তরকে ক্যাশে-ব্লক করতে বা এটিতে ফ্লাই-তে এটি করা ভাল যেহেতু আপনি ডেটা জেনারেট করেন, সম্ভবত অন্য FP গণনাগুলি থেকে যা FP রাউন্ডিং মোডটি ডিফল্ট হিসাবে সেট করা প্রয়োজন।

roundps / পিডি / এসএস / এসডি ইন্টেল সিপিইউগুলিতে ২ ইউপস, তবে এএমডি রেজেনে শুধুমাত্র 1 ইউপ (প্রতি 128-বিট লেন)। cvtps2dq এছাড়াও 1 uop। বস্তাবন্দী ডবল-> int রূপান্তর এছাড়াও একটি বদল অন্তর্ভুক্ত। Scalar FP-> int রূপান্তর (যে একটি পূর্ণসংখ্যা নিবন্ধন প্রতি কপি) সাধারণত এটি জন্য একটি অতিরিক্ত UOP খরচ।

তাই কিছু ক্ষেত্রে যাদু-নম্বরের কৌশলগুলি জয় হওয়ার সম্ভাবনা রয়েছে; _mm256_floor_ps() + cvt একটি জটিল সংঘাতের অংশ (অথবা যদি আপনার কাছে দ্বিগুণ থাকে এবং int32 চান তাহলে এটি সম্ভবত অনুসন্ধানের যোগ্য)।

@ ক্যাসিও রেনান এর int foo = floorf(f) gcc -O3 -fno-trapping-math (বা -ffast-math ) এর সাথে সংকলিত হলে স্বয়ং-ভেক্টরাইজ করবে -march= 4.1 বা এভিএক্স এর সাথে কিছু। https://godbolt.org/z/ae_KPv

এটি অন্যথায় স্কলার কোড ব্যবহার করে যদি এটি নিজেও ভেক্টরাইজড না হয় তবে এটি কার্যকর হতে পারে। আপনি কম্পাইলার পুরো জিনিস স্বয়ংক্রিয়-vectorize হবে আশা করছি বিশেষ করে যদি।

আমি একটি কার্যকর পূর্ণসংখ্যা মেঝে ফাংশন সংজ্ঞায়িত করতে চাই, অর্থাত্ ভাসা বা ডবল থেকে একটি রূপান্তর যা বিয়োগ অসীম দিকে ট্রান্সকেশন সঞ্চালন করে।

আমরা অনুমান করতে পারি যে মান এমন হয় যে কোন পূর্ণসংখ্যা ওভারফ্লো ঘটে না। এ পর্যন্ত আমি কয়েক বিকল্প আছে

  • int ঢালাই; এই নেতিবাচক মান বিশেষ পরিচালনা প্রয়োজন, কাস্ট শূন্য দিকে truncates হিসাবে;

    I= int(F); if (I < 0 && I != F) I--;
  • int থেকে মেঝে ফলাফল ঢালাই;

    int(floor(F));
  • ইতিবাচক পেতে একটি বড় শিফট সঙ্গে int- ঢালাই (এই বড় মান জন্য ভুল ফলাফল ফেরত দিতে পারেন);

    int(F + double(0x7fffffff)) - 0x7fffffff;

Int ঢালাই কুখ্যাত ধীর। তাই যদি পরীক্ষা হয়। আমি মেঝে ফাংশন টাইম না, কিন্তু পোস্ট পোস্ট এটি ধীরে ধীরে দাবি।

আপনি গতি, সঠিকতা বা অনুমতি সীমার পরিপ্রেক্ষিতে ভাল বিকল্প মনে করতে পারেন? এটা পোর্টেবল হতে হবে না। টার্গেট সাম্প্রতিক x86 / x64 আর্কিটেকচারগুলি।


আপনি যদি ব্যাচটিতে এটি করছেন তবে কম্পাইলারটি আপনি কী করছেন তা যদি আপনি জানেন তবে এটি স্বয়ংক্রিয়ভাবে তৈরি হতে পারে। উদাহরণস্বরূপ, এখানে একটি ছোট বাস্তবায়ন যা ফ্ল্যাটগুলির পূর্ণসংখ্যার রূপান্তরকে স্বাক্ষর করে GCC এ:

#include <cmath>

// Compile with -O3 and -march=native to see autovectorization
__attribute__((optimize("-fno-trapping-math")))
void testFunction(float* input, int* output, int length) {
  // Assume the input and output are aligned on a 32-bit boundary.
  // Of course, you have  to ensure this when calling testFunction, or else
  // you will have problems.
  input = static_cast<float*>(__builtin_assume_aligned(input, 32));
  output = static_cast<int*>(__builtin_assume_aligned(output, 32));

  // Also assume the length is a multiple of 32.
  if (length & 31) __builtin_unreachable();

  // Do the conversion
  for (int i = 0; i < length; ++i) {
    output[i] = floor(input[i]);
  }
}

এটি x86-64 এর জন্য তৈরি সমাবেশ (AVX512 নির্দেশাবলী সহ):

testFunction(float*, int*, int):
        test    edx, edx
        jle     .L5
        lea     ecx, [rdx-1]
        xor     eax, eax
.L3:
        # you can see here that the conversion was vectorized
        # to a vrndscaleps (that will round the float appropriately)
        # and a vcvttps2dq (thal will perform the conversion)
        vrndscaleps     ymm0, YMMWORD PTR [rdi+rax], 1
        vcvttps2dq      ymm0, ymm0
        vmovdqa64       YMMWORD PTR [rsi+rax], ymm0
        add     rax, 32
        cmp     rax, rdx
        jne     .L3
        vzeroupper
.L5:
        ret

যদি আপনার লক্ষ্য AVX512 সমর্থন করে না তবে এটি এখনও আপনার কাছে আছে বলে ধরে নেওয়া, এটি SSE4.1 নির্দেশাবলী ব্যবহার করে অটোভক্টরাইজ করা হবে। এটি -O3 -msse4.1 সাথে আউটপুট:

testFunction(float*, int*, int):
        test    edx, edx
        jle     .L1
        shr     edx, 2
        xor     eax, eax
        sal     rdx, 4
.L3:
        roundps xmm0, XMMWORD PTR [rdi+rax], 1
        cvttps2dq       xmm0, xmm0
        movaps  XMMWORD PTR [rsi+rax], xmm0
        add     rax, 16
        cmp     rax, rdx
        jne     .L3
.L1:
        ret

এটা Godbolt লাইভ দেখুন


কেন শুধু এই ব্যবহার করবেন না:

#include <cmath>

auto floor_(float const x) noexcept
{
  int const t(x);

  return t - (t > x);
}

যাদু সংখ্যা একটি বর্ণন আছে। ওয়েব পৃষ্ঠায় প্রস্তাবিত অ্যালগরিদম সহজ কাস্টিং চেয়ে অনেক বেশি কার্যকর হওয়া উচিত। আমি নিজে নিজে এটি ব্যবহার করি নি, তবে এটি তাদের সাইটে প্রস্তাবিত পারফরমেন্স তুলনা (xs_ToInt এবং xs_CRoundToInt প্রস্তাবিত ফাংশন):

Performing 10000000 times:
simple cast           2819 ms i.e. i = (long)f;
xs_ToInt              1242 ms i.e. i = xs_ToInt(f); //numerically same as above
bit-twiddle(full)     1093 ms i.e. i = BitConvertToInt(f); //rounding from Fluid
fistp                  676 ms i.e. i = FISTToInt(f); //Herf, et al x86 Assembly rounding 
bit-twiddle(limited)   623 ms i.e. i = FloatTo23Bits(f); //Herf, rounding only in the range (0...1]  
xs_CRoundToInt         609 ms i.e. i = xs_CRoundToInt(f); //rounding with "magic" numbers

উপরন্তু, xs_ToInt দৃশ্যত পরিবর্তন করা হয়েছে যাতে কর্মক্ষমতা উন্নত হয়:

Performing 10000000 times:
simple cast convert   3186 ms i.e. fi = (f*65536);
fistp convert         3031 ms i.e. fi = FISTToInt(f*65536);
xs_ToFix               622 ms i.e. fi = xs_Fix<16>::ToFix(f);

কিভাবে 'যাদু সংখ্যা' পদ্ধতি কাজ করে সে সম্পর্কে সংক্ষিপ্ত ব্যাখ্যা:

"মূলত, দুটি ভাসমান বিন্দু সংখ্যার যোগ করার জন্য, আপনার প্রসেসর সংখ্যাগুলির দশমিক বিন্দুকে" লাইন আপ করে "যাতে এটি সহজে বিট যুক্ত করতে পারে। এটি এমন সংখ্যাকে" স্বাভাবিককরণ "করে, যা সবচেয়ে গুরুত্বপূর্ণ বিটগুলি সংরক্ষিত থাকে , অর্থাৎ ছোট সংখ্যাটি বড় একটিকে "স্বাভাবিক করে তুলবে"। সুতরাং "যাদু সংখ্যা" রূপান্তর যা xs_CRoundToInt () ব্যবহার করে তা এই হল: আমরা একটি বড় যথেষ্ট ভাসমান বিন্দু সংখ্যা যুক্ত করি (একটি সংখ্যা যা এত বড় উল্লেখযোগ্য সংখ্যাগুলি শুধুমাত্র দশমিক বিন্দুতে এবং এর পরে কোনও নয়) আপনি যেটিকে রূপান্তর করছেন সেটির জন্য: (ক) প্রসেসর দ্বারা সংখ্যাটি তার পূর্ণসংখ্যাতে স্বাভাবিক করা হয় এবং (খ) দুইটি সংযোজনকে অবিচ্ছেদ্য না মুছে দেয় আপনি রূপান্তর করার চেষ্টা করছেন সংখ্যা তাত্পর্য বিট (যেমন XX00 + 00YY = XXYY)। "

উদ্ধৃতি একই ওয়েব পেজ থেকে নেওয়া হয়।