Apa teknik sederhana yang Anda gunakan untuk meningkatkan kinerja?

21

Saya sedang berbicara tentang cara kami menulis rutinitas sederhana untuk meningkatkan kinerja tanpa membuat kode Anda lebih sulit dibaca ... misalnya, ini adalah tipikal yang kami pelajari:

for(int i = 0; i < collection.length(); i++ ){
   // stuff here
}

Tapi, saya biasanya melakukan ini ketika a foreachtidak berlaku:

for(int i = 0, j = collection.length(); i < j; i++ ){
   // stuff here
}

Saya pikir ini adalah pendekatan yang lebih baik karena hanya akan memanggil lengthmetode sekali saja ... pacar saya bilang itu samar. Apakah ada trik sederhana lain yang Anda gunakan pada perkembangan Anda sendiri?

Cristian
sumber
34
+1 hanya karena punya pacar yang akan memberi tahu Anda ketika kode Anda tidak jelas.
Kristo
76
Anda hanya memposting ini untuk memberi tahu kami bahwa Anda punya pacar.
Josh K
11
@Christian: Jangan lupa bahwa ada optimisasi kompiler yang mungkin melakukan ini untuk Anda sehingga Anda mungkin hanya mempengaruhi keterbacaan dan tidak mempengaruhi kinerja sama sekali; optimisasi prematur adalah akar dari semua kejahatan ... Cobalah untuk menghindari lebih dari satu deklarasi atau penugasan pada baris yang sama, jangan membuat orang membacanya dua kali ... Anda harus menggunakan cara normal (contoh pertama Anda) atau meletakkan deklarasi kedua di luar for loop (walaupun itu juga mengurangi keterbacaan karena Anda perlu membaca kembali untuk melihat apa arti j).
Tamara Wijsman
5
@ TomWij: Kutipan yang benar (dan lengkap): "Kita harus melupakan efisiensi kecil, katakanlah sekitar 97% dari waktu: optimasi prematur adalah akar dari semua kejahatan. Namun kita tidak boleh melewatkan peluang kita dalam 3% kritis itu. "
Robert Harvey
3
@ Tomwij: Jika Anda menghabiskan tiga persen, maka menurut definisi Anda harus melakukannya dalam kode waktu-kritis, dan tidak membuang-buang waktu Anda di 97% lainnya.
Robert Harvey

Jawaban:

28

masukkan kuliah prematur-diskusi-adalah-akar-dari-semua-kejahatan

Yang mengatakan, berikut adalah beberapa kebiasaan saya sudah masuk untuk menghindari efisiensi yang tidak perlu, dan dalam beberapa kasus, membuat kode saya lebih sederhana dan lebih benar juga.

Ini bukan diskusi tentang prinsip-prinsip umum, tetapi beberapa hal yang harus diperhatikan untuk menghindari memasukkan inefisiensi yang tidak perlu ke dalam kode.

Ketahui big-O Anda

Ini mungkin harus digabungkan ke dalam diskusi panjang di atas. Cukup masuk akal bahwa loop di dalam loop, di mana loop dalam mengulangi perhitungan, akan menjadi lebih lambat. Sebagai contoh:

for (i = 0; i < strlen(str); i++) {
    ...
}

Ini akan membutuhkan banyak waktu jika string benar-benar panjang, karena panjangnya dihitung ulang pada setiap iterasi dari loop. Perhatikan bahwa GCC sebenarnya mengoptimalkan kasus ini karena strlen()ditandai sebagai fungsi murni.

Saat menyortir sejuta bilangan bulat 32-bit, bubble sort akan menjadi cara yang salah . Secara umum, penyortiran dapat dilakukan dalam waktu O (n * log n) (atau lebih baik, dalam hal penyortiran radix), jadi kecuali Anda tahu data Anda akan menjadi kecil, cari algoritma yang setidaknya O (n * log n).

