Говнокод по пятницам. Эпизод 2. Перверсивное реверсирование

@ЕвгенийП жду Ваш код - Говнокод по пятницам. Эпизод 2. Перверсивное реверсирование - #19 от пользователя Komandir

Код из #1 рождает 26 тактов 36 байт:

SWAP  R24
MOV R25,R24
ANDI  R25,$33
LDI R20,$04
MUL R25,R20
MOVW  R19:R18,R1:R0
CLR R1
ANDI  R24,$CC
LSR R24
LSR R24
OR  R18,R24
MOV R24,R18
ANDI  R24,$55
LSL R24
ANDI  R18,$AA
LSR R18
OR  R24,R18
RET

Компилятор увидел обмен нибблов, но почему то один двойной сдвиг заменил умножением …

а можно побитово менять от центра, D3 <->D4, D2<->D5, D1 <->D6, D0 <->D7?

BST BLD - любой бит можно скопировать/сохранить

Вариант по #1, но без заморочек от компилятора:

uint8_t __attribute__((noinline)) rb(uint8_t b) {
uint8_t r;
   asm volatile (
   "SWAP %1 \n\t"
   "MOV %0, %1 \n\t"
   "ANDI %0,0xCC \n\t"
   "LSR %0 \n\t"
   "LSR %0 \n\t"
   "ANDI %1,0x33 \n\t"
   "LSL %1 \n\t"
   "LSL %1 \n\t"
   "OR %1,%0 \n\t"
   "MOV %0,%1 \n\t"
   "ANDI %0,0xAA \n\t"
   "LSR %0 \n\t"
   "ANDI %1,0x55 \n\t"
   "LSL %1 \n\t"
   "OR %1,%0 \n\t"
   : "=&r" (b), "=&r" (r)
   : 
   :);
   return r;
}

32 байта и 23 такта вместе с вызовом и возвратом …

Да, да, обязательно, мне это интересно. Просто сегодня дома завал и аврал, разве только к вечеру или завтра.

Маленький шаг в сторону китайца))

Спойлер
uint8_t BITrvrs(uint8_t a)
{
return a/128+((a%128)/64)*2+((a%64)/32)*4+((a%32)/16)*8+((a%16)/8)*16+((a%8)/4)*32+((a%4)/2)*64+(a%2)*128;
}

Почитал…
Пока подожду…
Но я вам, в соответствии с заголовком темы, не советую говнокодить, по понедельникам, вторникам … etc
Не тот “код” получается)

Я еще раз предложу, а то может не заметили:
Могу объяснить теорию китайского решения. И рассказать, как можно обобщить на более длинное число. Мне кажется это единсвенное интересное в теме, а не цикл сдвигов на асме. Но “Edem das seine”, как было написано в одном известном месте.

Можно и старый, верный, САМЫЙ БЫСТРЫЙ - табличный метод применить…
Если 256 байт жалко - можно 16 под полубайт, но опять же за счёт скорости …

uint8_t r[] = {
  0x0, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0, 0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0,
  0x8, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8, 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8,
  0x4, 0x84, 0x44, 0xC4, 0x24, 0xA4, 0x64, 0xE4, 0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4,
  0xC, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC, 0x1C, 0x9C, 0x5C, 0xDC, 0x3C, 0xBC, 0x7C, 0xFC,
  0x2, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2, 0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2,
  0xA, 0x8A, 0x4A, 0xCA, 0x2A, 0xAA, 0x6A, 0xEA, 0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA,
  0x6, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6, 0x16, 0x96, 0x56, 0xD6, 0x36, 0xB6, 0x76, 0xF6,
  0xE, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE, 0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE,
  0x1, 0x81, 0x41, 0xC1, 0x21, 0xA1, 0x61, 0xE1, 0x11, 0x91, 0x51, 0xD1, 0x31, 0xB1, 0x71, 0xF1,
  0x9, 0x89, 0x49, 0xC9, 0x29, 0xA9, 0x69, 0xE9, 0x19, 0x99, 0x59, 0xD9, 0x39, 0xB9, 0x79, 0xF9,
  0x5, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5, 0x15, 0x95, 0x55, 0xD5, 0x35, 0xB5, 0x75, 0xF5,
  0xD, 0x8D, 0x4D, 0xCD, 0x2D, 0xAD, 0x6D, 0xED, 0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD, 0x7D, 0xFD,
  0x3, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3, 0x13, 0x93, 0x53, 0xD3, 0x33, 0xB3, 0x73, 0xF3,
  0xB, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB, 0x1B, 0x9B, 0x5B, 0xDB, 0x3B, 0xBB, 0x7B, 0xFB,
  0x7, 0x87, 0x47, 0xC7, 0x27, 0xA7, 0x67, 0xE7, 0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7,
  0xF, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF, 0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xff
};

