Bagikan melalui


Praktik terbaik pengoptimalan

Dokumen ini menjelaskan beberapa praktik terbaik untuk mengoptimalkan program C++ di Visual Studio.

Opsi Pengkompilasi dan Pengtaut

Pengoptimalan yang dipandu profil

Visual Studio mendukung pengoptimalan yang dipandu profil (PGO). Pengoptimalan ini menggunakan data profil dari eksekusi pelatihan versi aplikasi yang berinstrumentasi untuk mendorong pengoptimalan aplikasi nanti. Menggunakan PGO dapat memakan waktu, jadi mungkin bukan sesuatu yang digunakan setiap pengembang, tetapi sebaiknya gunakan PGO untuk build rilis akhir produk. Untuk informasi selengkapnya, lihat Pengoptimalan Yang Dipandu Profil.

Selain itu, Pengoptimalan Seluruh Program (juga dikenal sebagai Pembuatan Kode Waktu Tautan) dan /O1 pengoptimalan dan /O2 telah ditingkatkan. Secara umum, aplikasi yang dikompilasi dengan salah satu opsi ini akan lebih cepat daripada aplikasi yang sama yang dikompilasi dengan kompilator sebelumnya.

Untuk informasi selengkapnya, lihat /GL (Pengoptimalan Seluruh Program) dan/O1 , /O2 (Minimalkan Ukuran, Maksimalkan Kecepatan).

Tingkat pengoptimalan mana yang akan digunakan

Jika memungkinkan, build rilis akhir harus dikompilasi dengan Pengoptimalan Terpandu Profil. Jika tidak dimungkinkan untuk membangun dengan PGO, apakah karena infrastruktur yang tidak memadai untuk menjalankan build berinstrumen atau tidak memiliki akses ke skenario, maka kami sarankan membangun dengan Pengoptimalan Seluruh Program.

Sakelar /Gy juga sangat berguna. Ini menghasilkan COMDAT terpisah untuk setiap fungsi, memberikan lebih banyak fleksibilitas linker dalam hal menghapus COMDAT yang tidak direferensikan dan pelipatan COMDAT. Satu-satunya kelemahan untuk digunakan /Gy adalah dapat menyebabkan masalah saat penelusuran kesalahan. Oleh karena itu, umumnya disarankan untuk menggunakannya. Untuk informasi selengkapnya, lihat /Gy (Aktifkan Penautan Tingkat Fungsi).

Untuk menautkan di lingkungan 64-bit, disarankan untuk menggunakan /OPT:REF,ICF opsi linker, dan di lingkungan 32-bit, /OPT:REF disarankan. Untuk informasi selengkapnya, lihat /OPT (Pengoptimalan).

Disarankan juga untuk menghasilkan simbol debug, bahkan dengan build rilis yang dioptimalkan. Ini tidak memengaruhi kode yang dihasilkan, dan membuatnya jauh lebih mudah untuk men-debug aplikasi Anda, jika perlu.

Sakelar titik mengambang

Opsi /Op kompilator telah dihapus, dan empat opsi kompilator berikut yang menangani pengoptimalan titik mengambang telah ditambahkan:

Opsi Deskripsi
/fp:precise Ini adalah rekomendasi default dan harus digunakan dalam banyak kasus.
/fp:fast Direkomendasikan jika performa sangat penting, misalnya dalam game. Ini akan menghasilkan performa tercepat.
/fp:strict Direkomendasikan jika pengecualian floating-point yang tepat dan perilaku IEEE diinginkan. Ini akan menghasilkan performa paling lambat.
/fp:except[-] Dapat digunakan bersama dengan /fp:strict atau /fp:precise, tetapi tidak /fp:fast.

Untuk informasi selengkapnya, lihat /fp (Tentukan Perilaku Titik Mengambang).

Pengoptimalan declspecs

Di bagian ini kita akan melihat dua deslspecs yang dapat digunakan dalam program untuk membantu performa: __declspec(restrict) dan __declspec(noalias).

restrict Declspec hanya dapat diterapkan ke deklarasi fungsi yang mengembalikan penunjuk, seperti__declspec(restrict) void *malloc(size_t size);

restrict Declspec digunakan pada fungsi yang mengembalikan pointer yang tidak dialihkan. Kata kunci ini digunakan untuk implementasi malloc Pustaka C-Runtime karena tidak akan pernah mengembalikan nilai pointer yang sudah digunakan dalam program saat ini (kecuali Anda melakukan sesuatu yang ilegal, seperti menggunakan memori setelah dibebaskan).

restrict Declspec memberikan informasi lebih lanjut kepada pengkompilasi untuk melakukan pengoptimalan pengkompilasi. Salah satu hal tersulit bagi pengkompilasi untuk menentukan adalah apa yang menunjuk alias pointer lain, dan menggunakan informasi ini sangat membantu pengkompilasi.

Perlu ditunjukkan bahwa ini adalah janji kepada pengkompilasi, bukan sesuatu yang akan diverifikasi oleh pengkompilasi. Jika program Anda menggunakan declspec ini restrict secara tidak pantas, program Anda mungkin memiliki perilaku yang salah.

Untuk informasi selengkapnya, lihat restrict .

noalias Declspec juga hanya diterapkan ke fungsi, dan menunjukkan bahwa fungsi tersebut adalah fungsi semi-murni. Fungsi semi-murni adalah fungsi yang mereferensikan atau memodifikasi hanya lokal, argumen, dan tidak langsung argumen tingkat pertama. Declspec ini adalah janji kepada pengkompilasi, dan jika fungsi mereferensikan global atau tidak langsung tingkat kedua argumen pointer, pengkompilasi dapat menghasilkan kode yang merusak aplikasi.