Demikian juga, ketika berhadapan dengan basis data, waspadai indeks. Jika Anda SELECT * FROM people WHERE age = 20, dan Anda tidak memiliki indeks pada orang (usia), itu akan memerlukan pemindaian berurutan O (n) daripada pemindaian indeks O (log n) yang jauh lebih cepat.

Hirarki aritmatika integer

Ketika pemrograman dalam C, ingatlah bahwa beberapa operasi aritmatika lebih mahal daripada yang lain. Untuk bilangan bulat, hierarki berbunyi seperti ini (paling murah dulu):

  • + - ~ & | ^
  • << >>
  • *
  • /

Memang, compiler akan hal-hal yang biasanya mengoptimalkan seperti n / 2untuk n >> 1secara otomatis jika Anda menargetkan komputer mainstream, tetapi jika Anda menargetkan perangkat tertanam, Anda mungkin tidak mendapatkan kemewahan itu.

Juga, % 2dan & 1memiliki semantik yang berbeda. Pembagian dan modulus biasanya membulat ke nol, tetapi implementasinya didefinisikan. Baik >>dan &selalu berputar ke arah infinity negatif, yang (menurut saya) jauh lebih masuk akal. Misalnya, di komputer saya:

printf("%d\n", -1 % 2); // -1 (maybe)
printf("%d\n", -1 & 1); // 1

Karena itu, gunakan apa yang masuk akal. Jangan berpikir Anda menjadi anak yang baik dengan menggunakan % 2ketika Anda awalnya akan menulis & 1.

Operasi floating point yang mahal

Hindari operasi floating point berat seperti pow()dan log()dalam kode yang tidak benar-benar membutuhkannya, terutama ketika berhadapan dengan bilangan bulat. Ambil, misalnya, membaca nomor:

int parseInt(const char *str)
{
    const char *p;
    int         digits;
    int         number;
    int         position;

    // Count the number of digits
    for (p = str; isdigit(*p); p++)
        {}
    digits = p - str;

    // Sum the digits, multiplying them by their respective power of 10.
    number = 0;
    position = digits - 1;
    for (p = str; isdigit(*p); p++, position--)
        number += (*p - '0') * pow(10, position);

    return number;
}

Tidak hanya penggunaan ini pow()(dan konversi int<-> yang doublediperlukan untuk menggunakannya) agak mahal, tetapi juga menciptakan peluang untuk kehilangan presisi (kebetulan, kode di atas tidak memiliki masalah presisi). Itu sebabnya saya meringis ketika saya melihat fungsi jenis ini digunakan dalam konteks non-matematika.

Perhatikan juga bagaimana algoritma "pintar" di bawah ini, yang dikalikan 10 pada setiap iterasi, sebenarnya lebih ringkas daripada kode di atas:

int parseInt(const char *str)
{
    const char *p;
    int         number;

    number = 0;
    for (p = str; isdigit(*p); p++) {
        number *= 10;
        number += *p - '0';
    }

    return number;
}
Joey Adams
sumber
Jawaban yang sangat teliti.
Paddyslacker
1
Catatan, diskusi optimasi prematur tidak berlaku untuk kode sampah. Anda harus selalu menggunakan implementasi yang bekerja dengan baik sejak awal.
Perhatikan bahwa GCC sebenarnya mengoptimalkan kasus ini karena strlen () ditandai sebagai fungsi murni. Saya pikir maksud Anda itu adalah fungsi const, tidak murni.
Andy Lester
@Andy Lester: Sebenarnya, maksud saya murni. Dalam dokumentasi GCC , ini menyatakan bahwa const sedikit lebih ketat daripada murni dalam bahwa fungsi const tidak dapat membaca memori global. strlen()memeriksa string yang ditunjukkan oleh argumen penunjuknya, yang berarti itu tidak bisa berupa const. Juga, strlen()memang ditandai sebagai murni dalam glibcstring.h
Joey Adams
Anda benar, kesalahan saya, dan saya harus memeriksa ulang. Saya telah bekerja pada proyek Parrot yang menjelaskan fungsi sebagai salah satu pureatau constbahkan mendokumentasikannya di file header karena perbedaan halus antara keduanya. docs.parrot.org/parrot/1.3.0/html/docs/dev/c_functions.pod.html
Andy Lester
13

