Mengoptimumkan Penggunaan Memori dalam Go: Menguasai Penjajaran Struktur Data

Barbara Streisand
Lepaskan: 2024-11-16 09:54:03
asal
421 orang telah melayarinya

Pengoptimuman memori adalah penting untuk menulis sistem perisian berprestasi. Apabila perisian mempunyai jumlah memori yang terhad untuk digunakan, banyak isu boleh timbul apabila memori itu tidak digunakan dengan cekap. Itulah sebabnya pengoptimuman memori adalah penting untuk prestasi keseluruhan yang lebih baik.

Go mewarisi banyak ciri berfaedah C, tetapi apa yang saya perhatikan ialah sebahagian besar orang yang menggunakannya tidak mengetahui kuasa penuh bahasa ini. Salah satu sebabnya mungkin kekurangan pengetahuan tentang cara ia berfungsi pada tahap rendah, atau kekurangan pengalaman dengan bahasa seperti C atau C . Saya menyebut C dan C kerana asas Go cukup banyak dibina berdasarkan ciri-ciri indah C/C . Bukan kebetulan saya akan memetik temu bual Ken Thompson di Google I/O 2012:

Bagi saya, sebab saya bersemangat tentang Go adalah kerana hampir pada masa yang sama kami memulakan Go, saya membaca (atau cuba membaca) piawaian yang dicadangkan C 0x, dan itu adalah meyakinkan untuk saya.

Hari ini, kami akan bercakap tentang cara kami boleh mengoptimumkan program Go kami, dan lebih khusus lagi, cara yang baik untuk menggunakan struct dalam Go. Mari kita nyatakan dahulu apa itu struktur:

Struktur ialah jenis data yang ditentukan pengguna yang mengumpulkan pembolehubah berkaitan jenis berbeza di bawah satu nama.

Untuk memahami sepenuhnya di mana masalahnya, kami akan menyebut bahawa pemproses moden tidak membaca 1 bait pada satu masa dari memori. Bagaimana CPU mengambil data atau arahan yang disimpan dalam memori?

Dalam seni bina komputer, perkataan ialah unit data yang boleh dikendalikan oleh pemproses dalam satu operasi - secara amnya unit memori terkecil yang boleh dialamatkan. Ia adalah kumpulan bit bersaiz tetap (digit binari). Saiz perkataan pemproses menentukan keupayaannya untuk mengendalikan data dengan cekap. Saiz perkataan biasa termasuk 8, 16, 32, dan 64 bit. Sesetengah seni bina pemproses komputer menyokong separuh perkataan, iaitu separuh daripada bilangan bit dalam perkataan, dan kata ganda, iaitu dua perkataan bersebelahan.

Pada masa kini seni bina yang paling biasa ialah 32 bit dan 64 bit. Jika anda mempunyai pemproses 32 bit maka ia bermakna ia boleh mengakses 4 bait pada satu masa yang bermaksud saiz perkataan ialah 4 bait. Jika anda mempunyai pemproses 64 bit, ia boleh mengakses 8 bait pada masa yang bermaksud saiz perkataan ialah 8 bait.

Apabila kami menyimpan data dalam ingatan, setiap perkataan data 32-bit mempunyai alamat yang unik, seperti yang ditunjukkan di bawah.

Optimizing Memory Usage in Go: Mastering Data Structure Alignment

Rajah. 1 ‑ Memori Boleh Alamat Perkataan

Kita boleh membaca data dalam ingatan dan memuatkannya ke satu daftar menggunakan arahan muatkan perkataan (lw).

Setelah mengetahui teori di atas mari kita lihat apakah amalannya. Untuk huraian kes dengan struktur data struktur, saya akan menunjukkan dengan bahasa C. Struct dalam C ialah jenis data komposit yang membolehkan anda mengumpulkan berbilang pembolehubah bersama-sama dan menyimpannya dalam blok memori yang sama. Seperti yang kami katakan awal CPU mengakses data bergantung kepada seni bina yang diberikan. Setiap jenis data dalam C akan mempunyai keperluan penjajaran.

Jadi mari kita mempunyai yang berikut sebagai struktur mudah:

// structure 1
typedef struct example_1 {
    char c;
    short int s;
} struct1_t;


// structure 2
typedef struct example_2 {
    double d;
    int s;
    char c;
} struct2_t;
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Dan sekarang cuba kira saiz struktur berikut:

Saiz Struktur 1 = Saiz (char short int) = 1 2 = 3.

Saiz Struktur 2 = Saiz (double int char) = 8 4 1= 13.

Saiz sebenar menggunakan program C mungkin mengejutkan anda.

#include <stdio.h>


// structure 1
typedef struct example_1 {
    char c;
    short int s;
} struct1_t;

// structure 2
typedef struct example_2 {
    double d;
    int s;
    char c;
} struct2_t;

