Мне нужна функция контрольной суммы Интернета (одна контрольная сумма) для некоторого кода обработки ICMP ICv4 с использованием сырых сокетов, и я наткнулся на поведение, которое я не могу объяснить на 64-разрядном процессоре Intel (с использованием gcc 4.8.2). Мне было интересно, может ли кто-нибудь пролить свет на него.
Я реализовал первую функцию контрольной суммы, используя 32-разрядный аккумулятор и выполнив 16-разрядные суммы. Затем я реализовал то же самое с использованием 64-разрядного аккумулятора и 32-разрядных сумм, считая, что меньшее количество сумм приведет к более быстрому выполнению. В результате первая реализация выполняется в два раза быстрее, чем вторая (с оптимизацией O3). Я просто не могу понять, почему...
Приведенный ниже код фактически не выполняет точные контрольные суммы (я упростил его), но иллюстрирует проблему. Оба скомпилированы как 64-разрядные, работающие на 64-битной собственной платформе (LP64: короткие 16-битные, int 32-разрядные, длинные 64-разрядные, указатели 64-разрядные).
-
32-разрядный аккумулятор и 16-разрядные суммы
unsigned short cksum_16_le(unsigned char* data, size_t size) { unsigned short word; unsigned int sum = 0; unsigned int i; for(i = 0; i < size - 1; i += 2) sum += *((unsigned short*) (data + i)); sum = (sum & 0xffff) + (sum >> 16); sum = (sum & 0xffff) + (sum >> 16); return ~sum; }
50 000 вызовов функций по тем же самым данным 10k: ~ 1,1 секунды.
-
64-разрядный аккумулятор и 32-разрядные суммы
unsigned short cksum_32_le(unsigned char* data, size_t size) { unsigned long word; unsigned long sum = 0; unsigned int i; for(i = 0; i < size - 3; i += 4) sum += *((unsigned int*) (data + i)); sum = (sum & 0xffffffff) + (sum >> 32); sum = (sum & 0xffffffff) + (sum >> 32); sum = (sum & 0xffff) + (sum >> 16); sum = (sum & 0xffff) + (sum >> 16); return ~sum; }
50 000 вызовов функций по тем же самым данным 10k: ~ 2,2 секунды.
Update:
Кажется, что проблема связана с оборудованием. Запуск диагноза памяти выявил случайные ошибки четности шины (не уверен, почему это повлияет на 32-битную версию больше, чем на 16-разрядную версию, но там вы идете). Код работает как ожидалось на других серверах. Удалит вопрос в течение ближайших нескольких часов (связавшись с оборудованием, он больше не особенно полезен).
Окончательное обновление:
Интересно, что замена циклов for
на циклы while
и компиляция с оптимизацией O3 (показана ниже для случая с 64-разрядным аккумулятором) приводит к тому, что 32-разрядные и 64-разрядные накопители будут работать с одинаковой скоростью. Это связано с тем, что компилятор выполняет некоторую циклическую развертку (как ни странно, он не разворачивает цикл for
) и выполняет суммирование с помощью регистров mmx
...
uint64_t sum = 0;
const uint32_t* dptr = (const uint32_t*) data;
while (size > 3)
{
sum += (uint32_t) *dptr++;
size -= 4;
}