Dari pertanyaan Anda dan utas komentar, sepertinya Anda "berpikir" bahwa perubahan kode ini meningkatkan kinerja, tetapi Anda tidak benar-benar tahu apakah itu benar atau tidak.

Saya penggemar filosofi Kent Beck :

"Buat itu bekerja, lakukan dengan benar, buatlah dengan cepat."

Teknik saya untuk meningkatkan kinerja kode, pertama-tama dapatkan kode yang lulus tes unit dan diperhitungkan dengan baik dan kemudian (terutama untuk operasi perulangan) menulis tes unit yang memeriksa kinerja dan kemudian memperbaiki kode atau memikirkan algoritma yang berbeda jika yang saya ' Saya telah memilih tidak bekerja seperti yang diharapkan.

Misalnya, untuk menguji kecepatan dengan .NET code, saya menggunakan atribut Timeout NUnit untuk menulis pernyataan bahwa panggilan ke metode tertentu akan dijalankan dalam jumlah waktu tertentu.

Menggunakan sesuatu seperti atribut batas waktu NUnit dengan contoh kode yang Anda berikan (dan sejumlah besar iterasi untuk loop), Anda benar-benar dapat membuktikan apakah "peningkatan" Anda terhadap kode benar-benar membantu kinerja perfoma loop itu.

Satu penafian: Meskipun ini efektif pada level "mikro", ini tentu saja bukan satu-satunya cara untuk menguji kinerja dan tidak memperhitungkan masalah akun yang mungkin muncul pada level "makro" - tetapi ini adalah awal yang baik.

Paddyslacker
sumber
2
Sementara saya sangat percaya pada profil, saya juga percaya bahwa menyimpan tip-tip tips yang dicari Cristian adalah hal yang cerdas. Saya akan selalu memilih yang lebih cepat dari dua metode yang sama-sama dapat dibaca. Terpaksa ke optimasi pasca-dewasa bukanlah hal yang menyenangkan.
AShelly
Tidak perlu perlu unit test, tetapi selalu bermanfaat untuk menghabiskan 20 menit ini untuk memeriksa apakah beberapa mitos kinerja benar atau tidak, terutama karena jawabannya sering bergantung pada kompiler dan keadaan -O dan -g flag (atau Debug / Rilis dalam kasus VS).
mbq
+1 Jawaban ini menambah komentar saya yang terkait dengan pertanyaan itu sendiri.
Tamara Wijsman
1
@ Ashelly: jika kita berbicara tentang reformulasi sederhana dari sintaks loop, mengubahnya setelah faktanya sangat mudah dilakukan. Juga, apa yang Anda temukan sama-sama mudah dibaca mungkin tidak demikian untuk programmer lain. Sebaiknya gunakan sintaks "standar" sebanyak mungkin, dan ubah hanya jika terbukti perlu.
Joeri Sebrechts
@ Apakah Anda pasti dapat memikirkan dua metode yang sama-sama dapat dibaca dan Anda memilih yang kurang efisien yang tidak Anda lakukan di pekerjaan Anda? Adakah yang benar-benar melakukan itu?
glenatron
11

Ingatlah bahwa kompiler Anda dapat berubah:

for(int i = 0; i < collection.length(); i++ ){
   // stuff here
}

ke:

int j = collection.length();
for(int i = 0; i < j; i++ ){
   // stuff here
}

atau yang serupa, jika collectiontidak berubah selama loop.

