Якнайшвидше знайти два найбільші з п'яти малих цілих чисел


9

Я використовую варіацію 5-хрестоподібного серединного фільтра для даних зображень на невеликій вбудованій системі, тобто

    x
  x x x
    x

Алгоритм дійсно простий: прочитайте 5 безпідписаних цілих значень, отримайте найвищі 2, зробіть кілька обчислень на них і запишіть результат непідписаного цілого числа.

Приємно, що 5 цілих вхідних значень знаходяться в межах 0-20. Обчислене ціле значення також знаходиться в діапазоні 0-20!

За допомогою профілювання я зрозумів, що отримання найбільших двох чисел є вузьким місцем, тому я хочу прискорити цю частину. Який найшвидший спосіб здійснити цей вибір?

Поточний алгоритм використовує 32-бітну маску з 1 у положенні, заданому 5 числами, та функцією CLZ, підтримуваною HW.
Я мушу сказати, що процесор є власником, не доступний за межами моєї компанії. Мій компілятор - GCC, але призначений для цього процесора.

Я намагався з'ясувати, чи можу я використовувати таблицю пошуку, але мені не вдалося створити ключ, який я можу використовувати.

У мене є комбінацій для введення, але порядок не важливий, тобто такий же, як .215[5,0,0,0,5][5,5,0,0,0]

Буває так, що хеш-функція нижче створює ідеальний хеш без зіткнень!

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

Але хеш величезний, і для його використання просто не вистачає пам'яті.

Чи є кращий алгоритм, який я можу використовувати? Чи можна вирішити мою проблему за допомогою таблиці пошуку та генерації ключа?


1
Який алгоритм ви зараз використовуєте? Сім цілих порівнянь достатньо, це занадто повільно? Ви hashвже виконуєте більше операцій. Чи пов'язані наступні виклики методу, наприклад, чи просувається центральний xчерез матрицю рядок за рядком?
Рафаель

Фільтр прокручується через зображення рядок за рядком. Тобто отримайте 5 значень і зробіть обчислення, потім перемістіть все на один крок вправо і повторіть. Хеш був лише прикладом. Я визначив кілька рішень з розсувними вікнами, щоб мінімізувати читання даних, але все це зводиться до знаходження найвищих 2 значень.
Фредрік Піль

3
Швидше за все, ваш алгоритм, якщо його правильно реалізовувати, буде обмежений доступом до пам'яті, а не обчисленням. Використання хештету лише збільшить кількість доступу до пам'яті та сповільнить роботу. Будь ласка, опублікуйте свій поточний код, щоб ми побачили, як його можна вдосконалити - я вважаю, що можлива лише мікрооптимізація. Найбільше, про що я можу придумати, це: можливо, ми можемо скористатися тим, що 2 значення є спільними між сусідніми вікнами?
jkff

@jkff Залежно від матриці, розмірів кеша та функції кешування (кеш), кожне значення може бути завантажено лише один раз; тоді більшість операцій має виконуватися в регістрах або кеш-пам'яті L1. Трубопровід - це ще одне питання.
Рафаель

1
До речі, ви робите це вже паралельно? Це здається особливо придатним для паралелізації вектора або SIMD (наприклад, на графічному процесорі). Цей маршрут допоможе набагато більше, ніж заощадити кілька відсотків на клітинку.
Рафаель

Відповіді:


11

У своїй іншій відповіді я припускаю, що умовні стрибки можуть стати основною перешкодою для ефективності. Як наслідок, приходить до уваги сортування мереж : вони є агностичними даними, тобто така ж послідовність порівнянь виконується незалежно від введення, лише умовні умови є умовними.

Звичайно, сортування може бути занадто великою роботою; нам потрібні лише найбільші два числа. Пощастило для нас, селекційні мережі також вивчені. Кнут говорить нам, що знайти два найменших числа з п'яти² можна зробити за допомогою порівнянь [1, 5.3.4 ex 19] (і максимум стільки, скільки поміняє своп).U^2(5)=6

Мережа, яку він дає у рішеннях (переписаних на нульові масиви) є

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

який реалізує - після коригування напрямку порівнянь - у псевдокоді як

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

Тепер у наївних реалізаціях все ще є умовні стрибки (через код swap). Залежно від вашої машини, ви можете їх обійти за допомогою умовних інструкцій. x86, здається, його звичайний мудріт; ARM виглядає більш перспективною, оскільки, очевидно, більшість операцій самі по собі є умовними . Якщо я правильно розумію інструкції , перший своп перекладається на це, припускаючи, що наші значення масиву були завантажені в регістри R0через R4:

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

Так, так, звичайно, ви можете використовувати обмін XOR на EOR .

Я просто сподіваюся, що ваш процесор має це чи щось подібне. Звичайно, якщо ви будуєте цю річ для цієї мети, можливо, ви зможете там налагодити мережу?

