Skip to main content

მასივების სტრუქტურა სტრუქტურების მასივის წინააღმდეგ

Structure of Arrays vs Array of Structures

        ამ პოსტში ჩვენ განვიხილავთ მონაცემთა ორგანიზების 3 მექანიზმს და მათ შორის განსხვავებას:

  1. სტრუქტურების მასივს(Array of Structures).
  2. მასივების სტრუქტურას(Structure of Arrays).
  3. მასივების სტრუქტურების მასივი(Array of Structure of Arrays)
        განვიხილოთ მონაცემების პროცესორზე გადაცემის, კეშირების და დამუშავების მექანიზმი, რომელიც დეველოპერისგან მთლიანად დამალულია. პროცესორში ინსტრუქციების და მონაცემების გადატანა ხდება პერიოდულად და იმისთვის, რომ ყოველ ახალ ინსტრუქციაზე და მონაცემზე არ მოუწიოს წინ და უკან სიარული პროცესორში აქვს შესაბამისი კეშები(ინსტრუქციების და მონაცემების), ამიტომ პროგრამის მიმდინარეობის პროცესში, როდესაც ხდება მონაცემზე მიმართვა ხდება ამ მონაცემის მცირე მიდამოს გადატანა კეშში იმ იმედით, რომ შემდეგ ინსტრუქციებზე ეს მონაცემები შეიძლება დასჭირდეს. სწორედ ამ მიზეზის გამო, მონაცემების არასწორი ორგანიზების შემთხვევაში, როდესაც საჭირო ინფორმაციები გაბნეული არიან მეხსიერების სხვადასხვა ბლოკებში ვერ ხერხდება მონაცემების კეშის ეფექტური გამოყენება, რაც საბოლოოდ წარმადობაზე მკვეთრად აისახება.
სურათი ვიზუალურად ასახავს AoS-დან SoA-ში მონაცემების ტრანსფორმაციას.
მოვიყვანოთ სტრუქტურების მასივის მაგალითი:
//მონაცემთა სტრუქტურა რომელიც შედგება 4 float ელემენტისგან
struct color {
float r;
float g;
float b;
float a;
};

//მონაცემთა სტრუქტურა რომელიც ინახავს სტრუქტურების მასივს
struct pixelsAoS {
pixelsAoS(unsigned long int count) {
  size = count;
  rgba = new color[count];
}

~pixelsAoS() {
  delete[] rgba;
}

color* rgba;
unsigned long int size;
};
         ასეთ შემთხვევაში მონაცემები მეხსიერებაში განლაგდებიან შემდეგი თანმიმდევრობით: r1, g1, b1, a1, r2, g2, b2, a2, r3, g3, b3, a3, ..., rn, gn, bn, an. ახლა განვიხილოთ მასივების სტრუქტურის შემთხვევა. მოვიყვანოთ პროგრამული მაგალითი:
struct pixelsSoA {
pixelsSoA(unsigned long int count) {
  size = count;
  r = new float[count];
  g = new float[count];
  b = new float[count];
  a = new float[count];
}

~pixelsSoA() {
  delete[] r;
  delete[] g;
  delete[] b;
  delete[] a;
}

float* r;
float* g;
float* b;
float* a;
unsigned long int size;
};
        მასივების სტრუქტურის შემთხვევაში მონაცემები მეხსიერებაში გადალაგდებიან შემდეგნაირად: r1, r2, r3, r4, ...rn, g1, g2, g3, g4, ..., gn, b1, b2, b3, b4, ..., bn, a1, a2, a3, a4, ...an. მონაცემთა ჩაწერის ეს მეთოდი უფრო ეფექტურია კეშირების დროს, რადგან ასეთ ჩანაწერში ერთი და იგივე ველები მეხსიერებაში მიმდევრობით ხვდებიან და შესაბამისად კეშირებისას ნაკლებია იმის შანსი, რომ კეშში წამოღებულ მეხსიერების ნაწილს არასაჭირო ინფორმაცია წამოყვება. თუმცა ამ შემთხვევაში გასათვალისწინებელია კიდევ ერთი მნიშვნელოვანი რამ: რადგან ჩვენ პროცესორში გვაქვს ფიქსირებული ზომის კეში(რეალურად არსებობს რამოდენიმე დონის კეში სანამ ჩვენი მეხსიერება მიაღწევს პროცესორამდე), თუ ჩვენ გვექნება დიდი მასივები სტრუქტურაში და ისინი არ ჩაეტევიან კეშში, მაშინ კეშის მოქმედების ეფექტურობა ისევ დაეცემა. სწორედ ამიტომ გახდა მონაცემების კიდევ უფრო მოსახერხებელი ფორმით ჩაწერა რომელსაც ქვია მასივების სტრუქტურების მასივი. ასეთი ჩანაწერი საშუალებას გვაძლევს შევქმნათ მასივების სტრუქტურები რომელთაც ექნება სასურველი ზომა(ისეთი რომ კესში მთლიანად მოთავსდეს) და ამ ტიპის სტრუქტურის ელემენტები შევინახოთ მასივში. ასთი ტიპის ტრანსფორმაცია ზრდის არა მხოლოდ კეშის უშაობის ეფექტურობას არამედ ასევე მოსახერხებელია SIMD გამოთვლების დროს, რაც საბოლოო ჯამში ძალიან მკვეთრად მოქმედებს წარმადობაზე.

