Bisakah prosesor / jam yang lebih cepat mengeksekusi lebih banyak kode?

9

Saya menulis sebuah program untuk dijalankan pada ATmega 328 yang berjalan pada 16Mhz (ini adalah Arduino Duemilanove jika Anda mengenal mereka, ini adalah chip AVR).

Saya memiliki proses interupsi yang berjalan setiap 100 mikrodetik. Tidak mungkin, saya akan mengatakan, untuk mengetahui berapa banyak "kode" yang dapat Anda jalankan dalam satu loop 100 mikrodetik (saya menulis dalam C yang mungkin dikonversi ke perakitan kemudian menjadi gambar biner?).

Juga ini akan tergantung pada kompleksitas kode (satu liner raksasa mungkin berjalan lebih lambat daripada beberapa baris pendek misalnya).

Apakah pemahaman saya benar, bahwa prosesor saya dengan clock rate atau 16Mhz melakukan 16 juta siklus per detik (ini berarti 16 siklus per mikrodetik 16.000.000 / 1.000 / 1.000); Jadi, jika saya ingin melakukan lebih banyak dalam loop 100 mikrodetik, membeli model yang lebih cepat seperti versi 72Mhz akan memberi saya 72 siklus per mikrodetik (72.000.000 / 1.000 / 1.000)?

Saat ini berjalan sedikit terlalu lambat, yaitu butuh sedikit lebih lama dari 100 mikrodetik untuk melakukan loop (berapa lama tepatnya terlalu sulit untuk dikatakan, tetapi secara bertahap tertinggal) dan saya ingin melakukannya sedikit lagi, adalah ini pendekatan yang waras mendapatkan chip yang lebih cepat atau saya sudah gila?

jwbensley
sumber
.... ATmega328 BUKAN chip ARM. Itu adalah AVR.
vicatcu
Ceria, diperbaiki!
jwbensley

Jawaban:

9

Secara umum jumlah instruksi perakitan yang dapat dieksekusi perangkat per detik akan tergantung pada campuran instruksi dan berapa banyak siklus yang dibutuhkan oleh masing-masing tipe instruksi (CPI). Secara teori Anda dapat menghitung kode Anda dengan melihat file asm yang dibongkar dan melihat fungsi yang Anda khawatirkan, menghitung semua jenis instruksi yang berbeda di dalamnya, dan mencari hitungan siklus dari lembar data untuk prosesor target Anda.

Masalah dalam menentukan jumlah efektif instruksi per detik diperparah dalam prosesor yang lebih kompleks oleh kenyataan bahwa mereka disalurkan melalui pipa dan memiliki cache dan apa yang tidak. Ini bukan kasus untuk perangkat sederhana seperti ATMega328 yang merupakan instruksi tunggal dalam prosesor penerbangan.

Sedangkan untuk hal-hal praktis, untuk perangkat sederhana seperti AVR, jawaban saya akan lebih atau kurang "ya". Menggandakan kecepatan jam Anda seharusnya setengah dari waktu pelaksanaan fungsi yang diberikan. Untuk AVR, bagaimanapun, mereka tidak akan berjalan lebih cepat dari 20MHz, jadi Anda hanya bisa "overclock" Arduino Anda dengan 4MHz lain.

Saran ini tidak berlaku untuk prosesor yang memiliki fitur lebih canggih. Menggandakan kecepatan clock pada prosesor Intel Anda tidak akan dalam praktiknya menggandakan jumlah instruksi yang dieksekusi per detik (karena ramalan salah cabang, kesalahan cache, dan sebagainya).