Jika kode ini ada di bagian kritis waktu aplikasi Anda, ada baiknya mencari tahu apakah ini masalahnya atau tidak - atau memang apakah Anda dapat mengubah opsi kompiler untuk melakukan ini.

Ini akan menjaga keterbacaan kode (karena yang pertama adalah apa yang diharapkan kebanyakan orang), sambil memberi Anda beberapa siklus mesin tambahan. Anda kemudian bebas berkonsentrasi pada area lain di mana kompiler tidak dapat membantu Anda.

Di samping catatan: jika Anda mengubah collectiondi dalam loop dengan menambahkan atau menghapus elemen (ya, saya tahu itu ide yang buruk, tetapi itu memang terjadi) maka contoh kedua Anda tidak akan mengulang semua elemen atau akan mencoba untuk mengakses masa lalu akhir array.

ChrisF
sumber
1
Mengapa tidak lakukan saja secara eksplisit?
3
Dalam beberapa bahasa yang memeriksa batas, Anda akan memperlambat kode Anda jika Anda melakukannya secara eksplisit. Dengan loop ke collection.length compiler memindahkannya untuk Anda dan menghilangkan batas memeriksa. Dengan loop ke beberapa konstanta dari tempat lain di aplikasi Anda, Anda akan memiliki batas memeriksa setiap iterasi. Itu sebabnya penting untuk mengukur - intuisi tentang kinerja hampir tidak pernah benar.
Kate Gregory
1
Itulah sebabnya saya berkata "akan sangat berharga untuk mencari tahu".
ChrisF
Bagaimana kompiler C # mengetahui bahwa collection.length () tidak mengubah koleksi, seperti yang dilakukan stack.pop ()? Saya pikir akan lebih baik untuk memeriksa IL daripada menganggap kompiler mengoptimalkan ini. Di C ++, Anda dapat menandai metode sebagai const ('tidak mengubah objek'), sehingga kompiler dapat membuat optimasi ini dengan aman.
JBRWilkinson
1
@JBRW Pengoptimal yang melakukan ini juga menyadari ok-mari-sebut-itu-keteguhan-bahkan-ini-bukan-C + + metode koleksi. Lagi pula, Anda hanya bisa melihat-batas jika Anda dapat melihat bahwa ada sesuatu yang merupakan koleksi dan tahu cara mendapatkan panjangnya.
Kate Gregory
9

Optimalisasi semacam ini biasanya tidak disarankan. Sepotong optimasi dapat dengan mudah dilakukan oleh kompiler, Anda bekerja dengan bahasa pemrograman tingkat yang lebih tinggi daripada perakitan, jadi pikirkan di tingkat yang sama.

taktik
sumber
1
Beri dia buku tentang pemrograman;)
Joeri Sebrechts
1
+1, karena sebagian besar pacar kita cenderung lebih tertarik pada Lady Gaga daripada kejelasan kode.
haploid
Bisakah Anda menjelaskan mengapa itu tidak dianjurkan?
Macneil
@ Macneil baik ... trik yang membuat kode tidak umum dan sama sekali tidak berfungsi, itu bagian optimasi yang seharusnya dilakukan oleh kompiler.
tactoth
@ macneil jika Anda bekerja dalam bahasa tingkat yang lebih tinggi, pikirkan di tingkat yang sama.
tactoth
3

Ini mungkin tidak berlaku terlalu banyak untuk tujuan umum pengkodean, tapi saya sebagian besar menanamkan pengembangan hari ini. Kami memiliki prosesor target spesifik (yang tidak akan menjadi lebih cepat - itu akan tampak usang pada saat mereka pensiun sistem dalam 20+ tahun), dan tenggat waktu waktu yang sangat ketat untuk sebagian besar kode. Prosesor, seperti semua prosesor, memiliki kebiasaan tertentu mengenai operasi mana yang cepat atau lambat.

