Apakah praktik yang baik untuk menggunakan tipe data yang lebih kecil untuk variabel untuk menghemat memori?


32

Ketika saya belajar bahasa C ++ untuk pertama kalinya saya belajar bahwa selain int, float dll, versi yang lebih kecil atau lebih besar dari tipe data ini ada dalam bahasa tersebut. Misalnya saya bisa memanggil variabel x

int x;
or 
short int x;

Perbedaan utama adalah bahwa int pendek mengambil 2 byte memori sedangkan int mengambil 4 byte, dan int pendek memiliki nilai lebih rendah, tetapi kita juga bisa menyebutnya ini untuk membuatnya lebih kecil:

int x;
short int x;
unsigned short int x;

yang bahkan lebih ketat.

Pertanyaan saya di sini adalah apakah itu praktik yang baik untuk menggunakan tipe data terpisah sesuai dengan nilai apa yang variabel Anda ambil dalam program. Apakah ide yang baik untuk selalu mendeklarasikan variabel sesuai dengan tipe data ini?


3
apakah Anda mengetahui pola desain Flyweight ? "sebuah objek yang meminimalkan penggunaan memori dengan berbagi data sebanyak mungkin dengan objek serupa lainnya; ini adalah cara untuk menggunakan objek dalam jumlah besar ketika representasi berulang yang sederhana akan menggunakan jumlah memori yang tidak dapat diterima ..."
gnat

5
Dengan pengaturan pengemasan / penyejajaran kemasan standar, variabel-variabel akan tetap disejajarkan dengan batas 4 byte, jadi mungkin tidak ada perbedaan sama sekali.
nikie

36
Kasus klasik optimasi prematur.
scarfridge

1
@nikie - mereka mungkin disejajarkan pada batas 4 byte pada prosesor x86 tetapi ini tidak benar secara umum. MSP430 menempatkan char pada alamat byte apa saja dan yang lainnya pada alamat byte genap. Saya pikir AVR-32 dan ARM Cortex-M adalah sama.
uɐɪ

3
Bagian ke-2 dari pertanyaan Anda menyiratkan bahwa menambahkan unsignedentah bagaimana membuat integer menempati lebih sedikit ruang, yang tentu saja salah. Ini akan memiliki jumlah nilai representatif diskrit yang sama (memberi atau menerima 1 tergantung pada bagaimana tanda diwakili) tetapi hanya bergeser secara eksklusif ke positif.
underscore_d

Jawaban:


41

Sebagian besar waktu biaya ruang diabaikan dan Anda tidak perlu khawatir tentang hal itu, namun Anda harus khawatir tentang informasi tambahan yang Anda berikan dengan mendeklarasikan jenis. Misalnya, jika Anda:

unsigned int salary;

Anda memberikan informasi yang berguna kepada pengembang lain: gaji tidak boleh negatif.

Perbedaan antara pendek, int, panjang jarang akan menyebabkan masalah ruang dalam aplikasi Anda. Anda cenderung secara tidak sengaja membuat asumsi keliru bahwa suatu angka akan selalu cocok dengan beberapa tipe data. Mungkin lebih aman untuk selalu menggunakan int kecuali Anda 100% yakin angka Anda akan selalu sangat kecil. Meski begitu, tidak mungkin untuk menghemat jumlah ruang yang terlihat.


5
Benar itu jarang akan menimbulkan masalah hari ini, tetapi jika Anda merancang perpustakaan atau kelas yang akan digunakan pengembang lain, baik itu masalah lain. Mungkin mereka akan membutuhkan penyimpanan untuk sejuta objek ini, dalam hal ini perbedaannya besar - 4MB dibandingkan dengan 2MB hanya untuk bidang yang satu ini.
dodgy_coder

