লুপগুলির ক্রমটি কেন 2 ডি অ্যারেতে পুনরাবৃত্তি করার সময় কার্য সম্পাদনকে প্রভাবিত করে?


359

নীচে দুটি প্রোগ্রাম রয়েছে যা প্রায় একইরকম বাদে আমি প্রায় iএবং jভেরিয়েবলগুলি স্যুইচ করেছি । তারা উভয় সময় বিভিন্ন পরিমাণে চালানো। কেউ ব্যাখ্যা করতে পারে কেন এমন হয়?

সংস্করণ 1

#include <stdio.h>
#include <stdlib.h>

main () {
  int i,j;
  static int x[4000][4000];
  for (i = 0; i < 4000; i++) {
    for (j = 0; j < 4000; j++) {
      x[j][i] = i + j; }
  }
}

সংস্করণ 2

#include <stdio.h>
#include <stdlib.h>

main () {
  int i,j;
  static int x[4000][4000];
  for (j = 0; j < 4000; j++) {
     for (i = 0; i < 4000; i++) {
       x[j][i] = i + j; }
   }
}


7
আপনি কিছু মানদণ্ডের ফলাফল যুক্ত করতে পারেন?
naught101


14
@ naught101 মানদণ্ডগুলি 3 থেকে 10 বারের মধ্যে যে কোনও জায়গায় পারফরম্যান্সের পার্থক্য প্রদর্শন করবে। এটি মৌলিক সি / সি ++, আমি কীভাবে এত বেশি ভোট পেয়েছি তা সম্পর্কে পুরোপুরি স্ট্যাম্পড ...
টিসি 1

12
@ টিসি 1: আমি মনে করি না যে এটি প্রাথমিক; অন্তর্বর্তী হতে পারে। তবে এতে অবাক হওয়ার কিছু নেই যে "মৌলিক" জিনিসগুলি আরও বেশি লোকের পক্ষে উপযোগী হতে পারে, তাই অনেকগুলি উত্সাহ। তদতিরিক্ত, এটি এমন একটি প্রশ্ন যা গুগল করা শক্ত, এমনকি যদি এটি "বেসিক" হয়।
LarsH

উত্তর:


594

অন্যদের বলেছি, বিষয়টি অ্যারে মেমরি অবস্থান দোকান হল: x[i][j]। এখানে কিছুটা অন্তর্দৃষ্টি কেন:

আপনার কাছে একটি দ্বিমাত্রিক অ্যারে রয়েছে, তবে কম্পিউটারে মেমরিটি সহজাতভাবে 1-মাত্রিক। সুতরাং আপনি যখন নিজের অ্যারেটি কল্পনা করবেন তখন:

0,0 | 0,1 | 0,2 | 0,3
----+-----+-----+----
1,0 | 1,1 | 1,2 | 1,3
----+-----+-----+----
2,0 | 2,1 | 2,2 | 2,3

আপনার কম্পিউটার এটিকে একক লাইন হিসাবে স্মৃতিতে সঞ্চয় করে:

0,0 | 0,1 | 0,2 | 0,3 | 1,0 | 1,1 | 1,2 | 1,3 | 2,0 | 2,1 | 2,2 | 2,3

২ য় উদাহরণে, আপনি প্রথমে ২ য় নম্বর লুপ করে অ্যারে অ্যাক্সেস করেন,

x[0][0] 
        x[0][1]
                x[0][2]
                        x[0][3]
                                x[1][0] etc...

এর অর্থ হ'ল আপনি তাদের সবাইকে ক্রমে ক্রমে আঘাত করছেন। এখন 1 ম সংস্করণ দেখুন। আপনি করছেন:

x[0][0]
                                x[1][0]
                                                                x[2][0]
        x[0][1]
                                        x[1][1] etc...

যেভাবে মেমরিতে সি 2-ডি অ্যারে রেখেছিল, আপনি এটিকে পুরো জায়গা জুড়ে লাফিয়ে বলতে বলছেন। তবে এখন কিকারের জন্য: এই বিষয়টি কেন? সমস্ত স্মৃতি অ্যাক্সেস একই, তাই না?

না: ক্যাশের কারণে। আপনার স্মৃতি থেকে ডেটা সিপিইউতে সামান্য অংশে আনা হয় (যাকে 'ক্যাশে লাইন' বলা হয়) সাধারণত 64৪ বাইট। আপনার যদি 4-বাইট পূর্ণসংখ্যা থাকে, তার অর্থ আপনি একটি ঝরঝরে সামান্য বান্ডেলে টানা 16 টি পূর্ণসংখ্যা পেয়ে যাচ্ছেন। মেমরির এই অংশগুলি আনতে আসলে এটি যথেষ্ট ধীর; আপনার সিপিইউ একক ক্যাশে লাইনটি লোড হতে সময় নেয়ায় অনেক কাজ করতে পারে।

এখন অ্যাক্সেসের ক্রমের দিকে ফিরে তাকাুন: দ্বিতীয় উদাহরণটি হল (1) ১ in টি ইন্টের কিছু অংশ ধরা, (২) সমস্তটি সংশোধন করা, (৩) 4000 * 4000/16 বার পুনরাবৃত্তি করুন। এটি দুর্দান্ত এবং দ্রুত এবং সিপিইউতে সর্বদা কিছু কাজ করার থাকে।

