513x513 এর ম্যাট্রিক্স স্থানান্তরের চেয়ে 512x512 এর ম্যাট্রিক্স কেন খুব ধীর গতিতে চলছে?


218

বিভিন্ন আকারের স্কোয়ার ম্যাট্রিকগুলিতে কিছু পরীক্ষা-নিরীক্ষার পরে, একটি প্যাটার্ন সামনে এলো। অবিচ্ছিন্নভাবে, আকারের একটি ম্যাট্রিক্স স্থানান্তর করা একটি আকারের 2^nট্রান্সোপোজ করার চেয়ে ধীর2^n+1 । ছোট মানগুলির জন্য n, পার্থক্যটি প্রধান নয়।

512 এর মান নিয়ে বড় পার্থক্য দেখা দেয় ((কমপক্ষে আমার জন্য)

অস্বীকৃতি: আমি জানি যে উপাদানগুলি ডাবল অদলবদলের কারণে ফাংশনটি আসলে ম্যাট্রিক্স স্থানান্তর করে না, তবে এটি কোনও পার্থক্য করে না।

কোড অনুসরণ করে:

#define SAMPLES 1000
#define MATSIZE 512

#include <time.h>
#include <iostream>
int mat[MATSIZE][MATSIZE];

void transpose()
{
   for ( int i = 0 ; i < MATSIZE ; i++ )
   for ( int j = 0 ; j < MATSIZE ; j++ )
   {
       int aux = mat[i][j];
       mat[i][j] = mat[j][i];
       mat[j][i] = aux;
   }
}

int main()
{
   //initialize matrix
   for ( int i = 0 ; i < MATSIZE ; i++ )
   for ( int j = 0 ; j < MATSIZE ; j++ )
       mat[i][j] = i+j;

   int t = clock();
   for ( int i = 0 ; i < SAMPLES ; i++ )
       transpose();
   int elapsed = clock() - t;

   std::cout << "Average for a matrix of " << MATSIZE << ": " << elapsed / SAMPLES;
}

পরিবর্তনটি MATSIZEআমাদের আকার পরিবর্তন করতে দেয় (ডু!)। আমি আইডোনে দুটি সংস্করণ পোস্ট করেছি:

আমার পরিবেশে (এমএসভিএস 2010, সম্পূর্ণ অনুকূলিতকরণ), পার্থক্যটি একই রকম:

  • আকার 512 - গড় 2.19 এমএস
  • আকার 513 - গড়ে 0.57 এমএস

এটি কেন ঘটছে?


9
আপনার কোডটি আমার কাছে বন্ধুত্বপূর্ণ মনে হচ্ছে।
কোডসইনচাউস

7
: এটা প্রায় কাছাকাছি এই প্রশ্নের মতো একই সমস্যা stackoverflow.com/questions/7905760/...
Mysticial

বিস্তৃত করার যত্ন নিচ্ছেন, @ কোডস ইনচাউস? (বা অন্য কেউ।)
কোরাজা

@ বান কীভাবে গৃহীত উত্তর পড়বেন?
CodeInChaos

4
@ এনজমকক্সিয়া এটি অপ্টিমাইজেশন ছাড়াই কিছু পরিমাপ করা মূর্খ। অপ্টিমাইজেশন অক্ষম হওয়ার সাথে সাথে, উত্পন্ন কোডটি বহির্মুখী আবর্জনা দিয়ে লিটার করা হবে যা অন্যান্য বাধাগুলি আড়াল করবে। (যেমন মেমরি হিসাবে)
রহস্যময়

উত্তর:


197

ব্যাখ্যাটি আগ্নেয়র কুয়াশা থেকে সি ++ এর অপ্টিমাইজিং সফ্টওয়্যার থেকে আসে এবং এটি কীভাবে ডেটা অ্যাক্সেস এবং ক্যাশে সংরক্ষণ করা হয় তা হ্রাস করে।

শর্তাদি এবং বিস্তারিত তথ্যের জন্য, ক্যাশে ক্যাশে উইকি এন্ট্রি দেখুন , আমি এখানে এটি সংকীর্ণ করব।

একটি ক্যাশে সেট এবং লাইনে সংগঠিত হয় । একসময়, কেবল একটি সেট ব্যবহৃত হয়, যার মধ্যে এর মধ্যে থাকা যে কোনও লাইন ব্যবহার করা যেতে পারে। একটি রেখা মেমরিটি লাইনগুলিকে আয়না করতে পারে লাইনগুলির সংখ্যা আমাদের ক্যাশে আকার দেয়।

একটি নির্দিষ্ট মেমরি ঠিকানার জন্য, আমরা গণনা করতে পারি যে কোন সেটটি সূত্রের সাথে এটি মিরর করা উচিত:

set = ( address / lineSize ) % numberOfsets

সূত্র এই ধরনের আদর্শভাবে সেট জুড়ে একটি অভিন্ন বন্টন দেয়, কারণ প্রতিটি মেমরি অ্যাড্রেস হিসাবে সম্ভবত পড়তে হবে (আমি বললাম আদর্শভাবে )।

এটি স্পষ্ট যে ওভারল্যাপগুলি ঘটতে পারে। ক্যাশে মিসের ক্ষেত্রে, মেমরিটি ক্যাশে পড়ে এবং পুরানো মানটি প্রতিস্থাপন করা হয়। মনে রাখবেন যে প্রতিটি সেটে বেশ কয়েকটি লাইন রয়েছে, যার মধ্যে সর্বাধিক ব্যবহৃত একটি সদ্য পড়া মেমরির সাথে ওভাররাইট করা হয়।

আমি আগ্নেরের থেকে কিছুটা উদাহরণ অনুসরণ করার চেষ্টা করব:

ধরুন যে প্রতিটি সেটে 4 টি লাইন রয়েছে, প্রতিটি ধারণ করে 64 বাইট। আমরা প্রথমে ঠিকানাটি পড়ার চেষ্টা করি 0x2710যা সেট হয়ে যায় 28। এবং তারপর আমরা ঠিকানাগুলি পড়তে প্রচেষ্টা 0x2F00, 0x3700, 0x3F00এবং 0x4700। এই সমস্ত একই সেট অন্তর্গত। পড়ার আগে 0x4700সেটে সমস্ত লাইন দখল করে নেওয়া যেত। মেমরিটি পড়লে সেটটিতে বিদ্যমান লাইনটি শুরুর আগেই যে রেখাটি ধরে ছিল 0x2710। সমস্যাটি সত্য যে আমরা ঠিকানাগুলি 0x800পৃথক পৃথক (এই উদাহরণস্বরূপ) পড়ি । এটি সমালোচনামূলক অগ্রগতি (আবার এই উদাহরণের জন্য)।

সমালোচনামূলক গতিও গণনা করা যায়:

criticalStride = numberOfSets * lineSize

criticalStrideএকই ক্যাশে লাইনের পক্ষে চলকগুলি ব্যবধানযুক্ত বা একাধিক পৃথক দাবি করে।

এটি তত্ত্বের অংশ। এর পরে, ব্যাখ্যা (এছাড়াও আগ্নেয়র, আমি ভুলগুলি এড়ানোর জন্য এটি নিবিড়ভাবে অনুসরণ করছি):

একটি 8 কেবি ক্যাশে, সেট প্রতি 4 লাইন * 64 বাইটের লাইন আকারের সাথে 64x64 এর ম্যাট্রিক্স (মনে রাখবেন, প্রভাবগুলি ক্যাশে অনুযায়ী পৃথক হয়)। প্রতিটি লাইন ম্যাট্রিক্সের 64৪ টি উপাদান (-৪-বিট int) ধরে রাখতে পারে ।

সমালোচনামূলক পদক্ষেপটি হবে 2048 বাইট, যা ম্যাট্রিক্সের 4 সারি (যা স্মৃতিতে ধারাবাহিক) এর সাথে মিল রয়েছে।

ধরুন আমরা সারি ২৮ প্রক্রিয়াকরণ করছি processing আমরা এই সারির উপাদানগুলি নিয়ে যাওয়ার চেষ্টা করছি এবং কলাম ২৮ থেকে উপাদানগুলির সাথে সেগুলিকে অদলবদল করব the সারিটির প্রথম ৮ টি উপাদান একটি ক্যাশে রেখা তৈরি করেছে, তবে তারা আটটি আলাদা করে যাবে ২৮ কলামে ক্যাশে রেখাগুলি। মনে রাখবেন, সমালোচনামূলক গতি 4 টি সারি আলাদা (একটি কলামে টানা 4 উপাদান)।

যখন কলামে এলিমেন্ট 16 পৌঁছে যাবে (সেট প্রতি 4 টি ক্যাশে লাইন এবং 4 টি সারি আলাদা = অসুবিধা) প্রাক্তন -0 এলিমেন্টটি ক্যাশে থেকে উচ্ছেদ করা হবে। আমরা যখন কলামটির শেষ প্রান্তে পৌঁছেছি তখন সমস্ত পূর্ববর্তী ক্যাশে লাইনগুলি হারিয়ে গেছে এবং পরবর্তী উপাদানটিতে অ্যাক্সেস পুনরায় লোড করা প্রয়োজন (পুরো লাইনটি ওভাররাইট করা হয়েছে)।

এমন একটি আকার থাকা যা সমালোচনামূলক পদক্ষেপের একাধিক নয় , বিপর্যয়ের জন্য এই নিখুঁত দৃশ্যের জবাব দেয় , কারণ আমরা আর উল্লম্বভাবে পৃথক দিকের সমালোচনামূলক পদক্ষেপের উপাদানগুলির সাথে কাজ করছি না, তাই ক্যাশে পুনরায় লোডের সংখ্যা মারাত্মকভাবে হ্রাস পেয়েছে।

আরেকটি দাবি অস্বীকার - আমি কেবল ব্যাখ্যাটি পেয়েছি এবং আশা করি আমি এটি পেরেক দিয়েছি, তবে আমার ভুল হতে পারে। যাইহোক, আমি মিস্টিকাল থেকে একটি প্রতিক্রিয়া (বা নিশ্চিতকরণ) জন্য অপেক্ষা করছি । :)