30
Menggunakan unsigneddalam kasus ini adalah ide yang buruk: tidak hanya gaji tidak boleh negatif, tetapi perbedaan antara dua gaji juga tidak boleh negatif. (Secara umum, menggunakan unsigned untuk apa pun kecuali sedikit-twiddling dan memiliki perilaku yang didefinisikan pada overflow adalah ide yang buruk.)
zvrba

15
@ zvrba: Perbedaan antara dua gaji itu sendiri bukan gaji dan oleh karena itu sah untuk menggunakan jenis yang berbeda yang ditandatangani.
JeremyP

12
@ JeremyP Ya tetapi jika Anda menggunakan C (dan sepertinya ini juga berlaku di C ++), pengurangan integer yang tidak ditandatangani menghasilkan int yang tidak ditandatangani , yang tidak boleh negatif. Ini mungkin berubah menjadi nilai yang tepat jika Anda melemparkannya ke int yang ditandatangani, tetapi hasil perhitungannya adalah int yang tidak ditandatangani. Lihat juga jawaban ini untuk lebih banyak keanehan komputasi yang ditandatangani / tidak ditandatangani - itulah sebabnya Anda tidak boleh menggunakan variabel yang tidak ditandatangani kecuali Anda benar-benar mengutak-atik bit.
Tacroy

5
@zvrba: Perbedaannya adalah jumlah uang tetapi bukan gaji. Sekarang Anda dapat berargumen bahwa gaji juga merupakan kuantitas moneter (dibatasi ke angka positif dan 0 dengan memvalidasi input yang merupakan apa yang kebanyakan orang akan lakukan) tetapi perbedaan antara dua gaji itu sendiri bukanlah gaji.
JeremyP

29

OP mengatakan apa-apa tentang jenis sistem mereka menulis program untuk, tetapi saya menganggap OP memikirkan PC khas dengan memori GB sejak C ++ disebutkan. Seperti yang dikatakan salah satu komentar, bahkan dengan memori semacam itu, jika Anda memiliki beberapa juta item dari satu jenis - seperti array - maka ukuran variabel dapat membuat perbedaan.

Jika Anda masuk ke dunia sistem embedded - yang tidak benar-benar di luar lingkup pertanyaan, karena OP tidak membatasinya untuk PC - maka ukuran tipe data sangat penting. Saya baru saja menyelesaikan proyek cepat pada mikrokontroler 8-bit yang hanya memiliki 8K kata memori program dan 368 byte RAM. Di sana, jelas setiap byte dihitung. Seseorang tidak pernah menggunakan variabel yang lebih besar dari yang mereka butuhkan (baik dari sudut pandang ruang, dan ukuran kode - prosesor 8-bit menggunakan banyak instruksi untuk memanipulasi data 16 dan 32-bit). Mengapa menggunakan CPU dengan sumber daya yang terbatas? Dalam jumlah besar, harganya hanya seperempat.

Saat ini saya sedang melakukan proyek tertanam lainnya dengan mikrokontroler berbasis MIPS 32-bit yang memiliki flash 512K byte dan 128K byte RAM (dan biaya sekitar $ 6 dalam jumlah). Seperti halnya PC, ukuran data "alami" adalah 32-bit. Sekarang menjadi lebih efisien, kode-bijaksana, untuk menggunakan int untuk sebagian besar variabel, bukan karakter atau celana pendek. Tetapi sekali lagi, segala jenis array atau struktur harus dipertimbangkan apakah diperlukan tipe data yang lebih kecil. Tidak seperti kompiler untuk sistem yang lebih besar, kemungkinan besar variabel dalam struktur akan dikemas pada sistem tertanam. Saya berhati-hati untuk selalu mencoba untuk menempatkan semua variabel 32-bit terlebih dahulu, lalu 16-bit, lalu 8-bit untuk menghindari "lubang".


10
Memberi +1 untuk fakta bahwa berbagai aturan berlaku untuk sistem tertanam. Fakta bahwa C ++ disebutkan tidak berarti bahwa targetnya adalah PC. Salah satu proyek terbaru saya ditulis dalam C ++ pada prosesor dengan 32k RAM dan 256 ribu Flash.
uɐɪ