uint8_t __attribute__((noinline)) rb(uint8_t b) {
  return r[b];
}

14 тактов вместе с вызовом и возвратом …

MOV R30,R24
LDI R31,$00
SUBI R30,$00
SBCI R31,$FF
LD R24,Z
RET

и я о том жеж )))

Ну быстрее чем RBIT на ARM не получится - таблицу надо подгрузить, выбрать байт по смещению …

таблицу оставим в памяти, просто адрес на неё берём, понадобится таблица всего 256 байт, универсальная, для uint16_t просто реверсивные байты поменять местами, осталось понять как это реализовать для ARM, чесслово даже смотреть asm ARM ни грамма не хочется
PS таблицы видимо попадают в говнокод

ЗЫ …видимо подошёл к тому возрасту когда на мои вопросы к наладчику из Прибалтики как отлаживаться в тактах процессора услышал…ты посмотри какие у Вас девушки…какие у них огромные глаза )))

Просто в ARM камнях больше маленьких китайцев внутри сидит …

Не-не-не!
Только 32-разрядное целое.
И с возможностью масштабировать на 64 разряда.

Кое-как разгрёб и вернулся к нашим баранам.

В общем, изначально, когда я думал об ассемблере, я думал написать через обычные or’ы (на перетаскивание флага из регистра в регистр, как предложил @Komandir в №19 у меня ума не хватило :frowning: )

Ну, я написал через or’ы и сделал небольшой тестовый стенд для всех трёх подходов (из первого сообщения в строках №№ 53-57, решение от @Komandir в строках №№ 1-24 и с моими or’ами в строках №№ 26-51 – правда нет ни одного or’а, но думал я именно в таких терминах :slight_smile: )

Тестовый стенд
uint8_t __attribute__((noinline)) rbk(uint8_t b) {
  uint8_t r;
  asm volatile (
    "ROL %1 \n\t"
    "ROR %0 \n\t"
    "ROL %1 \n\t"
    "ROR %0 \n\t"
    "ROL %1 \n\t"
    "ROR %0 \n\t"
    "ROL %1 \n\t"
    "ROR %0 \n\t"
    "ROL %1 \n\t"
    "ROR %0 \n\t"
    "ROL %1 \n\t"
    "ROR %0 \n\t"
    "ROL %1 \n\t"
    "ROR %0 \n\t"
    "ROL %1 \n\t"
    "ROR %0 \n\t"
    : "=&r" (r)
    : "r" (b)
    :);
  return r;
}