ওহ এবং পরবর্তী সময়। শুধু লাউঞ্জের মাধ্যমে আমাকে সরাসরি পিং করুন । এসও-তে নামের প্রতিটি উদাহরণ খুঁজে পাচ্ছি না। :) আমি কেবল পর্যায়ক্রমিক ইমেল বিজ্ঞপ্তিগুলির মাধ্যমে এটি দেখেছি।
রহস্যময়

আমার বন্ধুদের @Mysticial @Luchian Grigore ওয়ান আমাকে বলে যে, তার Intel core i3পিসি চলমান Ubuntu 11.04 i386সঙ্গে প্রায় একই কর্মক্ষমতা প্রমান জিসিসি 4.6 .এবং তাই আমার কম্পিউটারের জন্য একই Intel Core 2 Duoসঙ্গে mingw gcc4.4 , যিনি বুকে গুলি চালাচ্ছেন windows 7(32).এটা একটি বড় পার্থক্য যখন প্রদর্শন করে আমি এই বিভাগটি কম বয়সী পিসি intel centrinoদিয়ে জিসিসি 4.6 দিয়ে সংকলন করছি , যারা চলছে ubuntu 12.04 i386
হংকক্সু চেন

এছাড়াও মনে রাখবেন যে মেমরি অ্যাক্সেস যেখানে 4096 এর একাধিক দ্বারা ঠিকানাগুলি পৃথক হয় ইন্টেল এসএনবি-পরিবার সিপিইউতে একটি মিথ্যা নির্ভরতা থাকে। (অর্থাত্ একটি পৃষ্ঠার মধ্যে একই অফসেট)। এটি থ্রিপুট হ্রাস করতে পারে যখন কিছু অপারেশন স্টোর হয়, esp। বোঝা এবং দোকান একটি মিশ্রণ।
পিটার কর্ডেস

