О сортировке (Ужасы Программирования - 2)
В комментариях к предыдущей записи Влад Шабанов предложил, казалось бы, отличное решение для лексикографической сортировки 8-байтных ключей (функция сравнения для qsort()):
#include "sys/endian.h";
int cmp(const void *d1,const void *d2)
{
uint64_t a1 = be64toh(* ((uint64_t *)d1));
uint64_t a2 = be64toh(* ((uint64_t *)d2));
return (a1 > a2) - (a1 < a2);
}
Однако результат бенчмарка оказался неожиданным: мое наивное и тупое в лоб решение оказалось более чем в полтора раза быстрее, чем предложенная компактная красота.
Будучи зацеплен словами про cmov, я безуспешно искал версию gcc, которая делала бы код с такими инструкциями, однако поиски оказались безуспешными: gcc 3.4, 4.1 и 4.3 для всех 64-битных процессоров делает одно и то же (при оптимизациях -O2/O3, без оптимизации там ужас-ужас):
... инициализация bswap %rdx bswap %rcx cmpq %rdx, %rcx seta %al cmpq %rdx, %rcx setb %dl movzbl %dl, %edx subl %edx, %eax retАссемблерный код варианта с bswap гораздо компактнее и красивее, чем то, что порождается из восьми последовательных if:
... инициализация... movzbl (%rdi), %edx movzbl (%rsi), %eax cmpb %al, %dl je .CONT .L20: .... тут возврат .CONT: movzbl 1(%rdi), %edx movzbl 1(%rsi), %eax cmpb %al, %dl jne .L20 и так еще 6 раз.
Понятно, что 8 инструкций, которые частично параллелятся, не должны бы быть быстрее 32 инструкций (которые тоже параллелятся, по меньшей мере вычислительная часть). Однако скучная линейная последовательность mov/mov/ cmp/jne оказывается быстрее даже вместе с оверхедом qsort и вызова callbacks, собственно код сравнения должен быть быстрее в разы.
Отгадка оказалась довольно тривиальной: ключи длинные, распределены равномерно, поэтому до 2-го и далее сравнений доходит не всегда. Можно наплевать на производительность, добавить 3 строчки кода и посчитать:
- Первое сравнение делается на моем тестовом множестве (18 млн ключей) 449 млн. раз.
- Второе - 302 млн.
- Третье - 146 млн.
- Четвертое - 10 млн.
- С пятого по седьмое - примерно по миллиону раз каждое.
Упражнения делались на Core 2 Q9300 (6 мегабайт кэша, но вроде бы каждой паре ядер доступна только половина), суммарный объем сортируемых данных - 140 мегабайт, типичные времена - 2.5-4 секунды.
Comments
> Отгадка оказалась довольно тривиальной Я думал, что на со
> Отгадка оказалась довольно тривиальной
Я думал, что на современных архитектурах, типа x86_64, чтение одного байта по скорости равно чтению 8 байт.
Sorry, подумал ещё раз. Ключи выровняны на границу 8 байт?
Sorry, подумал ещё раз. Ключи выровняны на границу 8 байт?
Да, конечно выравнены и конечно они в L1 сразу же
Да, конечно выравнены и конечно они в L1 сразу же
Алексей, изменятся ли бенчмарки от такого кода? #include <s
Алексей, изменятся ли бенчмарки от такого кода?
#include
static inline int cmp0(uint16_t c1, uint16_t c2)
{
return (c1 > c2) - (c1 < c2);
}
int cmp8(const void *d1,const void *d2)
{
uint16_t *p1 = (uint16_t *)d1;
uint16_t *p2 = (uint16_t *)d2;
#define CMPC(i) cmp0(p1[i],p2[i])
return CMPC(0) || CMPC(1) || CMPC(2) || CMPC(3);
}
ччёрт, как страшно отформатировался. думаю, что идея и так п
ччёрт, как страшно отформатировался. думаю, что идея и так понятна. раз на первые два байта приходится основная масса сравнений, то и сравнивать их лучше в одну операцию.
Ошибку с || вижу, кстати.
Ошибку с || вижу, кстати.
Просто bswap - это дико тормозная операция на Core2. Более о
Просто bswap - это дико тормозная операция на Core2. Более оптимально было бы через pshufb + pcmpgtq + ptest / movbe + cmp + sbb
Я на ассемблере влет не читаю, попробовал разобраться - и во
Я на ассемблере влет не читаю, попробовал разобраться - и вообще нашел патент про byte swap
Да вроде не дико, нормально тормозная. Вот нашел табличку -
Да вроде не дико, нормально тормозная. Вот нашел табличку - 4 такта latency, 1 такт на исполнение. Т.е. два bswap - 5-6 тактов.
http://swox.com/doc/x86-timing.pdf
Самый лучший список таймингов, который можно найти, находитс
Самый лучший список таймингов, который можно найти, находится на www.agner.org
Для Core2 4 такта latency - это до черта. Даже
<code>
ror ax, 8
ror eax, 16
ror ax, 8
</code>
несмотря на partial register stall получается быстрее, чем bswap eax
Для одного - быстрее, а для двух - вроде одинаково получаетс
Для одного - быстрее, а для двух - вроде одинаково получается.
Да, а pdf-ки по ссылке - отличные, спасибо!
Да, а pdf-ки по ссылке - отличные, спасибо!
забыл про be16toh сорри за флуд :)
забыл про be16toh
сорри за флуд :)
всё, на сегодня хватит. пора спать. static inline uint16_t
всё, на сегодня хватит. пора спать.
static inline uint16_t be16toh(uint16_t be)
{
return (be >> 8)|(be << 8);
}
int cmp8(const void *d1,const void *d2)
{
uint16_t *p1 = (uint16_t *)d1;
uint16_t *p2 = (uint16_t *)d2;
int c;
#define CMPC(i) if(__builtin_expect(c = be16toh(p1[i]) - be16toh(p2[i]),1)) return c
CMPC(0);
CMPC(1);
CMPC(2);
return be16toh(p1[3]) - be16toh(p2[3]);
}
Алексей, как этот вариант?
Алексей, как этот вариант?
Да я, честно говоря, тему уже проехал - теперь мне надо эти
Да я, честно говоря, тему уже проехал - теперь мне надо эти миллионы записей в BerkeleyDB или какой-то подобный аналог сложить за приемлемое время.
<pre> cmp8: .LFB3: movzwl (%rdi), %edx movz
cmp8:
.LFB3:
movzwl (%rdi), %edx
movzwl (%rsi), %eax
rolw $8, %dx
rolw $8, %ax
movzwl %dx, %edx
movzwl %ax, %eax
subl %eax, %edx
movl %edx, %eax
je .L5
.L2:
rep
ret
я, наверное, отстал от жизни, что что означает комбинация rep; ret?
А нельзя вначале поменять byteorder, отсортировать, потом по
А нельзя вначале поменять byteorder, отсортировать, потом поменять назад?
Я не думаю, что так будет выигрыш по перформансу.
Я не думаю, что так будет выигрыш по перформансу.
http://developers.slashdot.org/developers/08/11/23/1637219.s
http://developers.slashdot.org/developers/08/11/23/1637219.shtml
"Google has announced that they were able to sort one petabyte of data in 6 hours and 2 minutes across 4,000 computers.
Ага, невообразимо круто. Интересно, какое у них распределе
Ага, невообразимо круто.
Интересно, какое у них распределение ключей было. Если равномерное, то жизнь сильно легче.
Дрочим всем офисом!
Дрочим всем офисом!