Kami memiliki teknik yang digunakan untuk memastikan kami menghasilkan kode yang paling efisien, dengan tetap menjaga kewajaran bagi seluruh tim. Di tempat-tempat di mana konstruksi bahasa paling alami tidak menghasilkan kode yang paling efisien, kami telah membuat makro yang memastikan kode optimal digunakan. Jika kami melakukan proyek lanjutan untuk prosesor yang berbeda, kami dapat memperbarui makro untuk metode optimal pada prosesor itu.

Sebagai contoh spesifik, untuk prosesor kami saat ini, cabang mengosongkan pipa, menghentikan prosesor selama 8 siklus. Kompiler mengambil kode ini:

 bool isReady = (value > TriggerLevel);

dan mengubahnya menjadi setara dengan perakitan

isReady = 0
if (value > TriggerLevel)
{
  isReady = 1;
}

Ini akan membutuhkan 3 siklus, atau 10 jika dilompati isReady=1;. Tetapi prosesor memiliki maxinstruksi satu siklus , jadi jauh lebih baik untuk menulis kode untuk menghasilkan urutan ini yang dijamin akan selalu mengambil 3 siklus:

diff = value-TriggerLevel;
diff = max(diff, 0);
isReady = min(1,diff);

Jelas, maksud di sini kurang jelas dari aslinya. Jadi kami telah membuat makro, yang kami gunakan setiap kali kami ingin perbandingan boolean Greater-Than:

#define BOOL_GT(a,b) min(max((a)-(b),0),1)

//isReady = value > TriggerLevel;
isReady = BOOL_GT(value, TriggerLevel);

Kita dapat melakukan hal serupa untuk perbandingan lainnya. Untuk orang luar, kodenya sedikit lebih mudah dibaca daripada jika kita hanya menggunakan konstruk alami. Namun dengan cepat menjadi jelas setelah menghabiskan sedikit waktu bekerja dengan kode, dan itu jauh lebih baik daripada membiarkan setiap programmer bereksperimen dengan teknik optimasi mereka sendiri.

ASHelly
sumber
3

Nah, saran pertama adalah untuk menghindari optimisasi dini seperti itu sampai Anda tahu persis apa yang terjadi pada kode, sehingga Anda yakin bahwa Anda benar-benar membuatnya lebih cepat, dan tidak lebih lambat.

Dalam C # misalnya kompiler akan mengoptimalkan kode jika Anda mengulang panjang array, karena ia tahu bahwa ia tidak harus memeriksa indeks ketika Anda mengakses array. Jika Anda mencoba mengoptimalkannya dengan meletakkan panjang array dalam sebuah variabel, Anda akan memutuskan koneksi antara loop dan array, dan benar-benar membuat kode lebih lambat.

Jika Anda akan mengoptimalkan mikro, Anda harus membatasi diri pada hal-hal yang diketahui menggunakan banyak sumber daya. Jika hanya ada sedikit peningkatan kinerja, Anda harus menggunakan kode yang paling mudah dibaca dan dikelola. Bagaimana komputer bekerja berubah seiring waktu, sehingga sesuatu yang Anda temukan sedikit lebih cepat sekarang, mungkin tidak tetap seperti itu.

Guffa
sumber
3

Saya punya teknik yang sangat sederhana.

  1. Saya membuat kode saya berfungsi.
  2. Saya mengujinya untuk kecepatan.
  3. Jika cepat, saya kembali ke langkah 1 untuk beberapa fitur lainnya. Jika lambat, saya membuat profil untuk menemukan hambatannya.
  4. Saya memperbaiki kemacetan. Kembali ke langkah 1.

Ada banyak waktu di mana menghemat waktu untuk menghindari proses ini, tetapi secara umum Anda akan tahu apakah itu masalahnya. Jika ada keraguan, saya tetap menggunakan ini secara default.

Jason Baker
sumber
2

Manfaatkan hubungan arus pendek:

if(someVar || SomeMethod())

hanya membutuhkan waktu selama kode, dan dapat dibaca sebagai:

