Menemukan dua terbesar dari lima bilangan bulat kecil secepat mungkin

9

Saya menggunakan variasi filter median 5-silang pada data gambar pada sistem tertanam kecil, yaitu

    x
  x x x
    x

Algoritma ini sangat sederhana: baca 5 nilai integer tanpa tanda, dapatkan 2 tertinggi, lakukan beberapa perhitungan pada nilai tersebut dan tulis kembali hasil integer tanpa tanda.

Yang menyenangkan adalah bahwa nilai input 5 integer semuanya berada dalam kisaran 0-20. Nilai bilangan bulat yang dihitung juga berada dalam kisaran 0-20!

Melalui profiling, saya telah menemukan bahwa mendapatkan dua angka terbesar adalah hambatan sehingga saya ingin mempercepat bagian ini. Apa cara tercepat untuk melakukan seleksi ini?

Algoritma saat ini menggunakan topeng 32 bit dengan 1 di posisi yang diberikan oleh 5 angka dan fungsi CLZ yang didukung HW.
Saya harus mengatakan bahwa CPU itu adalah milik perusahaan, tidak tersedia di luar perusahaan saya. Kompiler saya adalah GCC tetapi dibuat khusus untuk CPU ini.

Saya telah mencoba mencari tahu apakah saya bisa menggunakan tabel pencarian tetapi saya gagal menghasilkan kunci yang bisa saya gunakan.

Saya memiliki kombinasi untuk input tetapi pesanan tidak penting, yaitu sama dengan .215[5,0,0,0,5][5,5,0,0,0]

Kebetulan fungsi hash di bawah ini menghasilkan hash sempurna tanpa tabrakan!

def hash(x):
    h = 0
    for i in x:
        h = 33*h+i
    return h

Tapi hash sangat besar dan tidak ada cukup memori untuk menggunakannya.

Apakah ada algoritma yang lebih baik yang bisa saya gunakan? Apakah mungkin untuk menyelesaikan masalah saya menggunakan tabel pencarian dan menghasilkan kunci?

Fredrik Pihl
sumber
1
Algoritma mana yang saat ini Anda gunakan? Tujuh perbandingan integer sudah cukup, apakah itu terlalu lambat? Anda hashsudah melakukan lebih banyak operasi. Apakah panggilan selanjutnya ke metode terkait, misalnya apakah pusat xbergerak melalui matriks baris-demi-baris?
Raphael
Filter ini dililitkan melalui baris demi baris gambar. Yaitu mendapatkan 5 nilai dan melakukan perhitungan kemudian memindahkan semuanya satu langkah ke kanan dan ulangi. Hash hanyalah sebuah contoh. Saya membandingkan beberapa solusi sliding-window untuk meminimalkan pembacaan data tetapi semuanya bermuara pada menemukan 2 nilai tertinggi.
Fredrik Pihl
3
Kemungkinan besar algoritma Anda, jika diterapkan dengan benar, akan dibatasi oleh akses memori dan bukan oleh perhitungan. Menggunakan hashtable hanya akan meningkatkan jumlah akses memori dan memperlambat segalanya. Silakan kirim kode Anda saat ini sehingga kami dapat melihat bagaimana hal itu dapat ditingkatkan - Saya percaya hanya optimasi mikro yang mungkin. Yang paling bisa saya pikirkan adalah: mungkin kita bisa mengambil keuntungan dari kenyataan bahwa 2 nilai sama antara jendela tetangga?
jkff
@ jkff Bergantung pada matriks, ukuran cache dan fungsi pemetaan (cache), setiap nilai mungkin hanya perlu dimuat satu kali; sebagian besar operasi harus berjalan pada register atau cache L1. Pipelining adalah masalah lain.
Raphael
1
Omong-omong, apakah Anda sudah melakukan ini secara paralel? Ini tampaknya sangat cocok untuk parallelisation vektor atau SIMD (misalnya pada GPU). Rute itu akan membantu lebih dari menghemat beberapa persen per sel.
Raphael

Jawaban:

11