13

Jawabannya tergantung pada sistem Anda. Secara umum, berikut ini kelebihan dan kekurangan menggunakan jenis yang lebih kecil:

Keuntungan

  • Tipe yang lebih kecil menggunakan lebih sedikit memori pada kebanyakan sistem.
  • Tipe yang lebih kecil memberikan perhitungan lebih cepat pada beberapa sistem. Terutama berlaku untuk float vs ganda pada banyak sistem. Dan tipe int yang lebih kecil juga memberikan kode yang lebih cepat secara signifikan pada CPU 8- atau 16-bit.

Kekurangan

  • Banyak CPU memiliki persyaratan pelurusan. Beberapa akses menyelaraskan data lebih cepat dari yang tidak selaras. Beberapa harus memiliki data yang disejajarkan bahkan untuk dapat mengaksesnya. Jenis integer yang lebih besar sama dengan satu unit yang disejajarkan, sehingga kemungkinan besar mereka tidak selaras. Ini berarti bahwa kompiler mungkin dipaksa untuk meletakkan bilangan bulat kecil Anda di yang lebih besar. Dan jika tipe yang lebih kecil adalah bagian dari struct yang lebih besar, Anda mungkin mendapatkan berbagai padding byte yang dimasukkan secara diam-diam ke dalam struct oleh kompiler, untuk memperbaiki perataan.
  • Konversi tersirat berbahaya. C dan C ++ memiliki beberapa aturan yang tidak jelas dan berbahaya tentang bagaimana variabel dipromosikan ke yang lebih besar, secara implisit tanpa typecast. Ada dua set aturan konversi implisit yang saling terkait satu sama lain, yang disebut "aturan promosi bilangan bulat" dan "konversi aritmatika biasa." Baca lebih lanjut tentang mereka di sini . Aturan-aturan ini adalah salah satu penyebab paling umum untuk bug di C dan C ++. Anda dapat menghindari banyak masalah hanya dengan menggunakan tipe integer yang sama di seluruh program.

Saran saya adalah menyukai ini:

system                             int types

small/low level embedded system    stdint.h with smaller types
32-bit embedded system             stdint.h, stick to int32_t and uint32_t.
32-bit desktop system              Only use (unsigned) int and long long.
64-bit system                      Only use (unsigned) int and long long.

Atau, Anda dapat menggunakan int_leastn_tatau int_fastn_tdari stdint.h, di mana n adalah angka 8, 16, 32 atau 64. int_leastn_ttipe berarti "Saya ingin ini setidaknya n byte tetapi saya tidak peduli jika kompiler mengalokasikannya sebagai tipe yang lebih besar sesuai dengan perataan ".

int_fastn_t berarti "Saya ingin ini menjadi n byte panjang, tetapi jika itu akan membuat kode saya akan berjalan lebih cepat, kompiler harus menggunakan tipe yang lebih besar daripada yang ditentukan".

Secara umum, berbagai tipe stdint.h adalah praktik yang jauh lebih baik daripada yang intlain-lain, karena mereka portabel. Tujuannya intadalah untuk tidak memberikan lebar yang ditentukan hanya untuk membuatnya portabel. Tetapi pada kenyataannya, sulit untuk port karena Anda tidak pernah tahu seberapa besar itu pada sistem tertentu.


Temukan tentang penyelarasan. Dalam proyek saya saat ini, penggunaan uint8_t pada MSP430 16-bit secara tidak sengaja menabrak MCU dengan cara yang misterius (kemungkinan besar akses yang tidak selaras terjadi di suatu tempat, mungkin kesalahan GCC, mungkin tidak) - hanya mengganti semua uint8_t dengan 'tanpa tanda' menghilangkan tabrakan. Penggunaan tipe 8-bit pada> lengkungan 8-bit jika tidak fatal setidaknya tidak efisien: kompiler menghasilkan instruksi tambahan 'dan reg, 0xff'. Gunakan 'int / unsigned' untuk portabilitas dan bebaskan kompiler dari kendala tambahan.
alexei