if(someMethod() || someVar)

namun itu akan mengevaluasi lebih cepat dari waktu ke waktu.

realworldcoder
sumber
1

Tunggu enam bulan, suruh bos Anda untuk membeli semua komputer baru. Serius. Waktu pemrogram jauh lebih mahal daripada perangkat keras dalam jangka panjang. Komputer berkinerja tinggi memungkinkan pembuat kode untuk menulis kode secara langsung tanpa harus khawatir tentang kecepatan.

Kristo
sumber
6
Er ... Bagaimana dengan kinerja yang dilihat pelanggan Anda? Apakah Anda cukup kaya untuk membeli komputer baru juga?
Robert Harvey
2
Dan kita hampir menabrak tembok kinerja; perhitungan multicore adalah satu-satunya jalan keluar, tetapi menunggu tidak akan membuat program Anda menggunakannya.
mbq
+1 Jawaban ini menambah komentar saya yang terkait dengan pertanyaan itu sendiri.
Tamara Wijsman
3
Tidak ada waktu pemrograman yang tidak lebih mahal daripada perangkat keras ketika Anda memiliki ribuan atau ribuan pengguna. Waktu programmer TIDAK lebih penting daripada waktu pengguna, dapatkan ini melalui kepala Anda sesegera mungkin.
HLGEM
1
Biasakan kebiasaan yang baik, maka tidak perlu waktu programmer seperti apa yang Anda lakukan sepanjang waktu.
Dominique McDonnell
1

Cobalah untuk tidak mengoptimalkan terlalu banyak sebelumnya, maka ketika Anda mengoptimalkan, khawatirkan sedikit tentang keterbacaan.

Ada sedikit saya benci lebih dari kompleksitas yang tidak perlu, tetapi ketika Anda menekan situasi yang kompleks solusi yang kompleks sering diperlukan.

Jika Anda menulis kode dengan cara yang paling jelas maka buatlah komentar yang menjelaskan mengapa kode itu diubah ketika Anda membuat perubahan yang rumit.

Khusus untuk makna Anda, saya menemukan bahwa banyak kali melakukan kebalikan dari pendekatan default Boolean kadang-kadang membantu:

for(int i = 0, j = collection.length(); i < j; i++ ){
// stuff here
}

bisa menjadi

for(int i = collection.length(); i > 0; i-=1 ){
// stuff here
}

Dalam banyak bahasa, selama Anda membuat penyesuaian yang tepat untuk bagian "barang" dan itu masih dapat dibaca. Itu hanya tidak mendekati masalah dengan cara kebanyakan orang akan berpikir untuk melakukannya terlebih dahulu karena itu dihitung mundur.

dalam c # misalnya:

        string[] collection = {"a","b"};

        string result = "";

        for (int i = 0, j = collection.Count() - 1; i < j; i++)
        {
            result += collection[i] + "~";
        }

bisa juga ditulis sebagai:

        for (int i = collection.Count() - 1; i > 0; i -= 1)
        {
            result = collection[i] + "~" + result;
        }

(dan ya, Anda harus melakukannya dengan join atau stringbuilder, tapi saya mencoba membuat contoh sederhana)

Ada banyak trik lain yang dapat digunakan yang tidak sulit untuk diikuti tetapi banyak dari mereka tidak berlaku di semua bahasa seperti menggunakan pertengahan di sisi kiri penugasan dalam vb lama untuk menghindari hukuman penugasan kembali string atau membaca file teks dalam mode biner di .net untuk melewati penalti buffering ketika file terlalu besar untuk dibaca.

Satu-satunya kasus yang benar-benar generik yang dapat saya pikirkan yang akan berlaku di mana-mana adalah dengan menerapkan beberapa aljabar Boolean ke kondisi rumit untuk mencoba mengubah persamaan menjadi sesuatu yang memiliki peluang lebih baik untuk mengambil keuntungan dari kondisi hubungan arus pendek atau mengubah kompleks. mengatur pernyataan if-then atau case yang bersarang ke dalam persamaan sepenuhnya. Tak satu pun dari ini bekerja dalam semua kasus, tetapi mereka bisa menghemat waktu secara signifikan.