Comments

  1. This comment has been removed by the author.

    ReplyDelete
  2. დავამატებ იმას რომ
    new ოპერატორის გამოყენების შემთხვევაში მეხსიერების გამოყოფა ხდება RAM-ში (ოპერატიულში)
    როგორც სტატიაშია აღნიშნული მასივის შემთხვევაში მეხსიერების ელემენტები ერთმანეთის მიმდევრობით არის დალაგებული, როცა გამოიყენება სტრუქტურების მასივი იმისათვის რომ მოხდეს მაგალითად
    50-ე ელემენთის b ცვლადის მნიშვნელობის წაკითხვა ხდება შემდეგი ოპერაციები
    პირველ რიგში გამოითვლება color სტრუქტურის ზომა (შეიძლება ჩაითვალოს რომ ეს კონსტანტა ამიტომ ამაზე დრო არც იხარჯება)
    color სტრუქტურა შეიცავს 4 ცალ float ტიპის ცვლადს ხოლო float-ის ზომა არის 32 ბიტი ანუ (4 ბაიტი)
    ანუ color სტრუქტურის ზომა გამოდის 32 * 4 = 128 ბიტი ხოლო ბაიტებში 4 * 4 = 16 ბაიტს
    შესაბამისად 50-ე ელემენტის მისამართი იქნება 49 * 16 = 784 (49-ზე მრავლდება იმის გამო რომ ჩვენ გვინდა 50-ე ელემენტის დასაწყისი ანუ 49-ე ელემენტი ბოლოს მიმატებული 1 იქნება 50 ელემენტის პირველი ბაიტი) ანუ მასივის საწყისი მისამართიდან უნდა გადავთვალოთ 49 ცალი 16 ბაიტი და მივიღებთ 50-ე ელემენტის დასაწყისს ხოლო b ცვლადი color სტრუქტურაში არის 3-ე ცვლადი რაც ნიშნავს იმას რომ 50 ელემენტის დასაწყისიდან კიდევ უნდა გადავთვალოთ კიდებ 2 ცალი 32 ბიტი (ჯამში 64 ბიტი) ანუ 8 ბაიტი მივადგებით მესამე ცვლადის დასაწყისს... ამ ადგილიდან უნდა წავიკითხოთ წავიკითხოთ 32 ბიტი ანუ 4 ბაიტი და ეს იქნება 50-ე ელემენტის b ცვლადის მნიშვნელობა როგორც შეამჩნიეთ ხდება რამდენიმე გამოთვლითი ოპერაცია იმისათვის რომ 1 ცვლადის მნიშვნელობა ამოვიღოთ რაც მოიცავს მეხსიერებასთან რამოდენიმე მიმართვასაც (ცუდ შემთხვევაში ხოლო კარგ შემთხვევაში 50-ე ელემენთტს პროცესორი მთლიანად წაიღებს კეშში და შემდეგ წაიკითხავს მის 3-ე ცვლადის მნიშვნელობას) ასევე ამ ყველაფერს თუ დავუმათებთ მეხსიერებასთან მიმართვის ოპერაციებს რომელიც საკმაოდ ხანგრძლივი პროცესია (გსმენიათ ალბათ ოპერატიული მეხსირების Timing-ების შესახებ მაგალითად 5-5-5-15 ან 9-9-9-24... ეს ციფრები რეალურად არის პროცესორის ტაქტების რაოდენობა რომლის განმაბლობაშიც იგი ელოდება ოეპრატიულ მეხსიერებას რადგან მეხსიერება არ არის ისეთი სწრაფი როგორც პროცესორი... პირველ მაგალითში როგორც ხედავთ ჯამში არის 30 ტაქტი რომელიც იხარჯება მონაცემების წაკითვისას ოეპრატიულიდან)
    უბრალოდ მასივების გამოყენების შემთხვევაში როგორც არის ნაჩვენები მეორე მაგალითში
    პირდაპირ ხდება მისამათღების განსაზღვრა მაგალითად r მასივიდან 50-ე ელემენტის წაკითხვა არის ერთი მიმართვის ოპერაცია ანუ 4 მასივიდან მონაცემების წაკითხვა უდრის ოპერატიულზე 4 მიმართვის ოეპრაციას, ამიტომ დამოკიდებულია ოეპრაციების ტიპზე რომელიც უნდა ჩაატაროთ მონაცემებზე და ამის მიხედვით უნდა განსაზღვროთ თუ რომელი სტრუქტურით გირჩევნიათ მონაცემების აღწერა მეხსიერებაში (მასივების სახით თუ სტრუქტურების მასივის სახით)
    რადგან როცა პროცესორი ხედავ რომ ინსტრუქციაში ფიგურირებს არა მნიშვნელობა არამედ მეხსირების მისამართი საიდანაც უნდა მოხდეს მნიშვნელობის წაკითხვა იგი პირველ რიგში ცდილობს ამ მნიშვნელობის L2 კეშში პოვნას თუ ვერ იპოვა მიდის ოპერატიულში და იქიდან მოაქვს ეს მნიშვნელობები და ინახავს L2 კეშში და შემდეგ იყენებს მათ... ამიტომ არის L2 კეში ყოველთვის დიდი ზომის რადგან მეორედ დონის ეშ მეხსიერებაში მონაცემები ყოველთვის მოდის ოეპრატიულიდან Bulk კოპირების სახით რომ წინასწარ ჰქონდეს ის მონაცემები რომლების შეიძლება დაჭირდეს პროცესორს... ამ თემასთან დაკავშირებით საკმაოდ კარგი და ვცელი სტატიები დევს wasm.ru-ზე და ეს საკითხის და ზოგადად პროცესორების მუშაობის პრინციპი გარჩეულია სხვადასხვა მწარმოებლების სხვადასხვა პროცესორების დონეზე და არა ზოგადი კონცეპციის სახით ;)

    ReplyDelete
  3. მშვენიერია ვაჟა, კარგია რომ გესმის თემა. დიდი და ვრცელი სტატიის დაწერას ყოველთვის ვერიდები, რადგან აბნევს მკითხველს. პირველი ეტაპისთვის ზოგადი სახით მიმოვიხილე რაშია მთავარი იდეა ჩადებული და საერთოდ რომ უნდა მიექცეს ასეთ რამეს ყურადღება პროგრამირებისას. ხშირ შემთხვევაში SoA-ს სახით აღწერა კოდის კითხვადობას ამცირებს და დიზაინს არღვევს, ამიტომ თუ კრიტიკული არაა მოთხოვნა მაქსიმალურ სისწრაფეზე თავს არიდებენ ხოლმე გამოყენებას. :)

    ReplyDelete