vicatcu
sumber
Hai, terima kasih atas jawaban informatif Anda! Saya telah melihat salah satunya ( coolcomponents.co.uk/catalog/product_info.php?products_id=808 ), Anda mengatakan AVR tidak bisa lebih cepat dari 20Mhz, mengapa begitu? Chip pada papan di atas ( uk.farnell.com/stmicroelectronics/stm32f103rbt6/… ) adalah ARM 72Mhz, bisakah saya mengharapkan peningkatan kinerja yang wajar dari ini dengan cara yang telah saya jelaskan di atas?
jwbensley
2
Menggandakan kecepatan pemrosesan mungkin tidak meningkatkan throughput instruksi Anda karena Anda mungkin mulai melebihi kecepatan di mana instruksi dapat diambil dari flash. Pada titik ini Anda mulai menekan "status tunggu Flash" di mana CPU berhenti sementara menunggu instruksi tiba dari flash. Beberapa mikrokontroler mengatasi hal ini dengan memungkinkan Anda untuk mengeksekusi kode dari RAM yang jauh lebih cepat daripada FLASH.
Majenko
@Majenko: lucu, kami berdua membuat titik yang sama pada saat yang sama
Jason S
Itu terjadi ... milikmu lebih baik dari milikku :)
Majenko
1
OK, saya telah menandai jawaban Vicatcu sebagai "jawabannya". Saya merasa itu adalah yang paling tepat sehubungan dengan pertanyaan awal saya tentang kecepatan yang berkaitan dengan kinerja meskipun semua jawaban bagus dan saya benar-benar puas dengan jawaban semua orang. Mereka telah menunjukkan kepada saya bahwa itu adalah subjek yang lebih luas daripada yang saya sadari, dan, mereka semua mengajari saya banyak dan memberi saya banyak untuk penelitian, jadi terima kasih kepada semua orang: D
jwbensley
8

@ vicatcu jawabannya cukup komprehensif. Satu hal tambahan yang perlu diperhatikan adalah bahwa CPU dapat mengalami kondisi tunggu (siklus CPU terhenti) saat mengakses I / O, termasuk memori program dan data.

Misalnya, kami menggunakan DSP TI F28335; beberapa area RAM adalah keadaan 0-tunggu untuk program dan memori data, jadi ketika Anda mengeksekusi kode dalam RAM, itu berjalan pada 1 siklus per instruksi (kecuali untuk instruksi yang membutuhkan lebih dari 1 siklus). Ketika Anda mengeksekusi kode dari memori FLASH (EEPROM bawaan, lebih atau kurang), namun, itu tidak dapat berjalan pada 150MHz penuh dan beberapa kali lebih lambat.


Sehubungan dengan kode interupsi berkecepatan tinggi, Anda harus mempelajari sejumlah hal.

Pertama, menjadi sangat akrab dengan kompiler Anda. Jika kompiler melakukan pekerjaan dengan baik, seharusnya tidak lebih lambat dari perakitan kode tangan untuk kebanyakan hal. (di mana "yang jauh lebih lambat": faktor 2 akan baik-baik saja bagi saya; faktor 10 akan tidak dapat diterima) Anda perlu belajar bagaimana (dan kapan) menggunakan bendera optimisasi kompiler, dan setiap kali sesekali Anda harus melihat di keluaran kompiler untuk melihat bagaimana hasilnya.

Beberapa hal lain yang dapat Anda lakukan pada kompiler untuk mempercepat kode:

  • gunakan fungsi sebaris (tidak ingat apakah C mendukung ini atau jika hanya C ++ - ism), baik untuk fungsi kecil maupun untuk fungsi yang akan dieksekusi hanya sekali atau dua kali. Kelemahannya adalah fungsi inline sulit untuk di-debug, terutama jika optimisasi kompiler dihidupkan. Tetapi mereka menyelamatkan Anda urutan panggilan / kembali tidak perlu, terutama jika abstraksi "fungsi" adalah untuk tujuan desain konseptual daripada implementasi kode.

  • Lihatlah manual kompiler Anda untuk melihat apakah ia memiliki fungsi intrinsik - ini adalah fungsi bawaan yang bergantung pada kompiler yang memetakan langsung ke instruksi perakitan prosesor; beberapa prosesor memiliki instruksi perakitan yang melakukan hal-hal berguna seperti min / max / bit mundur dan Anda dapat menghemat waktu melakukannya.

  • Jika Anda melakukan perhitungan numerik, pastikan Anda tidak memanggil fungsi perpustakaan matematika secara tidak perlu. Kami memiliki satu kasus di mana kode itu seperti y = (y+1) % 4untuk penghitung yang memiliki periode 4, mengharapkan kompiler untuk mengimplementasikan modulo 4 sebagai bitwise-AND. Sebaliknya itu disebut perpustakaan matematika. Jadi kami diganti dengan y = (y+1) & 3untuk melakukan apa yang kami inginkan.

  • Biasakan diri dengan halaman hack bit-twiddling . Saya jamin Anda akan menggunakan paling tidak salah satunya.

