avva: (Default)
[personal profile] avva
Итак, подробное обсуждение вчерашней загадки: что именно делает функция
unsigned char func(unsigned char c) {
  return c - 128 + (c >> 5&3)["(PI)"];
}


и как она это делает?


Правильный ответ: эта функция переводит шестнадцатиричную цифру в ее значение, причем как большие буквы, так и маленькие. Иными словами, эта функция переводит '0'...'9' в 0..9, 'A'...'F' в 10...15, и 'a'...'f' в 10...15. Подходящее название для этой функции - hex2int (или hex_to_int, или HexToInt, как пожелаете).

Первым правильный ответ дал [livejournal.com profile] renatm, второй - [livejournal.com profile] ashalynd. Потом еще многие дали правильный ответ. Некоторые комментаторы справедливо заметили, что функция работает не только для 16-ричных цифр, но и для других оснований, до конца алфавита. Это верно, но скорее побочный эффект, а не задумано так: в оригинале функция назывались HexToInt.

Немного о том, как это происходит. Запись "c >> 5&3" специально обманчива, потому что очередность операторов такова, что на самом деле это (c >> 5) & 3. Т.е. вынимаем из c шестой и седьмой биты. Далее, странное применение оператора [] - это старый трюк, который иногда используется для специального усложнения исходников, и больше ни на что особенно не годен. Дело в том, что в стандартах C и C++ значение оператора [] определяется так: a[b] это ровно то же самое, что *(a+b). Это формальное выражение известного принципа о том, что работа с массивами в C сводится (по большей части) к арифметике с указателями. А это значит, что, например, a[1] и 1[a] - совершенно одно и то же, хотя второй вариант выглядит странно и в нормальных программах никто так никогда не пишет.