11

Bergantung pada cara kerja sistem operasi tertentu, Anda biasanya mengharapkan memori untuk dialokasikan tidak dioptimalkan sehingga ketika Anda memanggil byte, atau kata atau beberapa tipe data kecil yang akan dialokasikan, nilai menempati seluruh register semua itu sangat sendiri. Bagaimana kompiler atau penerjemah Anda bekerja untuk mengartikan ini adalah sesuatu yang lain, jadi jika Anda mengkompilasi program dalam C # misalnya, nilai secara fisik mungkin menempati register untuk dirinya sendiri, namun nilainya akan diperiksa batas untuk memastikan Anda tidak cobalah untuk menyimpan nilai yang akan melampaui batas dari tipe data yang dimaksudkan.

Kinerja-bijaksana, dan jika Anda benar-benar jago tentang hal-hal seperti itu, kemungkinan lebih cepat untuk hanya menggunakan tipe data yang paling cocok dengan ukuran register target, tetapi kemudian Anda kehilangan semua gula sintaksis indah yang membuat bekerja dengan variabel sangat mudah .

Bagaimana ini membantu Anda? Nah, itu benar-benar terserah Anda untuk memutuskan situasi seperti apa yang Anda koding. Untuk hampir setiap program yang pernah saya tulis, cukup percaya pada kompiler Anda untuk mengoptimalkan berbagai hal dan menggunakan tipe data yang paling berguna bagi Anda. Jika Anda membutuhkan presisi tinggi, gunakan tipe data floating point yang lebih besar. Jika bekerja dengan hanya nilai positif, Anda mungkin dapat menggunakan integer yang tidak ditandatangani, tetapi sebagian besar, cukup menggunakan int datatype sudah cukup.

Namun, jika Anda memiliki beberapa persyaratan data yang sangat ketat, seperti menulis protokol komunikasi, atau semacam algoritma enkripsi, maka menggunakan tipe data rentang-periksa bisa sangat berguna, terutama jika Anda mencoba menghindari masalah yang berkaitan dengan overruns / underruns data. , atau nilai data tidak valid.

Satu-satunya alasan lain yang dapat saya pikirkan dari atas kepala saya untuk menggunakan tipe data tertentu adalah ketika Anda mencoba untuk mengomunikasikan maksud dalam kode Anda. Jika Anda menggunakan shortint misalnya, Anda memberi tahu pengembang lain bahwa Anda mengizinkan angka positif dan negatif dalam rentang nilai yang sangat kecil.


6

Seperti yang dikomentari scarfridge , ini adalah a

Kasus klasik optimasi prematur .

Mencoba mengoptimalkan penggunaan memori dapat memengaruhi area kinerja lainnya, dan aturan utama pengoptimalan adalah:

Aturan Pertama Optimalisasi Program: Jangan lakukan itu .

Aturan Kedua tentang Pengoptimalan Program (hanya untuk para ahli!): Jangan lakukan itu dulu . "

- Michael A. Jackson

Untuk mengetahui apakah sekarang saatnya mengoptimalkan, perlu dilakukan benchmarking dan pengujian. Anda perlu tahu di mana kode Anda tidak efisien, sehingga Anda dapat menargetkan optimasi Anda.

Dalam rangka untuk menentukan apakah dioptimalkan versi kode ini sebenarnya lebih baik dari pelaksanaan naif pada waktu tertentu, Anda perlu patokan mereka sisi-by-side dengan data yang sama.

Juga, ingat bahwa hanya karena implementasi yang diberikan lebih efisien pada generasi CPU saat ini, tidak berarti akan selalu begitu. Jawaban saya atas pertanyaan Apakah optimasi mikro penting saat pengkodean? merinci contoh dari pengalaman pribadi di mana optimasi usang menghasilkan urutan besarnya perlambatan.