which goes in set 24আপনি কি তার পরিবর্তে " সেট 28 " মানে ? এবং আপনি 32 সেট ধরে?
রুসলান

আপনি সঠিক, এটি 28 :) :) আমি লিঙ্কযুক্ত কাগজটিও ডাবল-চেক করেছি, মূল ব্যাখ্যাটির জন্য আপনি 9.2 ক্যাশে
সংগঠনটিতে

78

লুচিয়ান এই আচরণটি কেন ঘটে তার একটি ব্যাখ্যা দেয় তবে আমি ভেবেছিলাম যে এই সমস্যার একটি সম্ভাব্য সমাধান দেখানো এবং একই সাথে ক্যাশে বিস্মৃত অ্যালগরিদমগুলি সম্পর্কে কিছুটা দেখানো ভাল ধারণা হবে।

আপনার অ্যালগরিদম মূলত:

for (int i = 0; i < N; i++) 
   for (int j = 0; j < N; j++) 
        A[j][i] = A[i][j];

যা একটি আধুনিক সিপিইউর জন্য কেবল ভয়ঙ্কর। একটি সমাধান হ'ল আপনার ক্যাশে সিস্টেম সম্পর্কে বিশদ জানুন এবং সেই সমস্যাগুলি এড়াতে অ্যালগরিদমটিকে সামঞ্জস্য করুন। আপনি যতক্ষণ না এই বিবরণগুলি জানেন ততক্ষণ দুর্দান্ত কাজ করে .. বিশেষত বহনযোগ্য নয়।