Anda juga harus menggunakan periferal timer CPU Anda untuk mengukur waktu eksekusi kode - kebanyakan dari mereka memiliki timer / penghitung yang dapat diatur untuk dijalankan pada frekuensi clock CPU. Tangkap salinan penghitung di awal dan akhir kode kritis Anda, dan Anda dapat melihat berapa lama. Jika Anda tidak bisa melakukan itu, alternatif lain adalah menurunkan pin output di awal kode Anda, dan menaikkannya di akhir, dan lihat output ini pada osiloskop untuk menentukan waktu pelaksanaannya. Ada pengorbanan untuk setiap pendekatan: penghitung waktu internal / counter lebih fleksibel (Anda dapat mengatur waktu beberapa hal) tetapi lebih sulit untuk mendapatkan informasi, sedangkan pengaturan / kliring pin output segera terlihat pada ruang lingkup dan Anda dapat menangkap statistik, tetapi sulit untuk membedakan banyak acara.

Akhirnya, ada keterampilan yang sangat penting yang datang dengan pengalaman - baik umum dan dengan kombinasi prosesor / kompiler tertentu: mengetahui kapan dan kapan tidak mengoptimalkan . Secara umum jawabannya adalah jangan optimalkan. Kutipan Donald Knuth sering diposting di StackOverflow (biasanya hanya bagian terakhir):

Kita harus melupakan efisiensi kecil, katakanlah sekitar 97% dari waktu: optimasi prematur adalah akar dari semua kejahatan

Tetapi Anda berada dalam situasi di mana Anda tahu harus melakukan semacam optimasi, jadi inilah saatnya untuk menggigit peluru dan mengoptimalkan (atau mendapatkan prosesor yang lebih cepat, atau keduanya). Apakah TIDAK menulis seluruh ISR Anda dalam perakitan. Itu hampir merupakan bencana yang dijamin - jika Anda melakukannya, dalam beberapa bulan atau bahkan beberapa minggu Anda akan melupakan bagian dari apa yang Anda lakukan dan mengapa, dan kode ini cenderung sangat rapuh dan sulit untuk diubah. Namun, ada kemungkinan bagian-bagian kode Anda yang merupakan kandidat yang baik untuk perakitan.

Tanda-tanda bahwa bagian-bagian kode Anda sangat cocok untuk pengkodean perakitan:

  • fungsi yang terkandung dengan baik, rutinitas kecil yang terdefinisi dengan baik tidak akan berubah
  • fungsi yang dapat memanfaatkan instruksi perakitan khusus (min / maks / shift kanan / dll)
  • fungsi yang dipanggil berkali-kali (memberi Anda pengganda: jika Anda menyimpan 0,5 usec pada setiap panggilan, dan dipanggil 10 kali, itu menghemat 5 usec yang penting dalam kasus Anda)

Pelajari konvensi pemanggilan fungsi kompiler Anda (mis. Di mana ia menempatkan argumen dalam register, dan register mana yang disimpan / dipulihkan) sehingga Anda dapat menulis rutinitas perakitan yang dapat dipanggil-C.

Dalam proyek saya saat ini, kami memiliki basis kode yang cukup besar dengan kode kritis yang harus dijalankan dalam interupsi 10kHz (100usec - sound familiar?) Dan tidak ada banyak fungsi yang ditulis dalam perakitan. Yang ada, adalah hal-hal seperti perhitungan CRC, antrian perangkat lunak, ADC gain / kompensasi kompensasi.

Semoga berhasil!

