«Математика» в программировании. Выравнивание.

Привет всем.

Народная мудрость гласит — «Если бедуин нашел вход в твое жилище, завали его и вырой новое»…

После того как я посмотрел в исходный код в 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)» реально выносит мозг:

В моем сознании такие вещи вычисляются так.

  1. Нужно найти максимальную степень двойки, на которую будет делиться sizeof(C).
  2. Потом использовать эту степень двойки для выравнивания указателя.

Я недавно упражнялся с аналогичной хренью, когда переделывал 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. Мне так кажется…

На сегодня все. Всем пока 🙂

2 комментария

Alex  on 20 февраля, 2013

Если 2009 (последнее реальное исправление, остальное стили и удаление ненужных более частей) — это свеже, тогда да — «свежезакоммиченного»

Насчёт sizeof(C) — 1: этот класс НЕ универсальный, у нас в коде применяется только для объектов размерами 2 4 8. И почто его в таком случае перетяжелять? Чтобы помедленней был?

Kovalenko  on 20 февраля, 2013

Да я мимо проходил, спотыкнулся и стукнулся головой (об эту строчку).

То что это заточено под степень двойки, это было понятно.

Добавь ассерты и пожалей моск следующих любопытных Варвар.
assert(sizeof(C)==2 ||
sizeof(C)==4 ||
sizeof(C)==8);
🙂

Leave a Comment