Tagihan
sumber
ini adalah solusi, tetapi kompiler kemungkinan akan memuntahkan peringatan untuk panjang kelas paling umum () mengembalikan tipe yang tidak ditandatangani
stijn
Tetapi dengan membalikkan indeks, iterasi itu sendiri bisa menjadi lebih kompleks.
Tamara Wijsman
@stijn saya berpikir tentang c # ketika saya menulisnya, tapi mungkin saran ini juga termasuk dalam kategori bahasa khusus untuk alasan itu - lihat edit ... @ToWij tentu saja, saya tidak berpikir ada banyak jika ada saran seperti ini itu tidak menjalankan risiko itu. Jika / / barang-barang Anda adalah semacam manipulasi tumpukan, itu bahkan tidak mungkin untuk membalik logika dengan benar, tetapi dalam banyak kasus itu dan tidak terlalu membingungkan jika dilakukan dengan hati-hati dalam sebagian besar kasus IMHO.
Bill
kamu benar; di C ++ saya masih lebih suka loop 'normal' tetapi dengan panggilan length () diambil dari iterasi (seperti dalam const size_t len ​​= collection.length (); for (size_t i = 0; i <len; ++ i) {}) karena dua alasan: Saya menemukan loop penghitungan maju 'normal' menjadi lebih mudah dibaca / dimengerti (tapi itu mungkin hanya karena itu lebih umum), dan dibutuhkan panjang loop invariant () memanggil keluar dari loop.
stijn
1
  1. Profil. Apakah kita punya masalah? Dimana?
  2. Dalam 90% kasus yang terkait dengan IO, menerapkan caching (dan mungkin mendapatkan lebih banyak memori)
  3. Jika itu terkait CPU, terapkan caching
  4. Jika kinerja masih menjadi masalah, kami telah meninggalkan ranah teknik sederhana - lakukan matematika.
Maglob
sumber
1

Gunakan alat terbaik yang dapat Anda temukan - kompiler yang baik, profiler yang baik, perpustakaan yang baik. Dapatkan algoritma yang benar, atau lebih baik lagi - gunakan perpustakaan yang tepat untuk melakukannya untuk Anda. Optimalisasi loop sepele adalah kentang kecil, ditambah Anda tidak sepintar kompilator pengoptimal.

Pekerjaan
sumber
1

Yang paling sederhana bagi saya adalah menggunakan tumpukan bila memungkinkan setiap kali pola penggunaan kasus umum cocok dengan kisaran, katakanlah, [0, 64) tetapi memiliki kasus langka yang tidak memiliki batas atas kecil.

Contoh C sederhana (sebelum):

void some_hotspot_called_in_big_loops(int n, ...)
{
    // 'n' is, 99% of the time, <= 64.
    int* values = calloc(n, sizeof(int));

    // do stuff with values
    ...
    free(values);
}

Dan kemudian:

void some_hotspot_called_in_big_loops(int n, ...)
{
    // 'n' is, 99% of the time, <= 64.
    int values_mem[64] = {0}
    int* values = (n <= 64) ? values_mem: calloc(n, sizeof(int));

    // do stuff with values
    ...
    if (values != values_mem)
        free(values);
}

Saya telah menggeneralisasi seperti ini karena jenis hotspot ini banyak muncul dalam pembuatan profil:

void some_hotspot_called_in_big_loops(int n, ...)
{
    // 'n' is, 99% of the time, <= 64.
    MemFast values_mem;
    int* values = mf_calloc(&values_mem, n, sizeof(int));

    // do stuff with values
    ...

    mf_free(&values_mem);
}