Pada banyak prosesor, akses memori yang tidak selaras jauh lebih mahal daripada akses memori yang selaras. Mengemas beberapa celana pendek ke struct Anda mungkin hanya berarti bahwa program Anda harus melakukan operasi pack / unpack setiap kali Anda menyentuh salah satu nilai.

Karena alasan ini, kompiler modern mengabaikan saran Anda. Seperti komentar nikie :

Dengan pengaturan pengemasan / penyejajaran kemasan standar, variabel-variabel akan tetap disejajarkan dengan batas 4 byte, jadi mungkin tidak ada perbedaan sama sekali.

Tebak kompiler Anda dengan risiko sendiri.

Ada tempat untuk optimisasi seperti itu, ketika bekerja dengan dataset terabyte atau pengendali mikro tertanam, tetapi bagi kebanyakan dari kita, itu tidak benar-benar menjadi perhatian.


3

Perbedaan utama adalah bahwa int pendek mengambil 2 byte memori sedangkan int mengambil 4 byte, dan int pendek memiliki nilai lebih rendah, tetapi kita juga bisa menyebutnya ini untuk membuatnya lebih kecil:

Ini salah. Anda tidak dapat membuat asumsi tentang berapa banyak byte masing-masing jenis memegang, selain charmenjadi satu byte dan setidaknya 8 bit per byte, bersama dengan ukuran masing-masing jenis menjadi lebih besar atau sama dengan sebelumnya.

Manfaat kinerja sangat kecil untuk variabel stack - mereka mungkin akan tetap selaras / padded.

Karena ini, shortdan longpraktis tidak digunakan saat ini, dan Anda hampir selalu lebih baik menggunakan int.


Tentu saja, ada juga stdint.hyang bisa digunakan saat inttidak memotongnya. Jika Anda pernah mengalokasikan array besar integer / struct maka intX_tmasuk akal karena Anda bisa efisien dan bergantung pada ukuran tipe. Ini sama sekali tidak prematur karena Anda dapat menghemat megabita memori.


1
Sebenarnya, dengan munculnya lingkungan 64 bit, longmungkin berbeda dengan int. Jika kompiler Anda adalah LP64, intadalah 32 bit dan long64 bit dan Anda akan menemukan bahwa ints mungkin masih sejajar dengan 4 byte (misalnya kompiler saya).
JeremyP

1
@ JeremyP Ya, apakah saya mengatakan sebaliknya atau sesuatu?
Pubby

Kalimat terakhir Anda yang mengklaim pendek dan panjang praktis tidak ada gunanya. Lama tentu memiliki penggunaan, jika hanya sebagai tipe dasarint64_t
JeremyP

@ JeremyP: Anda bisa hidup dengan baik dengan int dan panjang.
gnasher729

@ gnasher729: Apa yang Anda gunakan jika Anda membutuhkan variabel yang dapat menyimpan nilai lebih dari 65 ribu, tetapi tidak pernah sebanyak satu miliar? int32_t,, int_fast32_tdan longsemuanya merupakan pilihan yang baik, long longhanya boros, dan inttidak portabel.
Ben Voigt

3

Ini akan dari sudut pandang OOP dan / atau perusahaan / aplikasi dan mungkin tidak berlaku di bidang / domain tertentu, tapi saya agak ingin memunculkan konsep obsesi primitif .

Merupakan ide yang bagus untuk menggunakan tipe data yang berbeda untuk berbagai jenis informasi dalam aplikasi Anda. Namun, mungkin bukan ide yang baik untuk menggunakan tipe bawaan untuk ini, kecuali jika Anda memiliki beberapa masalah kinerja yang serius (yang telah diukur dan diverifikasi dan sebagainya).