Jason S
sumber
nasihat yang baik tentang teknik pengukuran waktu eksekusi empiris
vicatcu
Jawaban hebat lainnya untuk pertanyaan saya, terima kasih banyak Jason S untuk pengetahuan yang luar biasa ini! Dua hal yang jelas setelah membaca ini; Pertama, saya dapat meningkatkan interupsi dari setiap 100uS menjadi 500uS untuk memberikan kode lebih banyak waktu untuk dieksekusi, saya menyadari sekarang ini tidak benar-benar bermanfaat bagi saya menjadi secepat itu. Kedua saya pikir kode saya mungkin terlalu tidak efisien, dengan waktu interupsi yang lebih lama dan kode yang lebih baik semuanya mungkin baik-baik saja. Stackoverflow adalah tempat yang lebih baik untuk memposting kode, jadi saya akan mempostingnya di sana dan meletakkan tautan di sini, jika ada yang ingin melihat dan membuat rekomendasi, silakan lakukan: D
jwbensley
5

Hal lain yang perlu diperhatikan - mungkin ada beberapa optimasi yang dapat Anda lakukan untuk membuat kode Anda lebih efisien.

Misalnya - Saya memiliki rutinitas yang berjalan dari dalam penghentian waktu. Rutin harus selesai dalam 52μS, dan harus melalui sejumlah besar memori saat melakukannya.

Saya mengelola peningkatan kecepatan besar dengan mengunci variabel penghitung utama ke register dengan (di µC & compiler saya - berbeda untuk Anda):

register unsigned int pointer asm("W9");

Saya tidak tahu format untuk kompiler Anda - RTFM, tetapi akan ada sesuatu yang dapat Anda lakukan untuk membuat rutinitas Anda lebih cepat tanpa harus beralih ke assembly.

Karena itu, Anda mungkin dapat melakukan pekerjaan yang jauh lebih baik dalam mengoptimalkan rutin Anda daripada kompiler, jadi beralih ke perakitan mungkin memberi Anda beberapa peningkatan kecepatan besar.

Majenko
sumber
lol Saya "secara bersamaan" mengomentari jawaban saya sendiri tentang penyetelan assembler dan mendaftar alokasi :)
vicatcu
Jika itu mengambil 100us pada prosesor 16 MHz - itu jelas sangat besar, jadi itu banyak kode untuk dioptimalkan. Saya pernah mendengar bahwa kompiler hari ini menghasilkan sekitar 1,1 kali kode daripada perakitan yang dioptimalisasi dengan tangan. Sama sekali tidak sepadan dengan rutinitas yang begitu besar. Untuk menghemat 20% dari fungsi 6 baris, mungkin ...
DefenestrationDay
1
Belum tentu ... Bisa jadi hanya 5 baris kode dalam satu lingkaran. Dan ini bukan tentang ukuran kode tetapi tentang efisiensi kode . Anda mungkin dapat menulis kode secara berbeda sehingga berjalan lebih cepat. Saya tahu untuk rutinitas interupsi yang saya lakukan. Misalnya, mengorbankan ukuran untuk kecepatan. Dengan menjalankan kode yang sama 10 kali secara berurutan Anda menghemat waktu memiliki kode untuk melakukan loop - dan variabel counter terkait. Ya, kodenya 10 kali lebih lama, tetapi berjalan lebih cepat.
Majenko
Hai Majenko, saya tidak tahu perakitan tetapi saya telah berpikir tentang belajar itu, dan berpikir bahwa Arduino akan menjadi lebih rumit daripada komputer desktop saya sehingga ini bisa menjadi waktu yang baik untuk belajar, terutama karena saya ingin tahu lebih lanjut tentang apa yang terjadi dan tingkat yang lebih rendah. Seperti yang orang lain katakan, saya tidak akan menulis ulang semuanya hanya bagian-bagian tertentu. Pemahaman saya adalah bahwa saya bisa keluar dan masuk ASM dalam C, apakah ini benar, apakah ini bagaimana seseorang dapat mencapai campuran C dan ASM ini? Saya akan memposting di stackoverflow untuk spesifik, hanya setelah ide umum.
jwbensley
@javano: Ya. Anda dapat keluar dan masuk ASM dalam C. Banyak sistem tertanam ditulis seperti itu - dalam campuran C dan perakitan - terutama karena ada beberapa hal yang tidak dapat dilakukan dalam kompiler C primitif yang tersedia di waktu. Namun, kompiler C modern seperti gcc (yang merupakan kompiler yang digunakan oleh Arduino) sekarang menangani sebagian besar dan dalam banyak kasus semua hal yang dulu memerlukan bahasa assembly.
davidcary