Di atas menggunakan tumpukan ketika data yang dialokasikan cukup kecil dalam 99,9% kasus tersebut, dan menggunakan tumpukan sebaliknya.

Dalam C ++ saya telah menggeneralisasi ini dengan urutan kecil standar-compliant (mirip dengan SmallVector implementasi di luar sana) yang berputar di sekitar konsep yang sama.

Ini bukan optimasi epik (saya sudah mendapatkan pengurangan dari, katakanlah, 3 detik untuk operasi untuk menyelesaikan hingga 1,8 detik), tetapi itu membutuhkan upaya sepele untuk diterapkan. Ketika Anda bisa mendapatkan sesuatu dari 3 detik menjadi 1,8 detik hanya dengan memperkenalkan sebaris kode dan mengubah dua, itu adalah pukulan yang cukup bagus untuk uang sekecil itu.


sumber
0

Yah ada banyak perubahan kinerja yang dapat Anda lakukan saat mengakses data yang akan berdampak besar pada aplikasi Anda. Jika Anda menulis kueri atau menggunakan ORM untuk mengakses databse, maka Anda perlu membaca beberapa buku tuning kinerja untuk backend database yang Anda gunakan. Kemungkinannya adalah Anda menggunakan teknologi yang dikenal berkinerja buruk. Tidak ada alasan untuk melakukan ini kecuali ketidaktahuan. Ini bukan optimasi prematur (saya mengutuk orang yang mengatakan ini karena telah begitu banyak saling terkait karena tidak pernah khawatir tentang kinerja), ini adalah desain yang bagus.

Hanya contoh singkat peningkatan kinerja untuk SQL Server: Gunakan indeks yang sesuai, hindari kursor - gunakan logika berbasis set, gunakan sargable di mana klausa, jangan pancang pandangan di atas tampilan, jangan kembalikan data lebih dari yang Anda butuhkan atau lebih kolom dari yang Anda butuhkan, jangan gunakan subqueries berkorelasi.

HLGEM
sumber
0

Jika ini adalah C ++, Anda harus terbiasa ++idaripada i++. ++itidak akan pernah lebih buruk, itu berarti persis sama dengan pernyataan yang berdiri sendiri, dan dalam beberapa kasus itu bisa menjadi peningkatan kinerja.

Tidak ada gunanya mengubah kode yang sudah ada jika itu bisa membantu, tetapi merupakan kebiasaan yang baik untuk masuk.

David Thornley
sumber
0

Saya punya pandangan yang sedikit berbeda. Hanya dengan mengikuti saran yang Anda dapatkan di sini tidak akan membuat banyak perbedaan, karena ada beberapa kesalahan yang perlu Anda lakukan, yang kemudian perlu Anda perbaiki, yang kemudian perlu Anda pelajari.

Kesalahan yang perlu Anda lakukan adalah mendesain struktur data Anda seperti yang dilakukan semua orang. Yaitu, dengan data yang berlebihan dan banyak lapisan abstraksi, dengan properti dan notifikasi yang merambat di seluruh struktur berusaha untuk tetap konsisten.

Maka kamu perlu melakukan penyetelan kinerja (profiling) dan membuatnya menunjukkan kepada Anda bagaimana, dalam banyak hal, berapa biaya yang harus Anda keluarkan untuk siklus adalah banyaknya lapisan abstraksi, dengan properti dan notifikasi yang merambat di seluruh struktur berusaha agar tetap konsisten.

Anda mungkin dapat memperbaiki masalah ini tanpa perubahan besar pada kode.

Maka jika Anda beruntung, Anda bisa belajar bahwa struktur data yang lebih sedikit lebih baik, dan lebih baik untuk bisa mentolerir ketidakkonsistenan sementara daripada mencoba untuk menjaga banyak hal tetap sesuai dengan gelombang pesan.

Bagaimana Anda menulis loop sama sekali tidak ada hubungannya dengan itu.

Mike Dunlavey
sumber