Jika kita ingin memodelkan suhu di Kelvin dalam aplikasi kita, kita BISA menggunakan ushortatau uintatau sesuatu yang serupa dengan menyatakan bahwa "gagasan tentang derajat negatif Kelvin tidak masuk akal dan kesalahan logika domain". Gagasan di balik ini masuk akal, tetapi Anda tidak akan sepenuhnya. Apa yang kami sadari adalah bahwa kami tidak dapat memiliki nilai negatif, jadi sangat berguna jika kami bisa mendapatkan kompiler untuk memastikan tidak ada yang memberikan nilai negatif ke suhu Kelvin. JUGA benar bahwa Anda tidak dapat melakukan operasi bitwise pada suhu. Dan Anda tidak bisa menambahkan ukuran berat (kg) ke suhu (K). Tetapi jika Anda memodelkan suhu dan massa sebagai uints, kita dapat melakukan hal itu.

Menggunakan tipe bawaan untuk memodelkan entitas DOMAIN kami akan mengarah pada beberapa kode berantakan dan beberapa cek yang terlewat dan invarian yang rusak. Bahkan jika suatu tipe menangkap BEBERAPA bagian dari entitas (tidak boleh negatif), itu pasti akan kehilangan yang lain (tidak dapat digunakan dalam ekspresi aritmatika yang sewenang-wenang, tidak dapat diperlakukan sebagai array bit, dll.)

Solusinya adalah mendefinisikan tipe baru yang merangkum invarian. Dengan cara ini Anda dapat memastikan bahwa uang adalah uang dan jarak adalah jarak, dan Anda tidak dapat menambahkannya bersama-sama, dan Anda tidak dapat membuat jarak negatif, tetapi Anda BISA membuat jumlah uang yang negatif (atau hutang). Tentu saja, tipe ini akan menggunakan tipe bawaan secara internal, tetapi ini disembunyikan dari klien. Berkaitan dengan pertanyaan Anda tentang konsumsi kinerja / memori, hal semacam ini dapat memungkinkan Anda untuk mengubah bagaimana hal-hal disimpan secara internal tanpa mengubah antarmuka fungsi Anda yang beroperasi pada entitas domain Anda, jika Anda mengetahui itu, a shortterlalu berlebihan besar.


1

Ya tentu saja. Sebaiknya gunakan uint_least8_tkamus, susunan konstanta besar, buffer dll. Lebih baik digunakan uint_fast8_tuntuk keperluan pemrosesan.

uint8_least_t(penyimpanan) -> uint8_fast_t(pemrosesan) -> uint8_least_t(penyimpanan).

Misalnya Anda mengambil simbol 8 bit dari source, kode 16 bit dari dictionariesdan beberapa 32 bit constants. Daripada Anda memproses operasi 10-15 bit dengan mereka dan output 8 bit destination.

Bayangkan Anda harus memproses 2 gigabita source. Jumlah operasi bit sangat besar. Anda akan menerima bonus kinerja yang luar biasa jika Anda akan beralih ke tipe cepat selama pemrosesan. Tipe cepat dapat berbeda untuk setiap keluarga CPU. Anda dapat menyertakan stdint.hdan penggunaan uint_fast8_t, uint_fast16_t, uint_fast32_t, dll

Anda bisa menggunakannya uint_least8_tsebagai ganti uint8_tportabilitas. Tapi tidak ada yang tahu cpu modern apa yang akan menggunakan fitur ini. Mesin VAC adalah benda museum. Jadi mungkin itu adalah pembunuhan yang berlebihan.


1
Meskipun Anda mungkin memiliki titik dengan tipe data yang Anda daftarkan, Anda harus menjelaskan mengapa mereka lebih baik daripada hanya menyatakan apa adanya. Bagi orang-orang seperti saya yang tidak terbiasa dengan tipe data itu, saya harus google mereka untuk memahami apa yang Anda bicarakan.
Peter M
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.