প্রথম উদাহরণটি হ'ল (1) ১ in টি ইন্টের কিছু অংশ ধরুন, (২) এর মধ্যে কেবল একটিটিকে সংশোধন করুন, (৩) 4000 * 4000 বার পুনরাবৃত্তি করুন। এর জন্য মেমরি থেকে "ফেচ" সংখ্যার 16 গুণ বেশি প্রয়োজন। আপনার সিপিইউতে প্রকৃতপক্ষে সেই স্মৃতিটি প্রদর্শিত হওয়ার অপেক্ষায় বসে প্রায় সময় ব্যয় করতে হবে এবং আপনি যখন বসে আছেন তখন আপনি মূল্যবান সময় নষ্ট করছেন।

গুরুত্বপূর্ণ তথ্য:

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

0,0 | 1,0 | 2,0 | 0,1 | 1,1 | 2,1 | 0,2 | 1,2 | 2,2 | 0,3 | 1,3 | 2,3

সি এর বিন্যাসকে 'সারি-মেজর' এবং ফোর্টরানসকে 'কলাম-মেজর' বলা হয়। আপনি দেখতে পাচ্ছেন, আপনার প্রোগ্রামিং ভাষাটি সারি-প্রধান বা কলাম-প্রধান কিনা তা জানা খুব গুরুত্বপূর্ণ! আরও তথ্যের জন্য এখানে একটি লিঙ্ক রয়েছে: http://en.wikedia.org/wiki/Row-major_order


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

7
আপনার কাছে "প্রথম" এবং "দ্বিতীয়" সংস্করণটি ভুল উপায়ে রয়েছে; প্রথম উদাহরণটি অভ্যন্তরীণ লুপের প্রথম সূচকে পরিবর্তিত হয় এবং এটি ধীর সম্পাদনকারী উদাহরণ হবে।
ক্যাফে

দুর্দান্ত উত্তর। মার্ক যদি এই জাতীয় কৌতূহল সম্পর্কে আরও পড়তে চান তবে আমি গ্রেট কোড রাইনের মতো একটি বইয়ের প্রস্তাব দেব।
wkl

8
ফোর্ত্রান থেকে সিটি সারি ক্রমটি বদলেছে তা নির্দেশ করার জন্য বোনাস পয়েন্ট। বৈজ্ঞানিক কম্পিউটিংয়ের জন্য এল 2 ক্যাশের আকার হ'ল সব কিছু কারণ যদি আপনার সমস্ত অ্যারে এল 2-তে ফিট করে তবে মূল স্মৃতিতে না গিয়ে গণনা শেষ করা যায়।
মাইকেল শপসিন

4
@ বিবারিরি: প্রতিটি প্রোগ্রামার মেমোরি সম্পর্কে নিখরচায়ভাবে উপলভ্য হওয়া ভাল পঠনযোগ্য।
ক্যাফে

68

সমাবেশের সাথে কিছুই করার নেই। এটি ক্যাশে মিস করার কারণে ।

সি বহুমাত্রিক অ্যারেগুলি দ্রুততম হিসাবে শেষ মাত্রা সহ সঞ্চিত হয়। সুতরাং প্রথম সংস্করণ প্রতিটি পুনরাবৃত্তিতে ক্যাশে মিস করবে, তবে দ্বিতীয় সংস্করণটি তা করবে না। সুতরাং দ্বিতীয় সংস্করণটি যথেষ্ট দ্রুত হওয়া উচিত।

আরও দেখুন: http://en.wikedia.org/wiki/Loop_inter بدل


23

সংস্করণ ২ আরও দ্রুত চলবে কারণ এটি আপনার কম্পিউটারের ক্যাশেটি সংস্করণ ১ এর চেয়ে ভাল ব্যবহার করে you আপনি যখন অ্যারেতে কোনও উপাদানটির জন্য অনুরোধ করবেন তখন আপনার ওএস সম্ভবত একটি মেমরি পৃষ্ঠায় ক্যাশে আনবে যা সেই উপাদানটি ধারণ করে। তবে, যেহেতু পরের কয়েকটি উপাদানগুলিও সেই পৃষ্ঠাটিতে রয়েছে (কারণ সেগুলি স্বচ্ছল) তাই পরবর্তী অ্যাক্সেসটি ইতিমধ্যে ক্যাশে থাকবে! এটির গতি বাড়ানোর জন্য এটি সংস্করণ 2 কী করছে।

সংস্করণ 1, অন্যদিকে, উপাদানগুলি কলাম অনুসারে নয়, সারি অনুসারে নয় access এই ধরণের অ্যাক্সেস মেমোরি স্তরে সামঞ্জস্যপূর্ণ নয়, সুতরাং প্রোগ্রামটি ওএস ক্যাশে যতটা সুবিধা নিতে পারে না।


এই অ্যারে মাপগুলির সাথে, সম্ভবত সিপিইউ-তে ওএসের পরিবর্তে ক্যাশে ম্যানেজার এখানে দায়বদ্ধ।
krlMLr

