Наскільки добре потрібно добре закругленому програмісту при виконанні бітових операцій? [зачинено]


34

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

Ще один приклад, який ілюструє точку:

 1141       public static int bitCount(int i) {
 1142           // HD, Figure 5-2
 1143           i = i - ((i >>> 1) & 0x55555555);
 1144           i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);
 1145           i = (i + (i >>> 4)) & 0x0f0f0f0f;
 1146           i = i + (i >>> 8);
 1147           i = i + (i >>> 16);
 1148           return i & 0x3f;
 1149       }

Цей код можна знайти в класі Integer .

Я не можу не почувати себе дурним, коли дивлюся на це. Я пропустив клас чи два у коледжі чи це не те, що я маю просто отримати ? Я можу робити прості битові операції (такі як ANDing, ORing, XORing, shifting), але давай, як хтось придумує такий код вище?

Наскільки добре потрібно добре закругленому програмісту при виконанні бітових операцій?

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


4
Який тип робіт з розробки ви робите (або хочете робити, якщо ви цього не робите зараз)? Я не вважаю, що це корисно в веб-розробці, але я бачив багато операцій з розрядними вбудованими системами.
Томас Оуенс

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

11
Що на землі є >>>оператором?
DeadMG

10
@DeadMG: правильний зсув без підпису. download.oracle.com/javase/tutorial/java/nutsandbolts/op3.html
c_maker

3
// HD, Figure 5-2було б перше, на що я хотів би поглянути. Відповідно до коментарів на початку файлу, HDє Henry S. Warren, Jr.'s Hacker's Delight.
шнадер

Відповіді:


38

Я б сказав, що як добре закруглений розробник, ви повинні розуміти операторів і бітових операцій.

Отже, як мінімум, ви зможете розібратися в коді вище, трохи подумавши.

Бітові операції, як правило, досить низькі, тому якщо ви працюєте на веб-сайтах та програмному забезпеченні LOB, ви навряд чи будете їх багато використовувати.

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

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


1
+1: Бітові операції - це важлива частина знань (жодна каламбур не призначена) для будь-якого розробника, але вони зараз дуже важливі лише в конкретних ситуаціях. Якщо ви ніколи не стикалися з ними щодня, то мати загальні знання краще, ніж рабство над ними. Тримайте вільний простір мозку.
Ніколас Сміт

Ви також повинні розуміти, коли ви будете ними користуватися, і не ухилятися від їх використання, якщо вони є правильним рішенням проблеми.
user606723

Щоб додати до коментаря @ user606723 - насправді є лише кілька місць, де зазвичай використовуються побізні речі , які більш-менш часто зустрічаються - хешування (та пов'язані з ним речі) та вилучення / встановлення певних кольорів RGB, якщо вони зберігаються в int. Наприклад, інформацію про процесор можна прочитати, перевіривши бітові прапори, повернені з певного реєстру, але це включає в себе asm і, як правило, має більш високі рівні lvl обгортки.
TC1

36

Якщо ви розумієте, як вирішити проблеми на кшталт "визначити, чи встановлені біти 3 і 8", "очистити біт 5" або "знайти ціле значення, представлене бітами 7-12", у вас достатньо розуміння бітових операторів, щоб перевірити Can Поле "Бітдідл " у контрольному списку "добре".

Що у вашому прикладі походить від Hacker's Delight - компіляції високопродуктивних алгоритмів для маніпулювання невеликими бітами даних, такими як цілі числа. Хто спочатку написав цей код, не просто виплюнув його за п’ять хвилин; історія, що стоїть за ним, швидше за все, була потреба у швидкому, безгалузевому способі підрахунку бітів, і автор мав певний час витратити погляд на струни бітів і приготування способу вирішення проблеми. Ніхто не збирається зрозуміти, як це працює з першого погляду, якщо вони не бачили його раніше. Маючи чітке розуміння бітових основ і деякий час, витрачений на експерименти з кодом, ви, напевно, могли зрозуміти, як він це робить.

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

Користувач, який відповів на ваше запитання SO, можливо, бачив проблему раніше або вивчив хешування. Напиши йому і запитай.


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

3
+1 для вирішення абревіатури "HD" у коментарі до коду вище.
Péter Török

Я люблю подібний матеріал і щойно замовив книгу HD. Дякую за довідку.
tcrosley

8

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

1143 i = i - ((i >>> 1) & 0x55555555);

Ви повинні розпізнати бітовий візерунок 0x555 ... як змінний зразок біт 0101 0101 0101 і що оператори зміщують його на 1 біт (праворуч), і це & є операцією маскування (і що означає маскування).

1144 i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);