Post a Comment

Popular posts from this blog

CPU GPU და ჰიბრიდული რენდერერები

წყარო         დღემდე აქტუალურია თემა CPU რენდერერი ჯობია თუ GPU . იმისათვის რომ ამ კითხვას მეტნაკლებად ამომწურავი პასუხი გავცეთ განვიხილოთ რენდერერის სტრუქტურა და მოცემულ პლათფორმებზე იპმლემენტაციასთან დაკავშირებული პრობლემები. რენდერერი შედგება რამოდენიმე დიდი კომპონენტისგან როგორიცაა ხილვადობის ამოცანა შეფერადება ინტეგრატორები ფუნქციონალი ხილვადობის ამოცანა         ხილვადობის ამოცანა ერთერთი ყველაზე რთულია გამოთვლითი რესურსის კუთხით. გარდა იმისა, რომ სხივის გეომეტრიასთან თანაკვეთის დათვლას საკმაოდ დიდი დრო ჭირდება, ასევე საჭიროა ამაჩქარებელ სტრუქტურების განახლება კადრიდან კადრზე დინამიური სცენებისათვის. კარგი ისაა, რომ რენდერერის ეს ნაწილი საკმაოდ ადვილად ენკაპსულირებადია და შესაბამისად გვხვდება ბიბლიოთეკები მაგალითად embree(intel), fireRays(AMD), OptiX prime(nvidia), ... რომლებიც ამ ამოცანას საკმაოდ ეფექტურად ხსნიან და რენდერერებშიც მეტნაკლებად ადვილად ინტეგრირდებიან.  სხივების მიდევნების პროცესში ძალიან მნიშვნელოვანია მსგავსი გამოთვლების ლოკალიზება და არსებული SIMD