12

কারণটি হ'ল ক্যাশে-স্থানীয় ডেটা অ্যাক্সেস। দ্বিতীয় প্রোগ্রামে আপনি মেমরির মাধ্যমে রৈখিক স্ক্যান করছেন যা ক্যাচিং এবং প্রিফেচিং থেকে উপকৃত হয়। আপনার প্রথম প্রোগ্রামটির মেমরির ব্যবহারের ধরণটি আরও বেশি ছড়িয়ে গেছে এবং এর ফলে আরও খারাপ ক্যাশের আচরণ রয়েছে।


11

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

  for (j=0; j<4000; j++) {
    int *p = x[j];
    for (i=0; i<4000; i++) {
      *p++ = i+j;
    }
  }

এটি প্রথম লুপটির জন্য কম সম্ভবত, কারণ এটি প্রতি বার 4000 দিয়ে পয়েন্টার "পি" বৃদ্ধি করতে হবে।

সম্পাদনা: p++ এবং এমনকি *p++ = ..বেশিরভাগ সিপিইউতে একটি সিপিইউ নির্দেশে সংকলন করা যায়। *p = ..; p += 4000পারবেন না, সুতরাং এটি অনুকূলকরণের কম সুবিধা আছে। এটি আরও বেশি কঠিন, কারণ সংকলকটির অভ্যন্তরীণ অ্যারের আকার জানতে এবং ব্যবহার করা দরকার। এবং এমনটি ঘটে না যে প্রায়শই স্বাভাবিক কোডের অভ্যন্তরীণ লুপে (এটি কেবলমাত্র বহু-মাত্রিক অ্যারেগুলির জন্য ঘটে, যেখানে শেষ সূচকটি লুপে স্থির রাখা হয়, এবং দ্বিতীয় থেকে শেষের দিকে একটি পদক্ষেপ নেওয়া হয়), সুতরাং অপ্টিমাইজেশনের অগ্রাধিকার কম হয় না ।


আমি কী পাই না কারণ এর জন্য প্রতিবার 4000 দিয়ে পয়েন্টারটি "পি" ঝাঁপিয়ে পড়তে হবে "এর অর্থ।
Veedrac

@ উইড্রাক পয়েন্টারটি অভ্যন্তরীণ লুপের ভিতরে 4000 দিয়ে বাড়ানো দরকার: p += 4000আইসোp++
ফিশাইনার

সংকলক কেন এটি একটি সমস্যা খুঁজে পাবে? iইতিমধ্যে এটি একটি পয়েন্টার ইনক্রিমেন্ট প্রদত্ত একটি নন-ইউনিট মান দ্বারা বর্ধিত হয়েছে।
Veedrac

আমি আরও ব্যাখ্যা যোগ করেছি
ফিশাইনার

Gcc.godbolt.org এ টাইপ int *f(int *p) { *p++ = 10; return p; } int *g(int *p) { *p = 10; p += 4000; return p; }করার চেষ্টা করুন । দুটোই মূলত একইরকম সংকলন করে বলে মনে হচ্ছে।
Veedrac

7

এই লাইনটি অপরাধী:

x[j][i]=i+j;

দ্বিতীয় সংস্করণ অবিচ্ছিন্ন মেমরি ব্যবহার করে এইভাবে যথেষ্ট দ্রুত হবে।

আমি চেষ্টা করেছিলাম

x[50000][50000];

এবং সম্পাদনের সময়টি ভার্সন 1 এর জন্য সংস্করণ 1 এর জন্য 0.6 এর তুলনায় 13 সেকেন্ড।


4

আমি একটি জেনেরিক উত্তর দেওয়ার চেষ্টা করি।

কারণ সি এর i[y][x]জন্য একটি সংক্ষিপ্তকরণ *(i + y*array_width + x)(শ্রেণীর চেষ্টা করুন int P[3]; 0[P] = 0xBEEF;) try

আপনি যখন পুনরাবৃত্তি করবেন y, আপনি আকারের অংশগুলি দিয়ে পুনরাবৃত্তি করুন array_width * sizeof(array_element)। যদি আপনার এটি আপনার অভ্যন্তরের লুপে থাকে তবে আপনার array_width * array_heightসেই অংশগুলির উপর পুনরাবৃত্তি হবে ।

অর্ডারটি উল্টিয়ে দেওয়ার মাধ্যমে আপনার কেবল array_heightখণ্ড-পুনরাবৃত্তি হবে এবং যে কোনও খণ্ড- array_widthপুনরাবৃত্তির মধ্যে আপনার কেবলমাত্র পুনরাবৃত্তি হবে sizeof(array_element)

সত্যিকারের পুরানো x86-সিপিইউতে থাকাকালীন এটি খুব বেশি কিছু যায় আসে না, আজকাল 'x86 ডেটা প্রিফেচিং এবং ক্যাশে করে। আপনি সম্ভবত আপনার ধীর পুনরাবৃত্তির ক্রমটিতে অনেকগুলি ক্যাশে মিস করেছেন।

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