Знову викрійка, ця - 0011 0011 0011. Крім того, що цього разу вона зміщує два рази і знову маскується. зміщення та маскування слідує шаблону, який ви повинні розпізнати ...

1145 i = (i + (i >>> 4)) & 0x0f0f0f0f;

малюнок твердне. Цього разу це 00001111 00001111, і, звичайно, ми цього разу переносимо 4. кожного разу ми зміщуємося за розміром маски.

Повернення 1148 i & 0x3f;

інший бітовий візерунок, 3f - це блок нулів, за яким йде більший блок з них.

Всі ці речі повинні бути очевидними з першого погляду, якщо ви "добре закруглені". Навіть якщо ви ніколи не думаєте, що будете використовувати його, ви, ймовірно, упустите деякі можливості значно спростити код, якщо цього не знаєте.

Навіть мовою вищого рівня використовуються бітові малюнки для зберігання НАЙЧОЛЬШОГО обсягу даних у менших полях. Ось чому ви завжди бачите обмеження в 127/8, 63/4 і 255/6 в іграх, це тому, що вам доведеться зберігати стільки цих речей, що без упаковки полів ви змушені будете використовувати в десять разів більше об'єм пам'яті. (Ну, і остаточним було б, якби вам потрібно було зберігати величезну кількість булевих файлів в масиві, ви могли б заощадити в 32-64 рази більше пам'яті, як і коли б ви не думали про це - більшість мов реалізують булеві як слово, яке часто буде 32 біти. Ті, хто не відчуває себе комфортно на цьому рівні, будуть протистояти можливостям зберігати подібні дані просто тому, що бояться невідомого.

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


5

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

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


4

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

основні арифметичні операції + - * / %можна зрозуміти, не розуміючи цього, хоча це може бути зручно для мікрооптимізації (хоча більшу частину часу компілятор зможе подбати про це за вас)

набір маніпуляцій з бітами | & ~ ^ << >> >>>вимагає принаймні простого розуміння, щоб мати можливість їх використовувати

однак більшу частину часу ви будете використовувати їх лише для передачі бітових прапорів методу, який полягає у ORпоєднанні та передачі int, а потім ANDвидалення налаштувань читабельніше, ніж передача декількох (до 32) булевих символів у довгому списку параметрів і дозволяє можливі зміни прапорів без зміни інтерфейсу

не кажучи вже про булеві, як правило, зберігаються окремо в байтах або ints, а не упаковувати їх, як це робить прапорці


що стосується фрагмента коду, він робить паралельний підрахунок бітів, це дозволяє алгоритму запускатися O(log(n))там, де n - кількість бітів замість наївного циклу, який єO(n)

перший крок є найскладнішим для розуміння, але якщо ви почнете з налаштування, він повинен замінити бітові послідовності 0b00на 0b00, 0b01до 0b01, 0b10до 0b01і 0b11до 0b10них стає легше слідувати

тому для першого кроку, i - ((i >>> 1) & 0x55555555)якщо ми вважаємо iрівним, 0b00_01_10_11тоді має бути результат цього0b00_01_01_10

(зауважте, що 0x5дорівнює 0b0101)

iuf, якщо ми беремо i = 0b00_01_10_11це означає, що 0b00_01_01_10 - (0b00_00_11_01 & 0b01_01_01_01)стає 0b00_01_10_11 - 0b00_00_01_01в свою чергу0b00_01_01_10

вони могли зробити (i & 0x55555555) + ((i >>> 1) & 0x55555555)для того ж результату, але це 1 додаткова операція

наступні кроки проходять у подібному руслі


4
Найважливіша якість цього коду полягає в тому, що він не є галузевим, що, ймовірно, дає навіть більші переваги, ніж зниження складності.
Саймон Ріхтер

3

Кожен повинен розуміти основні розрядні операції. Саме склад основних операцій для виконання завдань оптимізованим, надійним способом вимагає багато практики.

Тим, хто щодня працює з битовими маніпуляціями (на зразок вбудованих людей), звичайно, буде розвиватися сильна інтуїція та приємний мішок хитрощів.

Скільки майстерності повинен мати програміст, який не робить речі низького рівня, маніпулюючи трохи? Досить, щоб можна було сісти за допомогою строфи, такої, яку ви вставили, і попрацювати над нею повільно, як це був тизер мозку або пазл.

Таким же чином я б сказав, що вбудований програміст повинен розуміти стільки ж http, скільки веб-розробник розуміє про дотепну маніпуляцію. Іншими словами, це "добре", щоб не бути майстром під час маніпуляцій, якщо ви не використовуєте його постійно.


3
Насправді в деяких випадках вбудований програміст повинен зрозуміти більше про http, ніж про веб-розробника (я роблю і те, і інше). Займаючись веб-розробкою, зазвичай можна розраховувати на якийсь тип фреймворку. Як вбудований розробник, який працює з підключеними до Інтернету пристроями, мені довелося кодувати стек http з нуля.
tcrosley

@tcrosely, так, ви абсолютно праві. Можливо, кращим прикладом, ніж "http", було б щось на кшталт "ORM" або "JEE". Основна суть у тому, що взагалі не можна оволодіти якоюсь темою, якщо вони не практикують її регулярно.
Анджело

Я погоджуюся, і мені ніколи не доводилося мати справу ні з ORM, ні з JEE ​​(тільки JME повернувся тоді, коли його називали J2ME).
tcrosley

3

Захоплення хакера - це похідна робота. Родоначальником усіх є HakMem з 1972 року. Http://w3.pppl.gov/~Hammett/work/2009/AIM-239-ocr.pdf

Важливо знати, що очевидний алгоритм будь-якого завдання не обов'язково є найкращим. Існує маса випадків, коли важливо знати про існування елегантного вирішення проблеми з кукурудзою.


Існує також версія HTML HAKMEM . Подивіться ITEM 169
Mackie Messer

3

Наскільки важко побитові оператори інтерпретувати?

Я програмую вбудовані системи. Я багато цього практикував. Ваше пов’язане питання про хеш-карти з кодом

static int hash(int h) {
   // This function ensures that hashCodes that differ only by
   // constant multiples at each bit position have a bounded
   // number of collisions (approximately 8 at default load factor).
   h ^= (h >>> 20) ^ (h >>> 12);
   return h ^ (h >>> 7) ^ (h >>> 4);
}

мав ідеальний сенс для мене приблизно стільки часу, скільки знадобиться диктувати код вголос. Події, описані в bitCount, одразу зрозумілі, але потрібна хвилина, щоб з’ясувати, чому він насправді рахує шматочки. Хоча коментарі були б чудовими, і це дозволило б зрозуміти, що код робить лише трохи важче, ніж хеш-проблема.

Важливо зробити відмінність між читанням і розумінням коду. Я можу інтерпретувати bitCountкод і читати, що він робить, але доведення, чому він працює, або навіть, що він працює, зайняв би хвилину. Існує різниця між можливістю плавного читання коду та вмінням чіпнути, чому код є таким, яким він є. Деякі алгоритми просто важкі. , Що з hashкоду має сенс, але коментар пояснив , чому що робиться. Не перешкоджайте, якщо функцію, що використовує побітові оператори, важко зрозуміти, вони часто використовуються для виконання складних математичних речей, які важко не залежать від формату.

Аналогія

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

Я знаю, як використовувати наступні елементи регулярного вираження:

  • [] класи персонажів
  • В *, .і +символи узагальнення
  • Початок рядка ^і кінець рядка$
  • Класи символів \ d, \ w та \ s
  • Прапор / г

Цього достатньо для створення простих запитів, і багато запитів, які я бачу, не відстають від цього.

Все, що не в цьому списку, я добираюся до шпаргалки. Що-небудь, тобто, крім {}і ()- обману не вистачить. Мені достатньо відомо про цих хлопців, щоб знати, що мені знадобиться дошка, довідковий посібник і, можливо, колега. Можна спакувати кілька шалених алгоритмів у кілька коротких рядків регулярного виразів.

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

Це там, де ви перебуваєте з побітними операторами?

Отже, ви хочете бути добре округленими?

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

  1. Вміти легко читати та записувати загальні операції
    Для програміста програм звичайні операції з побітовими операторами включають основні оператори |та &встановлюють та очищають прапори. Це повинно бути легко. Ви повинні вміти читати та писати подібні речі

    open('file', O_WRONLY | O_APPEND | O_CREAT );
    // Use an OR operator ^ here and ^ here to set multiple flags
    

    не гальмуючи (припускаючи, що ви знаєте, що означають прапори ).

  2. Вміти читати більш складні операції з деякою роботою
    Підрахунок бітів дуже швидко за O (log (n)) час без гілок, гарантуючи, що кількість зіткнень у хеш-кодах може відрізнятись обмеженою сумою та аналізувати адреси електронної пошти , номери телефонів або HTML з регулярним виразом - важкі проблеми. Кожному, хто не є експертом у цих областях, розумно домагатися дошки, це нерозумно бути нездатним почати працювати, щоб зрозуміти.

  3. Вмійте писати деякі складні алгоритми з великою кількістю роботи.
    Якщо ви не експерт, не варто сподіватися, що ви зможете робити складні та складні речі. Однак хороший програміст повинен мати можливість це зробити, працюючи над ним постійно. Зробіть цього достатньо, і незабаром ви будете експертом :)


