Skip to Content

О сортировке (Ужасы Программирования - 2)

В комментариях к предыдущей записи Влад Шабанов предложил, казалось бы, отличное решение для лексикографической сортировки 8-байтных ключей (функция сравнения для qsort()):

  1. #include "sys/endian.h";
  2. int cmp(const void *d1,const void *d2)
  3. {
  4.     uint64_t a1 = be64toh(* ((uint64_t *)d1));
  5.     uint64_t a2 = be64toh(* ((uint64_t *)d2));
  6.     return (a1  > a2) - (a1 < a2);
  7. }

Однако результат бенчмарка оказался неожиданным: мое наивное и тупое в лоб решение оказалось более чем в полтора раза быстрее, чем предложенная компактная красота.

Будучи зацеплен словами про 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.

Ага, невообразимо круто. Интересно, какое у них распределе

Ага, невообразимо круто.

Интересно, какое у них распределение ключей было. Если равномерное, то жизнь сильно легче.

Дрочим всем офисом!

Дрочим всем офисом!

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <s> <i> <b> <blockquote>
  • Lines and paragraphs break automatically.
  • You can enable syntax highlighting of source code with the following tags: <code>, <blockcode>, <c>, <cpp>, <drupal5>, <drupal6>, <java>, <javascript>, <php>, <ruby>. The supported tag styles are: <foo>, [foo].
  • Images can be added to this post.

More information about formatting options



.