আমরা কি এর চেয়ে আরও ভাল করতে পারি? হ্যাঁ আমরা পারি: এই সমস্যার একটি সাধারণ পদ্ধতির নাম হ'ল ক্যাশে বিস্মৃত অ্যালগরিদম যা নাম অনুসারে নির্দিষ্ট ক্যাশে আকারের উপর নির্ভরশীল হওয়া এড়িয়ে চলে [1]

সমাধানটি দেখতে এটির মতো হবে:

void recursiveTranspose(int i0, int i1, int j0, int j1) {
    int di = i1 - i0, dj = j1 - j0;
    const int LEAFSIZE = 32; // well ok caching still affects this one here
    if (di >= dj && di > LEAFSIZE) {
        int im = (i0 + i1) / 2;
        recursiveTranspose(i0, im, j0, j1);
        recursiveTranspose(im, i1, j0, j1);
    } else if (dj > LEAFSIZE) {
        int jm = (j0 + j1) / 2;
        recursiveTranspose(i0, i1, j0, jm);
        recursiveTranspose(i0, i1, jm, j1);
    } else {
    for (int i = i0; i < i1; i++ )
        for (int j = j0; j < j1; j++ )
            mat[j][i] = mat[i][j];
    }
}

কিছুটা জটিল, তবে একটি সংক্ষিপ্ত পরীক্ষা আমার প্রাচীন e8400 তে ভিএস 2010 x 64 রিলিজ, টেস্টকোড সহ আকর্ষণীয় কিছু দেখায় MATSIZE 8192

int main() {
    LARGE_INTEGER start, end, freq;
    QueryPerformanceFrequency(&freq);
    QueryPerformanceCounter(&start);
    recursiveTranspose(0, MATSIZE, 0, MATSIZE);
    QueryPerformanceCounter(&end);
    printf("recursive: %.2fms\n", (end.QuadPart - start.QuadPart) / (double(freq.QuadPart) / 1000));

    QueryPerformanceCounter(&start);
    transpose();
    QueryPerformanceCounter(&end);
    printf("iterative: %.2fms\n", (end.QuadPart - start.QuadPart) / (double(freq.QuadPart) / 1000));
    return 0;
}

results: 
recursive: 480.58ms
iterative: 3678.46ms