სინათლის ხილული სპექტრი და სხივის თვისებები

Visible Spectrum სურათზე ნაჩვენებია პრიზმაში გამავალი თეთრი სხივის სპექტრულად გაშლის პროცესი.         სინათლე წარმოადგენს ელექტრომაგნიტურ ტალღას, რომელსაც როგორც ყველა ელექტრომაგნიტურ ტალღას გააჩნია რამოდენიმე მნიშვნელოვანი მახასიათებელი. ერთერთი მნიშვნელოვანი მახასიათებელი არის ტალღის სიგრძე, რომელიც განსაზღვრავს სხივის სპექტრულ ფერს. ელექტრომაგნიტური ტალღები ბუნებაში და თანამედროვე სამყაროში მრავლად გვხვდები. სხვადასხვა ტალთის სიგრძის(სიხშირის) ტალღებს იყენებენ როგორც საყოფაცხოვრებო(რადიო, მობილური ტელეფონი) დანიშნულების, ასევე სამედიცინო(რენდგენის სხივები) და სამხედრო(რადარები) მოწყობილობებში. ადამიანის თვალისთვის ხილული სინათლის ელექტრომაგნიტური ტალღების ტალღის სიგრძე იწყება დაახლოებით 400 ნანომეტრიდან და მთავრდება 700 ნანომეტრზე. ამ დიაპაზონს ქვემოთ ექცევა ულტრაიისფერი ტალღები და დიაპაზონს ზემოთ ექცევა ინფრაწითელი, რომელსაც ადამიანის თვალი ვერ აღიქვამს(იხილეთ ქვემოთ მოცემული სურათი). სინათლის თეთრი სხივი შედგება სხვადასხვა სიხშირის ტალღების ერთობლიობისგან.        

ფერების RGB მოდელი

RGB Color Model         ფერების RGB მოდელი წარმოადგენს ისეთ მოდელს რომელშიც სამი ძრირითადი ფერის წითელი, მწვანე და ლურჯის საშუალებით მიიღება ფერების ფართო სპექტრი. მისი დასახელებაც მოდის სწორედ ძირითადი ფერების ინგლისური სახელწოდების ინიციალებიდან(Red, Green, Blue).         ფერთა სპექტრის ამდაგვარი წარმოდგენა დაკავშირებულია იმასთან, რომ გამოსახულების გამოტანის მოწყობილობებში რომელიც გააჩნიათ კომპიუტერებს, ტელევიზორებს ფერის მიღება ფიზიკურად ხდება სწორედ ამ სამი ძირითადი ფერის შეზავებით. დღესდღეობით ყველაზე გავრცელებული არის 24 ბიტიანი RGB მოდელი, სადაც თითოეულ კომპონენტს ეთმობა ერთი ბაიტი და შესაბამისად შეუძლია მიიღოს ნებისმიერი მნიშვნელობა [0, 255] დიაპაზონში, რაც საბოლოოდ გვაძლევს 16777216 განსხვავებულ ფერს.