2

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

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


2

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

Я не пригадую, щоб використовувати побітові оператори через 5 років або близько PHP (можливо, це лише я), а не через 10 років або близько того, для програмування Windows, хоча деякі матеріали нижчого рівня Windows складають біти.

Ви кажете: "Я не можу не відчувати себе дурним, коли дивлюся на це". НЕ - сердитися.

Ви щойно зустріли вихід програміста-ковбоя.

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

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

Ось хороше правило: єдині "голі цілі числа", дозволені в коді, є 0 1. 1. Всі інші числа повинні бути #defines, cost, enums тощо, залежно від вашої мови.

Якби ці 3 та 0x33333333 сказали щось на зразок NUM_WIDGET_SHIFT_BITS та WIDGET_READ_MASK, код було б легше читати.

Соромно, хто б це не розмістив у проекті з відкритим кодом, але навіть для особистого кодування добре коментуйте та використовуйте змістовні визначення / перерахунки та власні стандарти кодування.


Я вважаю шестнадцяткові константи також допустимими. 0xFF00набагато читабельніший (для мене), ніж 0b1111111100000000. Мені не хочеться рахувати, щоб визначити кількість встановлених бітів.
Кевін Вермер

1

Цей конкретний фрагмент коду виведений прямо з книги Хекерське захоплення , малюнок 5.2. Його онлайн в C (поп-функція) тут . Зверніть увагу, автор зараз рекомендує використовувати оновлені версії: http://www.hackersdelight.org/HDcode/newCode/pop_arrayHS.c.txt

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

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