সম্পাদনা: আকারের প্রভাব সম্পর্কে: এটি এখনও কিছুটা হলেও লক্ষণীয় হলেও এটি অনেক কম উচ্চারণযোগ্য, কারণ আমরা পুনরাবৃত্ত সমাধানটি পাতার নোড হিসাবে 1 এ নামার পরিবর্তে ব্যবহার করছি (পুনরাবৃত্ত আলগোরিদমের জন্য স্বাভাবিক অপ্টিমাইজেশন)। যদি আমরা LEAFSIZE = 1 সেট করি তবে ক্যাশেটি আমার জন্য কোনও প্রভাব ফেলবে না [ 8193: 1214.06; 8192: 1171.62ms, 8191: 1351.07ms- এটি ত্রুটির প্রান্তরে রয়েছে, ওঠানামাটি 100 মিমি অঞ্চলে হয়; এই "বেঞ্চমার্ক" এমন কিছু নয় যা আমরা সম্পূর্ণ নির্ভুল মান চাইলে আমি খুব স্বাচ্ছন্দ্য বোধ করি])

[1] এই স্টাফের উত্স: আচ্ছা আপনি যদি লেসারসন এবং এই সহকারীর সাথে কাজ করে এমন কারও কাছ থেকে বক্তৃতা না পান তবে .. আমি তাদের কাগজপত্রকে একটি ভাল সূচনা পয়েন্ট বলে ধরে নিই। এই অ্যালগরিদমগুলি এখনও খুব কমই বর্ণিত হয়েছে - সিএলআর তাদের সম্পর্কে একটি একক পাদটীকা আছে। তবুও এটি মানুষকে অবাক করার এক দুর্দান্ত উপায়।


সম্পাদনা (দ্রষ্টব্য: আমি এই উত্তরটি পোস্ট করিনি এমন একজন নই; আমি কেবল এটি যুক্ত করতে চেয়েছিলাম):
উপরের কোডটির একটি সম্পূর্ণ সি ++ সংস্করণ এখানে:

template<class InIt, class OutIt>
void transpose(InIt const input, OutIt const output,
    size_t const rows, size_t const columns,
    size_t const r1 = 0, size_t const c1 = 0,
    size_t r2 = ~(size_t) 0, size_t c2 = ~(size_t) 0,
    size_t const leaf = 0x20)
{
    if (!~c2) { c2 = columns - c1; }
    if (!~r2) { r2 = rows - r1; }
    size_t const di = r2 - r1, dj = c2 - c1;
    if (di >= dj && di > leaf)
    {
        transpose(input, output, rows, columns, r1, c1, (r1 + r2) / 2, c2);
        transpose(input, output, rows, columns, (r1 + r2) / 2, c1, r2, c2);
    }
    else if (dj > leaf)
    {
        transpose(input, output, rows, columns, r1, c1, r2, (c1 + c2) / 2);
        transpose(input, output, rows, columns, r1, (c1 + c2) / 2, r2, c2);
    }
    else
    {
        for (ptrdiff_t i1 = (ptrdiff_t) r1, i2 = (ptrdiff_t) (i1 * columns);
            i1 < (ptrdiff_t) r2; ++i1, i2 += (ptrdiff_t) columns)
        {
            for (ptrdiff_t j1 = (ptrdiff_t) c1, j2 = (ptrdiff_t) (j1 * rows);
                j1 < (ptrdiff_t) c2; ++j1, j2 += (ptrdiff_t) rows)
            {
                output[j2 + i1] = input[i2 + j1];
            }
        }
    }
}

2
এটি প্রাসঙ্গিক হবে যদি আপনি বিভিন্ন আকারের ম্যাট্রিকের সাথে সময়ের তুলনা করেন, পুনরাবৃত্ত এবং পুনরুক্তি না করে। নির্দিষ্ট আকারের ম্যাট্রিক্সে পুনরাবৃত্ত সমাধানটি চেষ্টা করুন।
লুচিয়ান গ্রিগোর

@ লুচিয়ান যেহেতু আপনি ইতিমধ্যে ব্যাখ্যা করেছেন যে তিনি কেন আচরণটি দেখছেন তা আমি সাধারণভাবে এই সমস্যার একটি সমাধান প্রবর্তন করা বেশ আকর্ষণীয় বলে মনে করি।
ভু

কারণ, আমি প্রশ্ন করছি কেন বৃহত্তর ম্যাট্রিক্স প্রক্রিয়া করতে খুব কম সময় নেয়, দ্রুততর অ্যালগরিদমের সন্ধান না করে ...
লুচিয়ান গ্রিগোর