Це, мабуть, (що доводиться?) Найкраще, що ви можете зробити в класичній царині, тобто без використання обмеженого домену та виконання злих магів, що знаходяться в межах слова.


  1. Сортування та пошук Дональд Е. Кнут; Мистецтво комп’ютерного програмування Vol. 3 (2-е видання, 1998 р.)
  2. Зауважте, що це залишає два обраних елемента не упорядкованим. Замовлення їх вимагає додаткового порівняння, тобтоW^2(5)=7 загалом багато [1, p234 табл. 1].

Я приймаю це. Я отримав багато нових ідей, які мені потрібно орієнтувати, перш ніж рухатись далі. Посилання на Кнут завжди працює для мене :-) Дякую за ваші зусилля та час!
Фредрік Піль

@FredrikPihl Класно, будь ласка, повідомте нам, як це виходить у підсумку!
Рафаель

Я буду! Читання глави 5.3.3 прямо зараз. Любіть початок цього з посиланнями на Льюїса Керролла та тенісного турніру :-)
Фредрік Піль

2
Залежно від набору інструкцій, корисним може бути використання 2 * max (a, b) = a + b + abs (ab) разом із мережею вибору; це може бути менш затратним, ніж непередбачувані умовні стрибки (навіть без внутрішнього або умовного переходу для abs: gcc, принаймні для x86, генерує послідовність без стрибків, яка, здається, не залежить від x86). Маючи безперервну послідовність, також корисно в поєднанні зі SIMD або GPU.
AProgrammer

1
Зауважте, що мережі вибору (як-от мережі сортування) піддаються паралельним операціям; конкретно у зазначеній мережі вибору, порівняння 1: 4 і 0: 3 можна проводити паралельно (якщо процесор, компілятор тощо підтримують це ефективно), а порівняння 1: 3 і 0: 2 також можна проводити паралельно.
Брюс Ліллі

4

Просто так, щоб це було на столі, ось прямий алгоритм:

// 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

Завдяки розумній реалізації if ... else, можна позбутися деяких безумовних стрибків, які матиме прямий переклад.

Це некрасиво, але потрібно лише

  • п'ять-шість порівнянь (тобто умовні стрибки),
  • дев'ять-десять призначень (з 11 змінними, всі в регістрах) та
  • відсутність додаткового доступу до пам'яті.

Насправді, шість порівнянь є оптимальними для цієї проблеми, як показує теорема S у розділі 5.3.3 [1]; тут нам потрібноW2(5).

Однак, не можна очікувати, що це буде швидко на машинах з трубопроводами; враховуючи високий відсоток умовних стрибків, більшість часу, мабуть, буде проведено в стійлі.

Зауважте, що більш простий варіант - сортування x1та x2, а потім вставте інші значення згодом - займає чотири-сім порівнянь та лише п’ять-шість присвоєнь. Оскільки я очікую, що стрибки тут будуть дорожче, я затримався з цим.


  1. Сортування та пошук Дональд Е. Кнут; Мистецтво комп’ютерного програмування Vol. 3 (2-е видання, 1998 р.)

Цікаво, що оптимізаційний компілятор може зробити з ними.
Рафаель

Я реалізую це і порівняю його проти поточного рішення на основі CLZ. Дякую за ваш час!
Фредрік Піль

1
@FredrikPihl Що було результатом ваших орієнтирів?
Рафаель

1
Підхід на основі SWAP б'є CLZ! Зараз на мобільному телефоні. Можна опублікувати більше даних іншим разом, зараз на мобільному телефоні
Фредрік Піль,

@FredrikPihl Класно! Я щасливий, що старий добрий теоретичний підхід може (все-таки) бути корисним для практичного використання. :)
Рафаель

4

Це може бути чудовим додатком та тестом для проекту Souper . Супер - це супероптимізатор - інструмент, який бере як введення коротку послідовність коду і намагається максимально оптимізувати його (намагається знайти еквівалентну послідовність коду, яка буде швидшою).

Супер з відкритим кодом. Ви можете спробувати запустити Souper на фрагменті коду, щоб побачити, чи може він зробити краще.

Дивіться також конкурс Джона Регера щодо написання швидкого коду для сортування 16 4-бітних значень ; цілком можливо, що деякі з методів там можуть бути корисними.


Мені було б цікаво, що це може зробити в програмах, які намагаються ОП.
Рафаель

3

Ви можете використовувати a 213таблиця, яка отримує три цілих числа та виводить найбільші два. Потім можна використовувати три пошукові таблиці:

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

Аналогічно, використовуючи a 214 Таблиця, ви можете зменшити її до двох пошукових таблиць, хоча незрозуміло, що це буде швидше.

Якщо ви дійсно хочете невеликий столик, ви можете використовувати два 212таблиці "сортувати" два числа, а потім використовувати мережу сортування. Згідно з Вікіпедією , для цього потрібно не більше 18 пошукових таблиць (9 компараторів); ви можете зробити з меншим, оскільки (1) ви хочете знати лише два найбільші елементи, і (2) для деяких воріт компаратора вас може зацікавити лише максимум.

Ви також можете використовувати сингл 212стіл. Реалізація мережі сортування потім використовує менше доступу до пам'яті, але більше арифметичних. Таким чином ви отримуєте не більше 9 таблиць пошуку.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.