Dalam jawaban saya yang lain saya menyarankan bahwa lompatan bersyarat mungkin menjadi hambatan utama untuk efisiensi. Sebagai akibatnya, jaringan penyortiran muncul dalam pikiran: mereka adalah data agnostik, yaitu urutan perbandingan yang sama dijalankan tidak peduli input, dengan hanya swap yang bersyarat.

Tentu saja, menyortir mungkin terlalu banyak pekerjaan; kita hanya membutuhkan dua angka terbesar. Beruntung bagi kami, jaringan seleksi juga telah dipelajari. Knuth memberi tahu kita bahwa menemukan dua angka terkecil dari lima² dapat dilakukan dengan perbandingan [1, 5.3.4 ex 19] (dan paling banyak sebanyak swap).U^2(5)=6

Jaringan yang ia berikan dalam solusi (ditulis ulang ke array berbasis nol) adalah

[0:4][1:4][0:3][1:3][0:2][1:2]

yang mengimplementasikan - setelah menyesuaikan arah perbandingan - dalam pseudocode sebagai

def selMax2(a : int[])
  a.swap(0,4) if a[0] < a[4]
  a.swap(1,4) if a[1] < a[4]
  a.swap(0,3) if a[0] < a[3]
  a.swap(1,3) if a[1] < a[3]
  a.swap(0,2) if a[0] < a[2]
  a.swap(1,2) if a[1] < a[2]
  return (a[0], a[1])
end

Sekarang, implementasi naif masih memiliki lompatan bersyarat (melintasi kode swap). Tergantung pada mesin Anda, Anda dapat menghubungi mereka dengan instruksi bersyarat. x86 tampaknya seperti lumpur biasa; ARM terlihat lebih menjanjikan karena sebagian besar operasi tampaknya tergantung pada dirinya sendiri. Jika saya memahami instruksi dengan benar, swap pertama diterjemahkan menjadi ini, dengan asumsi nilai array kami telah dimuat ke register R0melalui R4:

CMP     R0,R4
MOVLT   R5 = R0
MOVLT   R0 = R4
MOVLT   R4 = R6

Ya, ya, tentu saja Anda dapat menggunakan XOR swapping dengan EOR .

Saya hanya berharap prosesor Anda memiliki ini, atau yang serupa. Tentu saja, jika Anda membangunnya untuk tujuan ini, mungkin Anda bisa mendapatkan jaringan terprogram di sana?

Ini mungkin (terbukti?) Yang terbaik yang dapat Anda lakukan di dunia klasik, yaitu tanpa menggunakan domain terbatas dan melakukan sulap kata-kata jahat.


  1. Penyortiran dan Pencarian oleh Donald E. Knuth; Seni Pemrograman Komputer Vol. 3 (2nd ed, 1998)
  2. Perhatikan bahwa ini membuat dua elemen yang dipilih tidak terurut. Memesannya membutuhkan perbandingan ekstra, yaitu total [1, p234 Tabel 1].W^2(5)=7
Raphael
sumber
Saya menerima ini. Saya menerima banyak ide baru yang perlu saya tolak sebelum melanjutkan. Mengacu pada Knuth selalu berhasil untuk saya :-) Terima kasih atas usaha dan waktu Anda!
Fredrik Pihl
@FredrikPihl Keren, tolong beri tahu kami bagaimana hasilnya pada akhirnya!
Raphael
Aku akan! Membaca Bab 5.3.3 sekarang. Senang dimulainya dengan referensi ke Lewis Carroll dan turnamen tenis :-)
Fredrik Pihl
2
Bergantung pada set instruksi, menggunakan 2 * maks (a, b) = a + b + abs (ab) bersama dengan jaringan seleksi dapat berguna; itu bisa lebih murah daripada lompatan kondisional yang tidak dapat diprediksi (bahkan tanpa gerakan intrinsik atau kondisional untuk abs: gcc, setidaknya untuk x86, menghasilkan urutan jumpless yang tampaknya tidak bergantung pada x86). Memiliki urutan jumpless juga berguna ketika dikombinasikan dengan SIMD atau GPU.
Pemrogram
1
Perhatikan bahwa jaringan pilihan (seperti jaringan sortir) dapat menerima operasi paralel; khusus dalam jaringan pemilihan yang ditentukan, perbandingan 1: 4 dan 0: 3 dapat dilakukan secara paralel (jika prosesor, kompiler, dll mendukung yang efisien), dan perbandingan 1: 3 dan 0: 2 juga dapat dilakukan secara paralel.
Bruce Lilly
4