@ লুচিয়ান 16383 এবং 16384 এর মধ্যে পার্থক্যগুলি হ'ল .. এখানে আমার জন্য 28 বনাম 27 মিলিয়ন, বা প্রায় 3.5% - সত্যই তাৎপর্যপূর্ণ নয়। এবং আমি যদি অবাক হতাম।
ভু

3
এটি কী করে recursiveTransposeতা বোঝাতে আকর্ষণীয় হতে পারে , অর্থাত এটি ছোট টাইলস ( LEAFSIZE x LEAFSIZEমাত্রার) উপর কাজ করে যতটা ক্যাশে পূরণ করে না ।
ম্যাথিউ এম।

60

লুচিয়ান গ্রিগোরের উত্তরের ব্যাখ্যার উদাহরণ হিসাবে , এখানে x৪x64৪ এবং looks 65x65৫ ম্যাট্রিকের দুটি ক্ষেত্রে ম্যাট্রিক্স ক্যাশে উপস্থিতি কেমন দেখাচ্ছে (সংখ্যার বিবরণের জন্য উপরের লিঙ্কটি দেখুন)।

নীচে অ্যানিমেশনগুলিতে রঙগুলি নীচের অর্থ:

  • সাদা - ক্যাশে নেই,
  • হালকা সবুজ - ক্যাশে,
  • উজ্জ্বল সবুজ - ক্যাশে আঘাত,
  • কমলা - শুধু র‍্যাম থেকে পড়ুন,
  • লাল - ক্যাশে মিস।

64x64 কেস:

64x64 ম্যাট্রিক্সের জন্য ক্যাশে উপস্থিতি অ্যানিমেশন

লক্ষ করুন যে কীভাবে নতুন সারিতে প্রতিটি অ্যাক্সেসের ফলে ক্যাশে মিস হয় results এবং এখন এটি 65x65 ম্যাট্রিক্সের সাধারণ কেসটি কীভাবে সন্ধান করছে:

65x65 ম্যাট্রিক্সের জন্য ক্যাশে উপস্থিতি অ্যানিমেশন

আপনি এখানে দেখতে পারেন যে প্রাথমিক উষ্ণায়ণের পরে বেশিরভাগ অ্যাক্সেসগুলি হ'ল ক্যাশে হিট। এইভাবে সিপিইউ ক্যাশেটি সাধারণভাবে কাজ করার উদ্দেশ্যে।


উপরের অ্যানিমেশনগুলির জন্য ফ্রেম তৈরি করা কোডটি এখানে দেখা যাবে


উল্লম্ব স্ক্যানিং ক্যাশে হিটগুলি প্রথম ক্ষেত্রে সংরক্ষণ করা হয় নি, তবে তারা দ্বিতীয় ক্ষেত্রে রয়েছে? মনে হচ্ছে কোনও প্রদত্ত ব্লক উভয় উদাহরণের বেশিরভাগ ব্লকের জন্য একবারে অ্যাক্সেস করা হয়েছে।
জোশিয়াহ ইয়্ডার

আমি @ লুচিয়ানগ্রিগোরের উত্তর থেকে দেখতে পাচ্ছি যে এটি কারণ কলামের সমস্ত লাইন একই সংযুক্ত।
জোশিয়াহ ইয়্ডার

হ্যাঁ, দুর্দান্ত দৃষ্টান্ত। আমি দেখতে পাচ্ছি যে তারা একই গতিতে আছে। কিন্তু আসলে, তারা না, তাই না?
কেলালাকা

@ কেলালাকা হ্যাঁ, অ্যানিমেশন এফপিএস একই। আমি মন্থরতা অনুকরণ করিনি, কেবল এখানে রঙগুলি গুরুত্বপূর্ণ।
Ruslan

বিভিন্ন ক্যাশে সেট চিত্রিত করে দুটি স্থির চিত্র থাকা আকর্ষণীয় হবে।
জোশিয়াহ ইয়্ডার
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.