int main()
{
    printf("sizeof(struct1_t) = %lu\n", sizeof(struct1_t));
    printf("sizeof(struct2_t) = %lu\n", sizeof(struct2_t));

    return 0;
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Output

sizeof(struct1_t) = 4
sizeof(struct2_t) = 16
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Seperti yang kita dapat lihat, saiz struktur berbeza daripada yang kami kira.

Apakah sebabnya?

C dan Go menggunakan teknik yang dikenali sebagai "padding struct" untuk memastikan data diselaraskan dengan betul dalam ingatan, yang boleh menjejaskan prestasi dengan ketara disebabkan oleh kekangan perkakasan dan seni bina. Pelapik dan penjajaran data mematuhi keperluan seni bina sistem, terutamanya untuk mengoptimumkan masa capaian CPU dengan memastikan sempadan data sejajar dengan saiz perkataan.

Mari kita lihat contoh untuk menggambarkan cara Go mengendalikan pelapik dan penjajaran, pertimbangkan struct berikut:

type Employee struct {
  IsAdmin  bool
  Id       int64
  Age      int32
  Salary   float32
}
Salin selepas log masuk
Salin selepas log masuk

Bool ialah 1 bait, int64 ialah 8 bait, int32 ialah 4 bait dan float32 ialah 4 bait = 17 bait(jumlah).

Mari sahkan saiz struct dengan memeriksa program Go yang disusun:

package main

import (
    "fmt"
    "unsafe"
)

type Employee struct {
    IsAdmin bool
    Id      int64
    Age     int32
    Salary  float32
}

func main() {

    var emp Employee

    fmt.Printf("Size of Employee: %d\n", unsafe.Sizeof(emp))
}
Salin selepas log masuk

Output

Size of Employee: 24
Salin selepas log masuk

Saiz yang dilaporkan ialah 24 bait, bukan 17. Percanggahan ini disebabkan oleh penjajaran memori. Untuk memahami cara penjajaran berfungsi, kita perlu memeriksa struktur dan memvisualisasikan memori yang didudukinya.

Optimizing Memory Usage in Go: Mastering Data Structure Alignment

Rajah 2 - Reka Letak Memori Tidak Dioptimumkan

Struktur Pekerja akan menggunakan 8*3 = 24 bait. Anda lihat masalahnya sekarang, terdapat banyak lubang kosong dalam susun atur Pekerja (jurang yang dicipta oleh peraturan penjajaran dipanggil "padding").

Pengoptimuman Padding dan Kesan Prestasi

Memahami cara penjajaran memori dan pelapik boleh menjejaskan prestasi aplikasi adalah penting. Khususnya, penjajaran data memberi kesan kepada bilangan kitaran CPU yang diperlukan untuk mengakses medan dalam struktur. Pengaruh ini kebanyakannya timbul daripada kesan cache CPU, dan bukannya kitaran jam mentah itu sendiri, kerana tingkah laku cache sangat bergantung pada lokaliti data dan penjajaran dalam blok memori.

CPU moden mengambil data daripada memori ke dalam perantara yang lebih pantas dipanggil cache, disusun dalam blok bersaiz tetap (biasanya 64 bait). Apabila data diselaraskan dengan baik dan disetempatkan dalam talian cache yang sama atau lebih sedikit, CPU boleh mengaksesnya dengan lebih cepat disebabkan oleh pengurangan operasi pemuatan cache.

Pertimbangkan struktur Go berikut untuk menggambarkan penjajaran lemah berbanding optimum:

// structure 1
typedef struct example_1 {
    char c;
    short int s;
} struct1_t;


// structure 2
typedef struct example_2 {
    double d;
    int s;
    char c;
} struct2_t;
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Bagaimana Penjajaran Mempengaruhi Prestasi

CPU membaca data dalam saiz perkataan dan bukannya saiz bait. Seperti yang saya jelaskan pada permulaan perkataan dalam sistem 64-bit ialah 8 bait, manakala perkataan dalam sistem 32-bit ialah 4 bait. Ringkasnya, CPU membaca alamat dalam berbilang saiz perkataannya. Untuk mengambil passportId berubah, CPU kami mengambil dua kitaran untuk mengakses data dan bukannya satu kitaran. Kitaran pertama akan mengambil memori 0 hingga 7 dan kitaran seterusnya akan mengambil yang lain. Dan ini adalah tidak cekap- kami memerlukan penjajaran struktur data. Dengan hanya menjajarkan data, komputer memastikan bahawa var passportId boleh diambil dalam kitaran SATU CPU.

Optimizing Memory Usage in Go: Mastering Data Structure Alignment

Rajah 3 - Membandingkan Kecekapan Capaian Memori

Padding ialah kunci untuk mencapai penjajaran data. Padding berlaku kerana CPU moden dioptimumkan untuk membaca data daripada memori pada alamat sejajar. Penjajaran ini membolehkan CPU membaca data dalam satu operasi.

Optimizing Memory Usage in Go: Mastering Data Structure Alignment

Rajah 4 - Hanya menjajarkan data

Tanpa padding, data mungkin tidak sejajar, membawa kepada berbilang akses memori dan prestasi yang lebih perlahan. Oleh itu, walaupun padding mungkin membazirkan sedikit memori, ia memastikan program anda berjalan dengan cekap.

Strategi Pengoptimuman Padding

Struktur sejajar menggunakan memori yang lebih sedikit kerana ia mempunyai susunan medan struct yang lebih baik berbanding dengan Tidak Sejajar. Disebabkan pelapik, dua struktur data 13 bait menjadi 16 bait dan 24 bait masing-masing. Oleh itu, anda menjimatkan memori tambahan dengan hanya menyusun semula medan struct anda.

Optimizing Memory Usage in Go: Mastering Data Structure Alignment

Rajah 5 - Mengoptimumkan Susunan Medan

Data yang tidak sejajar boleh memperlahankan prestasi kerana CPU mungkin memerlukan berbilang kitaran untuk mengakses medan tidak sejajar. Sebaliknya, data yang dijajarkan dengan betul meminimumkan beban talian cache, yang penting untuk prestasi, terutamanya dalam sistem yang kelajuan memori menjadi halangan.

Mari kita lakukan penanda aras mudah untuk membuktikannya:

#include <stdio.h>


// structure 1
typedef struct example_1 {
    char c;
    short int s;
} struct1_t;

// structure 2
typedef struct example_2 {
    double d;
    int s;
    char c;
} struct2_t;

int main()
{
    printf("sizeof(struct1_t) = %lu\n", sizeof(struct1_t));
    printf("sizeof(struct2_t) = %lu\n", sizeof(struct2_t));

    return 0;
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Output

sizeof(struct1_t) = 4
sizeof(struct2_t) = 16
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Seperti yang anda lihat, melintasi Aligned sememangnya mengambil masa yang lebih singkat berbanding rakan sejawatannya.

Padding ditambahkan untuk memastikan setiap medan struct berbaris dengan betul dalam ingatan berdasarkan keperluannya, seperti yang kita lihat sebelum ini. Tetapi sementara ia membolehkan akses yang cekap, padding juga boleh membuang ruang jika medan tidak disusun dengan baik.

Memahami cara menjajarkan medan struct dengan betul untuk meminimumkan sisa memori akibat pelapik adalah penting untuk penggunaan memori yang cekap, terutamanya dalam aplikasi kritikal prestasi. Di bawah, saya akan memberikan contoh dengan struktur yang tidak sejajar dan kemudian menunjukkan versi yang dioptimumkan bagi struktur yang sama.

Dalam struct yang tidak dijajarkan dengan baik, medan dipesan tanpa mengambil kira saiz dan keperluan penjajarannya, yang boleh membawa kepada padding tambahan dan peningkatan penggunaan memori:

// structure 1
typedef struct example_1 {
    char c;
    short int s;
} struct1_t;


// structure 2
typedef struct example_2 {
    double d;
    int s;
    char c;
} struct2_t;
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Jumlah ingatan boleh jadi 1 (bool) 7 (padding) 8 (float64) 4 (int32) 4 (padding) 16 (rentetan) = 40 bait.

Struktur yang dioptimumkan menyusun medan daripada saiz terbesar hingga terkecil, mengurangkan atau menghapuskan keperluan untuk pelapik tambahan dengan ketara:

#include <stdio.h>


// structure 1
typedef struct example_1 {
    char c;
    short int s;
} struct1_t;

// structure 2
typedef struct example_2 {
    double d;
    int s;
    char c;
} struct2_t;

int main()
{
    printf("sizeof(struct1_t) = %lu\n", sizeof(struct1_t));
    printf("sizeof(struct2_t) = %lu\n", sizeof(struct2_t));

    return 0;
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Jumlah memori kemudiannya akan mengandungi 8 (float64) 16 (rentetan) 4 (int32) 1 (bool) 3 (padding) = 32 bait.

Jom buktikan perkara di atas:

sizeof(struct1_t) = 4
sizeof(struct2_t) = 16
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Output

type Employee struct {
  IsAdmin  bool
  Id       int64
  Age      int32
  Salary   float32
}
Salin selepas log masuk
Salin selepas log masuk

Mengurangkan saiz struktur daripada 40 bait kepada 32 bait bermakna pengurangan 20% dalam penggunaan memori setiap contoh Orang. Ini boleh membawa kepada penjimatan yang besar dalam aplikasi di mana banyak kejadian sedemikian dibuat atau disimpan, meningkatkan kecekapan cache dan berpotensi mengurangkan bilangan kehilangan cache.

Kesimpulan

Penjajaran data ialah faktor penting dalam mengoptimumkan penggunaan memori dan meningkatkan prestasi sistem. Dengan menyusun data struktur dengan betul, penggunaan memori bukan sahaja menjadi lebih cekap tetapi juga lebih pantas dari segi masa baca CPU, menyumbang dengan ketara kepada kecekapan sistem keseluruhan.

Atas ialah kandungan terperinci Mengoptimumkan Penggunaan Memori dalam Go: Menguasai Penjajaran Struktur Data. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

sumber:dev.to
Kenyataan Laman Web ini
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn
Artikel terbaru oleh pengarang
Tutorial Popular
Lagi>
Muat turun terkini
Lagi>
kesan web
Kod sumber laman web
Bahan laman web
Templat hujung hadapan