Hanya saja itu ada di atas meja, inilah algoritma langsung:

// Sort x1, x2
if x1 < x2
  M1 = x2
  m1 = x1
else
  M1 = x1
  m1 = x2
end

// Sort x3, x4
if x3 < x4
  M2 = x4
  m2 = x3
else
  M2 = x3
  m2 = x4
end

// Pick largest two
if M1 > M2
  M3 = M1
  if m1 > M2
    m3 = m1
  else
    m3 = M2
  end
else
  M3 = M2
  if m2 > M1
    m3 = m2
  else
    m3 = M1
  end
end

// Insert x4
if x4 > M3
  m3 = M3
  M3 = x4
else if x4 > m3
  m3 = x4
end

Dengan implementasi cerdas if ... else, seseorang dapat menyingkirkan beberapa lompatan tanpa syarat yang dimiliki terjemahan langsung.

Ini jelek tapi hanya butuh

  • lima atau enam perbandingan (yaitu lompatan bersyarat),
  • sembilan hingga sepuluh penugasan (dengan 11 variabel, semuanya dalam register) dan
  • tidak ada akses memori tambahan.

Faktanya, enam perbandingan optimal untuk masalah ini seperti yang ditunjukkan Teorema S di bagian 5.3.3 dari [1]; di sini kita membutuhkan .W2(5)

Ini tidak bisa diharapkan untuk cepat pada mesin dengan pipelining, meskipun; mengingat persentase tinggi dari lompatan bersyarat, sebagian besar waktu mungkin akan dihabiskan di warung.

Perhatikan bahwa varian yang lebih sederhana - urutkan x1dan x2, kemudian masukkan nilai-nilai lain selanjutnya - membutuhkan empat hingga tujuh perbandingan dan hanya lima hingga enam penugasan. Karena saya berharap lompatan lebih mahal di sini, saya terjebak dengan yang ini.


  1. Penyortiran dan Pencarian oleh Donald E. Knuth; Seni Pemrograman Komputer Vol. 3 (2nd ed, 1998)
Raphael
sumber
Saya bertanya-tanya apa yang dapat dilakukan oleh kompiler yang mengoptimalkan ini.
Raphael
Saya akan menerapkan ini dan membandingkannya dengan solusi berbasis CLZ saat ini. Terima kasih atas waktunya!
Fredrik Pihl
1
@FredrikPihl Apa hasil dari tolok ukur Anda?
Raphael
1
Pendekatan berbasis SWAP mengalahkan CLZ! Di ponsel sekarang. Dapat memposting lebih banyak data di lain waktu, di perangkat seluler sekarang
Fredrik Pihl
@FredrikPihl Keren! Saya senang pendekatan teori lama yang baik masih dapat digunakan secara praktis. :)
Raphael
4

Ini bisa menjadi aplikasi dan uji kasus yang bagus untuk proyek Souper . Souper adalah superoptimizer - alat yang mengambil urutan kode pendek sebagai input, dan mencoba mengoptimalkannya sebanyak mungkin (mencoba menemukan urutan kode yang setara yang akan lebih cepat).

Souper adalah open source. Anda mungkin mencoba menjalankan Souper pada cuplikan kode Anda untuk melihat apakah itu bisa lebih baik.

Lihat juga kontes John Regehr tentang penulisan kode cepat untuk mengurutkan 16 nilai 4-bit ; mungkin beberapa teknik di sana mungkin bermanfaat.

DW
sumber
Saya akan tertarik dengan apa yang dapat dilakukan pada program yang dicoba OP.
Raphael
3

213

T[T[T[441*a+21*b+c]*21+d]*21+e]

214

212

212

Yuval Filmus
sumber