«Математика» в программировании. Выравнивание.
Привет всем.
Народная мудрость гласит — «Если бедуин нашел вход в твое жилище, завали его и вырой новое»…
После того как я посмотрел в исходный код в FB, эта занятие снова стало эпизодически жрать мое время и мозг. Про второе заглядывание я не буду много говорить — оно меня не разочаровало. Сегодня вот, в третий раз решил посмотреть «чего нового?» и реально подвис на пару часов въезжая в одну единственную строчку кода свежезакоммиченного «Aligner.h». Если хотите и себе вынести мозг — читайте дальше.
// Aligns input parameter. template <typename C> class Aligner { private: #ifdef RISC_ALIGNMENT Firebird::HalfStaticArray<C, BUFFER_SMALL> localBuffer; #endif const C* bPointer; public: Aligner(const UCHAR* buf, ULONG len) { fb_assert(len % sizeof(C) == 0); #ifdef RISC_ALIGNMENT if ((IPTR) buf & (sizeof(C) - 1)) // <--- ВОТ ОНА, СМЕРТЬ МОЯ. { C* tempPointer = localBuffer.getBuffer(len/sizeof(C)+(len%sizeof(C)?1:0)); memcpy(tempPointer, buf, len); bPointer = tempPointer; } else #endif bPointer = reinterpret_cast<const C*>(buf); } //... };
Созерцая эту строчку, я даже вспомнил о своем высшем (прикладном) математическом образовании и о том, что я все (абсолютно все) выкинул из головы сразу после того как стал прикладываться к программированию «по-настоящему».
На квадратный трехчлен выражение в условии вроде не похоже…
Когда я начинаю думать о том что sizeof(C) не равен степени двойки и запускаю в голове вычисление, у меня в ней срабатывает предохранитель и она переходит в режим «полного идиота».
Общий смысл задачи, в целом-то понятен — нужно выявить невыравненный указатель на данные. И переместить данные в выравненный буфер.
Но вот логика «(IPTR) buf & (sizeof(C) — 1)» реально выносит мозг:
В моем сознании такие вещи вычисляются так.
- Нужно найти максимальную степень двойки, на которую будет делиться sizeof(C).
- Потом использовать эту степень двойки для выравнивания указателя.
Я недавно упражнялся с аналогичной хренью, когда переделывал Forward-Only хранилище рядов. Нужно было подобрать такой размер записи (в хранилище рядов), чтобы её заголовок всегда целиком помещался на странице хранилища и был выравнен. Адская математика, однако. Скажу честно — сформированный алгоритм я потом проверил тестом.
Вообщем, чтобы выйти из ступора, я накатал вот такой VBS-код, который сравнивает результаты FB-шного условия и моего. И прогнал его на небольшом диапазоне значений.
option explicit const c_N=12 dim p,s dim fb dim my for p=0 to c_N for s=1 to c_N fb=fb_align_test(p,s) my=my_align_test(p,s) if(fb<>my)then wscript.echo p&" "&s&" fb:"&fb&" my:"&my end if next next function fb_align_test(byval p,byval s) 'if ((IPTR) buf & (sizeof(C) - 1)) dim b b=(p And (s-1)) fb_align_test=(b<>0) end function 'fb_align_test function my_align_test(byval p,byval s) dim x,test_x x=0 test_x=1 while((s mod test_x)=0) x=test_x test_x=test_x*2 wend if(x=0)then err.raise -1,"my_align_test","p:"&p&" s:"&s dim n n=(p mod x) my_align_test=(n<>0) end function 'my_align_test
Вывод:
2 3 fb:Истина my:Ложь 2 7 fb:Истина my:Ложь 2 11 fb:Истина my:Ложь 3 3 fb:Истина my:Ложь 3 7 fb:Истина my:Ложь 3 11 fb:Истина my:Ложь 4 5 fb:Истина my:Ложь 4 6 fb:Истина my:Ложь 4 7 fb:Истина my:Ложь 5 5 fb:Истина my:Ложь 5 7 fb:Истина my:Ложь 6 3 fb:Истина my:Ложь 6 5 fb:Истина my:Ложь 6 6 fb:Истина my:Ложь 6 7 fb:Истина my:Ложь 6 11 fb:Истина my:Ложь 7 3 fb:Истина my:Ложь 7 5 fb:Истина my:Ложь 7 7 fb:Истина my:Ложь 7 11 fb:Истина my:Ложь 8 9 fb:Истина my:Ложь 8 10 fb:Истина my:Ложь 8 11 fb:Истина my:Ложь 8 12 fb:Истина my:Ложь 9 9 fb:Истина my:Ложь 9 11 fb:Истина my:Ложь 10 3 fb:Истина my:Ложь 10 7 fb:Истина my:Ложь 10 9 fb:Истина my:Ложь 10 10 fb:Истина my:Ложь 10 11 fb:Истина my:Ложь 11 3 fb:Истина my:Ложь 11 7 fb:Истина my:Ложь 11 9 fb:Истина my:Ложь 11 11 fb:Истина my:Ложь 12 5 fb:Истина my:Ложь 12 6 fb:Истина my:Ложь 12 7 fb:Истина my:Ложь 12 9 fb:Истина my:Ложь 12 10 fb:Истина my:Ложь 12 11 fb:Истина my:Ложь 12 12 fb:Истина my:Ложь
В целом, конечно, лучше переб выравнять чем не выравнять…
Но я бы не допер до «((IPTR) buf & (sizeof(C) — 1))».
Ну и, чтобы два раза не вставать.
fb_assert(len % sizeof(C) == 0); //... C* tempPointer=localBuffer.getBuffer(len/sizeof(C)+(len%sizeof(C)?1:0));
Если len должен быть кратным sizeof(C), то может стоило передавать сюда не длину буфера, а количество элементов в нем? И выкинуть все эти «len%sizeof(C)»?
И, чтобы получше стыковаться с 64-битной (Windows) платформой, вместо ULONG лучше юзать size_t. Мне так кажется…
На сегодня все. Всем пока 🙂
Alex on 20 февраля, 2013
Если 2009 (последнее реальное исправление, остальное стили и удаление ненужных более частей) — это свеже, тогда да — «свежезакоммиченного»
Насчёт sizeof(C) — 1: этот класс НЕ универсальный, у нас в коде применяется только для объектов размерами 2 4 8. И почто его в таком случае перетяжелять? Чтобы помедленней был?