Untuk informasi selengkapnya, lihat noalias .

Pragma pengoptimalan

Ada juga beberapa pragma yang berguna untuk membantu mengoptimalkan kode. Yang pertama akan kita bahas adalah #pragma optimize:

#pragma optimize("{opt-list}", on | off)

Pragma ini memungkinkan Anda untuk menetapkan tingkat pengoptimalan tertentu berdasarkan fungsi demi fungsi. Ini sangat ideal untuk kesempatan langka di mana aplikasi Anda mengalami crash ketika fungsi tertentu dikompilasi dengan pengoptimalan. Anda dapat menggunakan ini untuk menonaktifkan pengoptimalan untuk satu fungsi:

#pragma optimize("", off)
int myFunc() {...}
#pragma optimize("", on)

Untuk informasi selengkapnya, lihat optimize .

Inlining adalah salah satu pengoptimalan terpenting yang dilakukan kompilator dan di sini kita berbicara tentang beberapa pragma yang membantu memodifikasi perilaku ini.

#pragma inline_recursion berguna untuk menentukan apakah Anda ingin aplikasi dapat menggariskan panggilan rekursif atau tidak. Secara default tidak aktif. Untuk rekursi dangkal fungsi kecil, Anda dapat mengaktifkannya. Untuk informasi selengkapnya, lihat inline_recursion .

Pragma berguna lainnya untuk membatasi kedalaman inlining adalah #pragma inline_depth. Ini biasanya berguna dalam situasi di mana Anda mencoba membatasi ukuran program atau fungsi. Untuk informasi selengkapnya, lihat inline_depth .

__restrict dan __assume

Ada beberapa kata kunci di Visual Studio yang dapat membantu performa: __restrict dan __assume.

Pertama, perlu dicatat bahwa __restrict dan __declspec(restrict) merupakan dua hal yang berbeda. Meskipun mereka agak terkait, semantik mereka berbeda. __restrict adalah jenis kualifikasi, seperti const atau volatile, tetapi secara eksklusif untuk jenis penunjuk.

Penunjuk yang dimodifikasi dengan __restrict disebut sebagai penunjuk __restrict. Penunjuk __restrict adalah penunjuk yang hanya dapat diakses melalui penunjuk __restrict. Dengan kata lain, pointer lain tidak dapat digunakan untuk mengakses data yang ditunjukkan oleh penunjuk __restrict.

__restrict dapat menjadi alat yang kuat untuk pengoptimal Microsoft C++, tetapi gunakan dengan sangat hati-hati. Jika digunakan secara tidak benar, pengoptimal mungkin melakukan pengoptimalan yang akan merusak aplikasi Anda.

Dengan __assume, pengembang dapat memberi tahu pengkompilasi untuk membuat asumsi tentang nilai beberapa variabel.

Misalnya __assume(a < 5); memberi tahu pengoptimal bahwa pada baris kode tersebut variabel a kurang dari 5. Sekali lagi ini adalah janji kepada pengkompilasi. Jika a sebenarnya 6 pada titik ini dalam program maka perilaku program setelah pengkompilasi dioptimalkan mungkin bukan yang Anda harapkan. __assume paling berguna sebelum beralih pernyataan dan/atau ekspresi bersyar.

Ada beberapa batasan untuk __assume. Pertama, seperti __restrict, itu hanya saran, sehingga pengkompilasi bebas untuk mengabaikannya. Selain itu, __assume saat ini hanya berfungsi dengan ketidaksetaraan variabel terhadap konstanta. Ini tidak menyebarluaskan ketidaksamaan simbolis, misalnya, asumsikan(a < b).

Dukungan intrinsik

Intrinsik adalah panggilan fungsi di mana kompilator memiliki pengetahuan intrinsik tentang panggilan, dan daripada memanggil fungsi di pustaka, ia memancarkan kode untuk fungsi tersebut. File <header intrin.h> berisi semua intrinsik yang tersedia untuk setiap platform perangkat keras yang didukung.

Intrinsik memberi programmer kemampuan untuk masuk jauh ke dalam kode tanpa harus menggunakan assembly. Ada beberapa manfaat untuk menggunakan intrinsik:

  • Kode Anda lebih portabel. Beberapa intrinsik tersedia pada beberapa arsitektur CPU.

  • Kode Anda lebih mudah dibaca, karena kode masih ditulis dalam C/C++.

  • Kode Anda mendapatkan manfaat pengoptimalan pengkompilasi. Ketika pengkompilasi menjadi lebih baik, pembuatan kode untuk intrinsik meningkat.

Untuk informasi selengkapnya, lihat Compiler Intrinsics.

Pengecualian

Ada hit performa yang terkait dengan penggunaan pengecualian. Beberapa batasan diperkenalkan saat menggunakan blok coba yang menghambat pengompilasi melakukan pengoptimalan tertentu. Pada platform x86 ada penurunan performa tambahan dari blok percobaan karena informasi status tambahan yang harus dihasilkan selama eksekusi kode. Pada platform 64-bit, blok percobaan tidak menurunkan performa sebanyak, tetapi setelah pengecualian dilemparkan, proses menemukan handler dan melepas penat tumpukan bisa mahal.

Oleh karena itu, disarankan untuk menghindari pengenalan blok coba/tangkap ke dalam kode yang tidak benar-benar membutuhkannya. Jika Anda harus menggunakan pengecualian, gunakan pengecualian sinkron jika memungkinkan. Untuk informasi selengkapnya, lihat Penanganan Pengecualian Terstruktur (C/C++).

Terakhir, hanya melempar pengecualian untuk kasus luar biasa. Menggunakan pengecualian untuk alur kontrol umum kemungkinan akan membuat performa menderita.

Lihat juga