Так что эта часть функции сводится к "(PI)"[(c>>5) &3]. Это значит, что из c мы вычитаем 128, а потом прибавляем, в зависимости от 6-7 бит c, одно из чисел 40, 80, 73, 41 (численные значения '(', 'P', 'I', ')' ).
Поскольку вся арифметика тут 8-битная беззнаковая (unsigned), то тот факт, что при вычитании 128 мы можем перепрыгнуть в верхние 128 байт, а потом при прибавлении перепрыгнуть 0 обратно, никакой роли не играет, и можно это упростить так: -128 + 40/80/73/41 все равно, что -88/-48/-55/-87, в зависимости от шестого-седьмого бита c. Ну а теперь осталось заметить, что это можно написать так: -88 / -'0' / -('A'-10) / -('a'-10), а также заметить, что для символов '0'..'9', 'A'..'F', 'a'..f' шестой и седьмой биты получаются соответственно 1, 2 и 3. Теперь, надеюсь, понятно, почему получается желаемый результат. Первое число из четырех - оно же левая скобка '(' - вообще никакой роли не играет, и я выбрал ее просто для симметрии, чтобы получилась красивая строка (PI).

До того, как над ней поработали усложнители (до меня - еще несколько коллег на внутренней рассылке), исходная функция выглядела так:
inline unsigned char HexToInt(unsigned char c) {	 
  DCHECK(IsHexDigit(c));	 
  static unsigned char kOffset[4] = {0, 0x30u, 0x37u, 0x57u};	 
  return c - kOffset[(c >> 5) & 3];	 
}	 

Она была в одном из исходных файлов браузера Chrome, но полгода назад ее убрали, когда кто-то справедливо решил методически убить расплодившиеся функции перевода шестнадцатеричных цифр. Вот чекин, в котором ее убивают.

Наверное, кому-то может быть интересно знать, зачем вообще писать эту функцию таким странным и непонятным способом, а не очевидный код, который проверяет, находится ли символ между '0' и '9', и тогда вычитает '0', потом между 'A' и 'F', и так далее. Все это можно написать одним выражением, используя оператор ?:, конечно. Этот вопрос затрагивает интересные и весьма нетривиальные соображения насчет оптимизации кода для современных CPU. С одной стороны, чем меньше код обращается к памяти, тем лучше, потому что обращение к памяти, особенно не попадающее в кэш процессора, занимает в наше время астрономическое время по сравнению с операциями в регистрах - в десятки или сотни раз дольше. Да и даже если обращение попадает в кэш, это может означать, что что-нибудь другое полезное туда не попадет, он ведь не резиновый. Поэтому, казалось бы, эта версия с массивом должна быть медленней, чем "очевидный" код, который я описал выше. Но с другой стороны, в "очевидной" версии есть условные переходы (после сравнения мы выбираем, что дальше делать), а они на современных архитектурах тоже весьма дороги, потому что если процессору не удастся верно угадать, какой из двух выборов ему предстоит совершить, то ему придется обнулить конвейер (flush the pipeline) уже прочитанных и приготовленных инструкций.

Поэтому я не знаю точно, какая из двух версий функции HexToInt - "очевидная", сравнивающая символы, или "странная", приведенная выше, быстрее на практике. Подозреваю, что зависит от того, как часто она вызывается: если миллионы раз подряд, то наверняка быстрее "странная", потому что вспомогательный массив будет плотно сидеть в кэше все время; если редко, то "очевидная" может оказаться быстрее. В любом случае, если нет необходимости оптимизировать именно эту функцию - а такую функцию почти никогда нет нужды оптимизировать - я лично написал бы "очевидную" версию, конечно - именно потому, что ее легче прочесть и сразу понять.

В качестве подарка тем, кто дочитал до конца - еще две версии функции HexToInt; подозреваю, что они обе быстрее тех двух, что я рассматривал выше. Несомненно, вы сами теперь сможете разобраться, как они работают.

Первая версия взята из исходников SQLite:

return (c + 9*(1 & (c>>6))) & 15;


Вторую версию написал на рабочей рассылке коллега по имени Дэвид Тернер:

return c - (0x57373000 >> (c >> 5)*8);

Date: 2011-03-04 01:33 pm (UTC)
From: [identity profile] migmit.livejournal.com
Помнится, в замечательной игрушке "Heroes of MatMex" один из монс...преподавателей предлагал игроку написать визуализацию байта за 11 байт.

Я пока так и не знаю, как это делается.

Date: 2011-03-04 02:09 pm (UTC)
ext_373361: (Default)
From: [identity profile] imenno.livejournal.com
По-видимому, надо это сделать на каком-то ассемблере? Каком?

Date: 2011-03-04 02:23 pm (UTC)
From: [identity profile] migmit.livejournal.com
Ну, кроме x86 мы тогда вообще ничего не знали.
Мозговой штурм предложил следующую формализацию задачи: 16-битный x86, исходно в некотором (по желанию программиста) 8-битном регистре находится нужный байт, содержимое прочих регистров неизвестно; на выходе в 16-битном регистре (тоже по выбору программиста) находятся ASCII-коды двух 16-ричных цифр, составляющих этот байт, в любом удобном регистре.
В общем, задачу мы всё равно не решили. Лучшим приближением стал найденный в какой-то книжке способ визуализации половинки байта за 5 байт.

(no subject)

From: [identity profile] penguinny.livejournal.com - Date: 2011-03-04 06:35 pm (UTC) - Expand

(no subject)

From: [identity profile] andy-shev.livejournal.com - Date: 2011-03-05 10:18 am (UTC) - Expand

Date: 2011-03-04 01:41 pm (UTC)
From: [identity profile] http://users.livejournal.com/_zlot_/
Интересно пережует ли обычный binary translation исходную простую функцию hex2int в быструю версию автоматически.

Date: 2011-03-04 01:44 pm (UTC)
From: [identity profile] penguinny.livejournal.com
Версия коллеги крайне элегантна.

Date: 2011-03-04 01:50 pm (UTC)
From: [identity profile] penguinny.livejournal.com
Кстати,

return c - (0x57373000 >> ((c >> 5) << 3));

должно бы быть ещё быстрее.

Date: 2011-03-04 01:53 pm (UTC)
From: [identity profile] netp-npokon.livejournal.com
Ну уж это-то компилятор сам догадается.
Вообще интересно было бы сравнить ассемблер, а не реализацию на C.

(no subject)

From: [identity profile] penguinny.livejournal.com - Date: 2011-03-04 02:02 pm (UTC) - Expand

(no subject)

From: [identity profile] netp-npokon.livejournal.com - Date: 2011-03-04 02:12 pm (UTC) - Expand

(no subject)

From: [identity profile] penguinny.livejournal.com - Date: 2011-03-04 02:19 pm (UTC) - Expand

(no subject)

From: [identity profile] penguinny.livejournal.com - Date: 2011-03-04 02:58 pm (UTC) - Expand

(no subject)

From: [identity profile] madfire.livejournal.com - Date: 2011-03-04 09:59 pm (UTC) - Expand

(no subject)

From: [identity profile] penguinny.livejournal.com - Date: 2011-03-04 02:12 pm (UTC) - Expand

Date: 2011-03-04 01:57 pm (UTC)
From: [identity profile] mudak.livejournal.com
не должно, т.к. компилятор сам превращает *8 в <<3

Date: 2011-03-04 02:10 pm (UTC)

Date: 2011-03-04 01:46 pm (UTC)
From: (Anonymous)
Блин, а гугол-то окукливается. Пишет извращенские функции, якобы чтобы сэкономить условный переход, в то время как нормальные компиляторы сами умеют от них избавляться, используя такие операции процессора как set и cmov.

Date: 2011-03-04 02:10 pm (UTC)
ext_373361: (Default)
From: [identity profile] imenno.livejournal.com
Тернер рулит!

Date: 2011-03-04 02:20 pm (UTC)
From: [identity profile] potan.livejournal.com
Пожалели сделать массив из 256 байт?
Какая там сейчас строка кеша используется...

Date: 2011-03-04 03:22 pm (UTC)
From: [identity profile] korvinz.livejournal.com
блин какой же я профан во всем этом (((( Хоть на мастеров посмотреть

Date: 2011-03-04 03:23 pm (UTC)
alexeybobkov: (Default)
From: [personal profile] alexeybobkov
Что касается запутанного кода, то для меня вне конкуренции всегда был вот этот шедевр, вычисляющий "пи".

Date: 2011-03-04 03:28 pm (UTC)
From: [identity profile] avva.livejournal.com
Да, это классика :)
(deleted comment)

(no subject)

From: [identity profile] avva.livejournal.com - Date: 2011-03-04 06:59 pm (UTC) - Expand

Раз пошла такая пьянка

Date: 2011-03-04 08:14 pm (UTC)
From: (Anonymous)
Из моего проэкта, покорёжено для нечитабельности:

long func2(double d)
{
d += 6755399441055744.0;
return *(__int64*)(&d);
}

Date: 2011-03-05 06:41 pm (UTC)
From: [identity profile] yms.livejournal.com
ну, "пи" - это громко сказано.
выдает 3.156

Date: 2011-03-04 05:18 pm (UTC)
From: [identity profile] ilya-dogolazky.livejournal.com
Соображение про выбор обращения к памяти по сравнению с ветвлением недействительно для армовой архитектуры, где ?: далеко не всегда ветвится.

Date: 2011-03-04 06:01 pm (UTC)
From: [identity profile] avva.livejournal.com
Спасибо, я совсем почти не знаю армовую архитектуру, а надо бы как-то ознакомиться.

(no subject)

From: [identity profile] ilya-dogolazky.livejournal.com - Date: 2011-03-04 06:24 pm (UTC) - Expand

Date: 2011-03-04 05:51 pm (UTC)
From: (Anonymous)
При всем при том, что функция по таблице работает значительно быстрей. А отставание на условном переходе ну очень уж минимальное.

Скажите, а вы там проверяли на практике результаты или это ради развлечения?

Date: 2011-03-04 06:01 pm (UTC)
From: [identity profile] avva.livejournal.com
Простите, не очень понял, какой из вариантов вы называете "функцией по таблице".

В ответ на ваш вопрос: это чисто ради развлечения. Полгода назад, когда эту функцию удалили из исходников Хрома, это было не потому, что она быстрее или медленнее, а чтобы код использовал одну стандартную фунцкию для этого дела, а не десять разных[1], которые накопились в разных местах.

Вообще не очень легко представить естественные обстоятельства, при которых оптимизация этой функции может дать нетривиальный вклад в performance. 16-ричные цифры должны придти из какого-то источника - например, файла или веб-страницы - и тогда время, затраченное в I/O на чтение этой цифры, на несколько порядков больше времени любого из вариантов HexToInt(). Если, может быть, кто-то делает очень интенсивные вычисления на 16-ричных строках, переводя их в числа и обратно миллионы раз все время - но кто ж будет так извращаться.

[1] Я не знаю точно, сколько их было, скорее всего меньше десяти.

(no subject)

From: (Anonymous) - Date: 2011-03-04 07:56 pm (UTC) - Expand

(no subject)

From: (Anonymous) - Date: 2011-03-04 08:03 pm (UTC) - Expand

(no subject)

From: [identity profile] avva.livejournal.com - Date: 2011-03-04 08:53 pm (UTC) - Expand

(no subject)

From: [identity profile] igogo3000.livejournal.com - Date: 2011-03-04 10:06 pm (UTC) - Expand

(no subject)

From: [identity profile] avva.livejournal.com - Date: 2011-03-04 10:12 pm (UTC) - Expand

(no subject)

From: [identity profile] igogo3000.livejournal.com - Date: 2011-03-04 10:37 pm (UTC) - Expand

(no subject)

From: [identity profile] avva.livejournal.com - Date: 2011-03-04 10:44 pm (UTC) - Expand

(no subject)

From: [identity profile] igogo3000.livejournal.com - Date: 2011-03-04 10:49 pm (UTC) - Expand

(no subject)

From: [identity profile] igogo3000.livejournal.com - Date: 2011-03-04 10:57 pm (UTC) - Expand

(no subject)

From: [identity profile] avva.livejournal.com - Date: 2011-03-04 11:24 pm (UTC) - Expand

(no subject)

From: [identity profile] igogo3000.livejournal.com - Date: 2011-03-04 11:53 pm (UTC) - Expand

(no subject)

From: [identity profile] avva.livejournal.com - Date: 2011-03-05 12:11 am (UTC) - Expand

(no subject)

From: [identity profile] igogo3000.livejournal.com - Date: 2011-03-05 01:23 am (UTC) - Expand

(no subject)

From: [identity profile] igogo3000.livejournal.com - Date: 2011-03-05 01:24 am (UTC) - Expand

(no subject)

From: [identity profile] igogo3000.livejournal.com - Date: 2011-03-04 09:04 pm (UTC) - Expand

(no subject)

From: (Anonymous) - Date: 2011-03-05 05:25 am (UTC) - Expand

Date: 2011-03-04 07:22 pm (UTC)
From: [identity profile] zigmar.livejournal.com
Красиво. -128 меня запутал. Я подумал про перевод, проверил индексы букв "(PI)", и обнаружив что они не на что внятное не попадают, стал копать в другую сторону :)

Date: 2011-03-06 06:29 pm (UTC)
From: [identity profile] digest.livejournal.com
Знавал я компиляторы в embedded, которые вполне могли "(PI)" засунуть в какой-нибудь .const, а весь сегмент .const - в медленнющий некешируемый ROM, ну да не суть, понятно же, что задача усложнена нарочно, а версии с константами и шифтами действительно красивые.

Вот такой вопрос у меня возник: на интервью ты бы завалил кандидата, не решившего эту задачу?

Date: 2011-03-07 01:18 pm (UTC)
From: [identity profile] gianthare.livejournal.com
Исходя из допущения, что ASCII-table надо наизусть знать?

(no subject)

From: [identity profile] digest.livejournal.com - Date: 2011-03-07 01:28 pm (UTC) - Expand

(no subject)

From: [identity profile] gianthare.livejournal.com - Date: 2011-03-07 01:31 pm (UTC) - Expand

(no subject)

From: [identity profile] gianthare.livejournal.com - Date: 2011-03-07 01:32 pm (UTC) - Expand

(no subject)

From: [identity profile] avva.livejournal.com - Date: 2011-03-07 01:34 pm (UTC) - Expand

(no subject)

From: [identity profile] digest.livejournal.com - Date: 2011-03-07 01:42 pm (UTC) - Expand

(no subject)

From: [identity profile] gianthare.livejournal.com - Date: 2011-03-07 01:44 pm (UTC) - Expand

(no subject)

From: [identity profile] digest.livejournal.com - Date: 2011-03-07 01:52 pm (UTC) - Expand

(no subject)

From: [identity profile] gianthare.livejournal.com - Date: 2011-03-07 01:53 pm (UTC) - Expand

(no subject)

From: [identity profile] avva.livejournal.com - Date: 2011-03-07 02:03 pm (UTC) - Expand

(no subject)

From: [identity profile] digest.livejournal.com - Date: 2011-03-07 02:11 pm (UTC) - Expand

(no subject)

From: (Anonymous) - Date: 2011-03-12 09:35 pm (UTC) - Expand

December 2025

S M T W T F S
  123 4 56
78 9 10 11 1213
1415 1617181920
21 22 23 24 2526 27
28293031   

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated Dec. 29th, 2025 11:39 am
Powered by Dreamwidth Studios