uint8_t __attribute__((noinline)) rbo(uint8_t b) {
	asm volatile (
		"mov __tmp_reg__, %0 \n\t"
		"clr %0 \n\t"
		"sbrc __tmp_reg__, 0 \n\t"
		"sbr  %0, 0x80 \n\t"
		"sbrc __tmp_reg__, 1 \n\t"
		"sbr  %0, 0x40 \n\t"
		"sbrc __tmp_reg__, 2 \n\t"
		"sbr  %0, 0x20 \n\t"
		"sbrc __tmp_reg__, 3 \n\t"
		"sbr  %0, 0x10 \n\t"
		"sbrc __tmp_reg__, 4 \n\t"
		"sbr  %0, 0x08 \n\t"
		"sbrc __tmp_reg__, 5 \n\t"
		"sbr  %0, 0x04 \n\t"
		"sbrc __tmp_reg__, 6 \n\t"
		"sbr  %0, 0x02 \n\t"
		"sbrc __tmp_reg__, 7 \n\t"
		"sbr  %0, 0x01 \n\t"
			: 
			: "r" (b)
			: 
	);
	return b;
}

uint8_t rbi(uint8_t b) {
   b = (b & 0xF0) >> 4 | (b & 0x0F) << 4; // меняем местами нибблы (4-ки битов)
   b = (b & 0xCC) >> 2 | (b & 0x33) << 2; // внутри каждой четвёрки меняем местами пары битов
   return (b & 0xAA) >> 1 | (b & 0x55) << 1;	// внутри каждой пары меняем местами биты
}


uint32_t doTest(uint8_t (* f) (uint8_t) ) {
	const uint32_t start = micros();
	for (uint16_t i = 0; i < 256 * 16; f(i++));
	return micros() - start;
}

void printOneLine(const char * name, const uint32_t time, const float percent) {
	Serial.print(name);
	Serial.print(": ");
	Serial.print(time);
	Serial.print(" (");
	Serial.print(percent);
	Serial.println("%)");
}
	
void setup (void) {
	Serial.begin(9600);

	const uint32_t rbiTime = doTest(rbi);
	const uint32_t rboTime = doTest(rbo);
	const uint32_t rbkTime = doTest(rbk);

	const float onePercent = rbiTime / 100.0;

	const float rbiPercent = rbiTime / onePercent;
	const float rboPercent = rboTime / onePercent;
	const float rbkPercent = rbkTime / onePercent;
	
	printOneLine("rbi", rbiTime, rbiPercent);
	printOneLine("rbo", rboTime, rboPercent);
	printOneLine("rbk", rbkTime, rbkPercent);
}

void loop(void) {}
Результат тестирования
rbi: 10044 (100.00%)
rbo: 8764 (87.26%)
rbk: 8500 (84.63%)

Ну, ничего неожиданного – ROL’ы от @Komandir быстрее всех, а вариант на Си – медленнее всех. А кто-то ожидал чего-то другого?

Но, всё-таки, эти ассемблерные решения я как-то говнокодом не считаю. Так что – нещитова :slight_smile:

Без таблицы самый быстрый код в №24 - он копия Си шного, только компилятор что то намудрил и раздул код …

Коллеги! Инверсия через флаг является стандартным решением, описанным в куче литературы по системному программированию. Если не тратить такты на цикл и просто 8 раз повторить, по выходит идеальное решение. обращение к таблице даст сравнимый результат, при хорошем кэше процессора.

Но все равно, странно, что вы не понимаете, что китайское решение - красивая математика, а не манипуляция с командами процессора. Правильно же сказали, что у некоторых просто в системе команд есть инверсия за один такт. И что?
А если нет команд процессора? Недоступен низкий уровень.
Представьте далекое будущее, когда микроуровень давно отдан ИИ и никто не лезет в эти дебри, этому не учат в универах и даже преподаватели С++ уже померли. Все программирование в “промптах” для ИИ. Попробуй ему внятно объяснить, что тебе надо?

Так вроде не об инверсии идет речь, а об реверсе или зеркальном преобразовании. Это же разные вещи. Не? такое преобразование сдвигом через флаг С не сделать.

Да ему обидно, что никто не отреагировал:

Давай, объясняй. Мог бы и не спрашивать, раз так сильно хочется)))

Из сегодняшнего ))

:upside_down_face:

Читал ранее или сейчас читаешь? Чего в теме той не отметился?))