1

Пояснення на прикладі. Дані - це послідовності бітів. Дозволяє рахувати біти на байті 01001101, маючи такі операції: 1. Ми можемо перевірити значення останнього біта. 2. Ми можемо змістити послідовність.

  1. 01001101 -> останній байт дорівнює 1, всього = 1. зрушення
  2. 10100110 -> останній байт дорівнює 0, всього = 1. зрушення
  3. 01010011 -> останній байт дорівнює 1, всього = 2. зрушення
  4. 10101001 -> останній байт дорівнює 1, всього = 3. зрушення
  5. 11010100 -> останній байт дорівнює 0, всього = 3. зрушення
  6. 01101010 -> останній байт дорівнює 0, всього = 3. зрушення
  7. 00110101 -> останній байт дорівнює 1, всього = 4. зрушення
  8. 10011010 -> останній байт дорівнює 0, всього = 4. зрушення

Наша відповідь: 4.

Це було не важко, чи не так? Велика справа з побітними операціями полягає в тому, що ми можемо зробити обмежені речі. Ми не можемо отримати доступ трохи безпосередньо. Але ми можемо, наприклад, знати значення останнього біта, порівнюючи його з MASK 00000001, і ми можемо зробити кожен біт останнім із операціями зсуву. Звичайно, отриманий алгоритм буде виглядати страшно для тих, до кого не звик. Нічого спільного з інтелектом.


0

Я б не сказав, що вам це потрібно, якщо робота, яку ви виконуєте, не стосується:

  • Обробка аудіо
  • Обробка відео
  • Графіка
  • Мережа (особливо там, де важливий розмір пакета)
  • Величезна кількість даних

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

Окрім цих областей, я вважаю це великим плюсом, якби розробник / старший розробник міг продемонструвати зміщення бітів та використання | & і ^, оскільки це проявляє інтерес до професії, яку, можна сказати, призводить до більш стійкого та надійного коду.

Наскільки не "отримувати" метод з першого погляду, вам потрібно пояснити, що він робить, і деяку інформацію. Я б не сказав, що це пов’язано з інтелектом, але наскільки ви знайомі з роботою з шістнадцятковим днем ​​і визнанням проблем, які певні зразки можуть вирішити.

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