Цена абстракции
Андрей Аксёнов, http://sphinxsearch.com/
Москва, РИТ 2014
Disclaimer
• Как обычно, ничего интересного
• Фактически, сборник анекдотов
• Низкоуровневых, на C++
• И матерных ещё
• Если вы senior PHP architect, 2 года опыта –
применить на практике будет сложно!!!
• Но, возможно, любопытно!!!
Disclaimer-2
• Циферки будут не везде
• Объяснения будут не везде
• Личный странный опыт => YMMV
• Пересказы чужих баек => YMMУаабщеV
• И клёвых картинок сегодня почти нет 
Про вектора
Нет препятствий патриотам!
• Про скорость, древняя-древняя история
• Раз, vector<int> & res
• Два, vector<int> g_res + reserve() +20%
• Три, int[12] + int * res + MAGIC_EOF +40%
• Про память, понятная беда
• 8, 16, 32, 64, …, 512K, 1M, 2M, 4M, …
• reserve() заранее – спасает, но жрет память
• Политики resize – tradeoff RAM/CPU
Внезапно, новая история
• Тест поиска, от 0 до 100K матчей
vector<matches> res;
• Uno, “совершенно очевидно, что…”
• static (1 раз на 1000 поисков) совсем хорошо
• res.reserve(100000); каждый раз тоже неплохо
• res.reserve(1000); даст 6 resize, ммм, так себе
• “просто” вектор совсем плохо
А как на самом деле?
• “Просто” вектор разумеется, тормозит
• static vector<matches> res + reserve(100000),
разумеется, быстрее всего
• res.reserve(1000)… по скорости одинаков
• Скорее всего, кеш аллокатора
• Да пофигу, такое повторить несложно
Про хеши
Корабль в бутылке
Что такое хеш
• Задача, по ключу K найти значение V
• Влобное решение, перебор
• Быстрое решение, перебор / N
• Массив key2value[N]
• Хеш-функция F(K) = {0, …, N-1}
• V (ну или указатель) кладем в key2value[F(K)]
• Или в F(K)+1, +2, +3, …, или +1, +4, +9, +16…
• Fun fact, в этом вашем PHP массивов-то нет
Open addressing VS chaining
• Chaining
• Фиксированные N слотов, список значений
• Можно MTF
• Open addressing
• Динамические M слотов, в них само значение
• Linear, quadratic итп probing
• Duo, “совершенно очевидно, что…”
• Chaining = аллоки, и там и там = кеш миссы
А как на самом деле?
• Тестовое приложение, FT индексатор
• Перекошенный (!!!) доступ к ключевикам
• Staticsize, OA/LP, LF ~0.6 100%
• “Медленный” dynamic resize 100%
• “Медленный” load factor 0.8 100%
• “Медленный” load factor 0.95 100%
• “Медленный” chaining, 500 Kalloc 70-90%
Чуть подробнее про chaining
• 264K значений, 19075K лукапов, Zipf
• Эталон, static open addr 108 M/s, 100%
• Chaining, 16K слотов 75 M/s, 70%
• Chaining, 16K слотов, MTF 81 M/s, 75%
• Chaining, 256K слотов, MTF 95 M/s, 88%
• Такое вот “медленно”
Про быстрые функции
• CRC32 ведь вообще не хеш-функция!!!
• 1961/1975, детектирование ошибок
• FNV, Jenkins Lookup3, MurMur, CityHash,
SuperFastHash, shift-add-xor, shift-mul-xor, …
• Tre, “совершенно очевидно, что…”
• Новые функции клевее (см. тесты SMHasher)
• Новые функции быстрее
• CRC32 отстой и столетназад устарело
А как на самом деле?
• Напоминаю, 19075K лукапов
• Все функции... вредят, медленнее на 3-10%
• Кроме shift-mul-xor, вредит не сильно
• 106 mb/sec vs 108 mb/sec
• В принципе, понятно, почему
DWORD uHash = 0;
for ( int i=0; i<(iLen+3)>>2; i++ )
uHash ^=
((DWORD*)pData)[i] * 0x607cbb77UL;
Как же разогнать хеширование?!
• Удалось!
• Но не хеш-функцией!!!
• Quattro, “совершенно очевидно, что…”
• libc это быстро, L1 кеш память рулит
• Поэтому strlen это вполне ок (и обычно ок)
• А как на самом деле?
Как же разогнать хеширование?!
• Удалось!
• Но не хеш-функцией!!!
• Quattro, “совершенно очевидно, что…”
• libc это быстро, L1 кеш память рулит
• Поэтому strlen это вполне ок (и обычно ок)
• А как на самом деле?
• Одновременно считаем crc32() + strlen
• Оп, внезапно +10%, или 110 вместо 101 mb/sec
Про строчки
Пацаны к успеху шли
• Выпилили strlen(), получили успех!
• Через это захотелось выпилить strcmp()
if (entry->hash==hash && strcmp(…)==0) …
• Cinque, “совершенно очевидно, что…”
• Проверка типично для равных, коротких строк
• Побайтово ок, избавляемся от вызова = profit
• А как на самом деле?
• Тормозит!!!
И про успехи академических наук
• Guest story (c) Arseny Kapoulkine // pugiXML
• Задача, найти подстроку в подстроке
• Решение, Бойер-Мур, Кнут-Моррис-Пратт,
Ахо-Корасик, и еще 40+ вариантов
• Sei, “совершенно очевидно, что…”
• Тупой перебор заведомо некруто
• И вообще, Кнут уже дедушка, а мы черпаки
• Поэтому реализуем BM, KMP или что-там-ещё
А как на самом деле?
• Хрясь-раз, тупой перебор… SSE перебор
• Быстрее почти всегда, но не всегда
• Хрясь-два, обращаем внимание на частоты
• Ищем самый редкий символ на SSE
• В момент совпадения тупо strcmp()
• БЫСТРЕЕ ВООБЩЕ ВСЕГДА
• Кроме... ровно одного synthetic worst case
Про хардкод
Хардкод или таблицы?
• Задача, быстро “промотать” некие
ненужные данные
while ( p<pEnd && ( *p & 0x80 ) )
switch ( *p )
{
case 252: p += 2; break;
case 253: p += 3; break;
case 254: p += 4; break;
case 255: p += 2; break;
default: p += 1; break;
}
Хардкод или таблицы?
• Задача, быстро “промотать” некие
ненужные данные
• Sette, “совершенно очевидно, что…”
• while (…) p += skip_length_table[*p]
• switch это всякие JMP, а то целые CMP
• Таблица длин махонькая, отлично кешируется
• В других местах таблицы работают лучше
• И тут обязана быть лучше!
А как на самом деле?
• 1.8 msec switch
• 3.2 msec skip_table
• Объяснения нет, какие-нибудь r/w stalls
И на уровень выше
Случайная история про read()
• Otto, “совершенно очевидно, что…”
• read() это лишнее копирование, медленно
• mmap() почитай прямой доступ, быстро
• А как на самом деле?
Случайная история про read()
• Otto, “совершенно очевидно, что…”
• read() это лишнее копирование, медленно
• mmap() почитай прямой доступ, быстро
• А как на самом деле?
• Несколько лет, несколько попыток
• Ни разу не смогли измерить эффект, но вот!!!
• [the rarity]
• 10 msec, 1 msec, 0.1 msec
Случайная история про MySQL
• Как нам побыстрее сделать SELECT *?
• Nove, “совершенно очевидно, что…”
• MyISAM это быстро, линейный тупой файл
• InnoDB это медленно, транзакции и все дела
• По размеру файла и то сразу всё видать!
• А как на самом деле?
Случайная история про MySQL
• Как нам побыстрее сделать SELECT *?
• Nove, “совершенно очевидно, что…”
• MyISAM это быстро, линейный тупой файл
• InnoDB это медленно, транзакции и все дела
• По размеру файла и то сразу всё видать!
• А как на самом деле?
• 54 MB/sec SELECT title, data FROM myisam
• 192 MB/sec SELECT title, data FROM innodb
Итого
ИТОГО
• Производительность –
странная штука
ИТОГО
• Производительность – странная штука
• “Все знают, что это так” – не работает
• “Интуитивно понятно, что” – не работает
• “Они проделали бенчмарк” – не работает
• Ни на микроуровне, ни на макроуровне
Никому нельзя верить
Особенно самому себе
Bench, bench, bench!
Вопросы?
(Можно подумать, я уложусь в 40 мин.)
shodan@sphinxsearch.com

Цена абстракции, Андрей Аксёнов (Sphinx)

  • 1.
    Цена абстракции Андрей Аксёнов,http://sphinxsearch.com/ Москва, РИТ 2014
  • 2.
    Disclaimer • Как обычно,ничего интересного • Фактически, сборник анекдотов • Низкоуровневых, на C++ • И матерных ещё • Если вы senior PHP architect, 2 года опыта – применить на практике будет сложно!!! • Но, возможно, любопытно!!!
  • 3.
    Disclaimer-2 • Циферки будутне везде • Объяснения будут не везде • Личный странный опыт => YMMV • Пересказы чужих баек => YMMУаабщеV • И клёвых картинок сегодня почти нет 
  • 4.
  • 6.
    Нет препятствий патриотам! •Про скорость, древняя-древняя история • Раз, vector<int> & res • Два, vector<int> g_res + reserve() +20% • Три, int[12] + int * res + MAGIC_EOF +40% • Про память, понятная беда • 8, 16, 32, 64, …, 512K, 1M, 2M, 4M, … • reserve() заранее – спасает, но жрет память • Политики resize – tradeoff RAM/CPU
  • 7.
    Внезапно, новая история •Тест поиска, от 0 до 100K матчей vector<matches> res; • Uno, “совершенно очевидно, что…” • static (1 раз на 1000 поисков) совсем хорошо • res.reserve(100000); каждый раз тоже неплохо • res.reserve(1000); даст 6 resize, ммм, так себе • “просто” вектор совсем плохо
  • 8.
    А как насамом деле? • “Просто” вектор разумеется, тормозит • static vector<matches> res + reserve(100000), разумеется, быстрее всего • res.reserve(1000)… по скорости одинаков • Скорее всего, кеш аллокатора • Да пофигу, такое повторить несложно
  • 9.
  • 10.
  • 11.
    Что такое хеш •Задача, по ключу K найти значение V • Влобное решение, перебор • Быстрое решение, перебор / N • Массив key2value[N] • Хеш-функция F(K) = {0, …, N-1} • V (ну или указатель) кладем в key2value[F(K)] • Или в F(K)+1, +2, +3, …, или +1, +4, +9, +16… • Fun fact, в этом вашем PHP массивов-то нет
  • 12.
    Open addressing VSchaining • Chaining • Фиксированные N слотов, список значений • Можно MTF • Open addressing • Динамические M слотов, в них само значение • Linear, quadratic итп probing • Duo, “совершенно очевидно, что…” • Chaining = аллоки, и там и там = кеш миссы
  • 14.
    А как насамом деле? • Тестовое приложение, FT индексатор • Перекошенный (!!!) доступ к ключевикам • Staticsize, OA/LP, LF ~0.6 100% • “Медленный” dynamic resize 100% • “Медленный” load factor 0.8 100% • “Медленный” load factor 0.95 100% • “Медленный” chaining, 500 Kalloc 70-90%
  • 15.
    Чуть подробнее проchaining • 264K значений, 19075K лукапов, Zipf • Эталон, static open addr 108 M/s, 100% • Chaining, 16K слотов 75 M/s, 70% • Chaining, 16K слотов, MTF 81 M/s, 75% • Chaining, 256K слотов, MTF 95 M/s, 88% • Такое вот “медленно”
  • 16.
    Про быстрые функции •CRC32 ведь вообще не хеш-функция!!! • 1961/1975, детектирование ошибок • FNV, Jenkins Lookup3, MurMur, CityHash, SuperFastHash, shift-add-xor, shift-mul-xor, … • Tre, “совершенно очевидно, что…” • Новые функции клевее (см. тесты SMHasher) • Новые функции быстрее • CRC32 отстой и столетназад устарело
  • 17.
    А как насамом деле? • Напоминаю, 19075K лукапов • Все функции... вредят, медленнее на 3-10% • Кроме shift-mul-xor, вредит не сильно • 106 mb/sec vs 108 mb/sec • В принципе, понятно, почему DWORD uHash = 0; for ( int i=0; i<(iLen+3)>>2; i++ ) uHash ^= ((DWORD*)pData)[i] * 0x607cbb77UL;
  • 18.
    Как же разогнатьхеширование?! • Удалось! • Но не хеш-функцией!!! • Quattro, “совершенно очевидно, что…” • libc это быстро, L1 кеш память рулит • Поэтому strlen это вполне ок (и обычно ок) • А как на самом деле?
  • 19.
    Как же разогнатьхеширование?! • Удалось! • Но не хеш-функцией!!! • Quattro, “совершенно очевидно, что…” • libc это быстро, L1 кеш память рулит • Поэтому strlen это вполне ок (и обычно ок) • А как на самом деле? • Одновременно считаем crc32() + strlen • Оп, внезапно +10%, или 110 вместо 101 mb/sec
  • 20.
  • 21.
    Пацаны к успехушли • Выпилили strlen(), получили успех! • Через это захотелось выпилить strcmp() if (entry->hash==hash && strcmp(…)==0) … • Cinque, “совершенно очевидно, что…” • Проверка типично для равных, коротких строк • Побайтово ок, избавляемся от вызова = profit • А как на самом деле? • Тормозит!!!
  • 22.
    И про успехиакадемических наук • Guest story (c) Arseny Kapoulkine // pugiXML • Задача, найти подстроку в подстроке • Решение, Бойер-Мур, Кнут-Моррис-Пратт, Ахо-Корасик, и еще 40+ вариантов • Sei, “совершенно очевидно, что…” • Тупой перебор заведомо некруто • И вообще, Кнут уже дедушка, а мы черпаки • Поэтому реализуем BM, KMP или что-там-ещё
  • 23.
    А как насамом деле? • Хрясь-раз, тупой перебор… SSE перебор • Быстрее почти всегда, но не всегда • Хрясь-два, обращаем внимание на частоты • Ищем самый редкий символ на SSE • В момент совпадения тупо strcmp() • БЫСТРЕЕ ВООБЩЕ ВСЕГДА • Кроме... ровно одного synthetic worst case
  • 24.
  • 25.
    Хардкод или таблицы? •Задача, быстро “промотать” некие ненужные данные while ( p<pEnd && ( *p & 0x80 ) ) switch ( *p ) { case 252: p += 2; break; case 253: p += 3; break; case 254: p += 4; break; case 255: p += 2; break; default: p += 1; break; }
  • 26.
    Хардкод или таблицы? •Задача, быстро “промотать” некие ненужные данные • Sette, “совершенно очевидно, что…” • while (…) p += skip_length_table[*p] • switch это всякие JMP, а то целые CMP • Таблица длин махонькая, отлично кешируется • В других местах таблицы работают лучше • И тут обязана быть лучше!
  • 27.
    А как насамом деле? • 1.8 msec switch • 3.2 msec skip_table • Объяснения нет, какие-нибудь r/w stalls
  • 28.
  • 30.
    Случайная история проread() • Otto, “совершенно очевидно, что…” • read() это лишнее копирование, медленно • mmap() почитай прямой доступ, быстро • А как на самом деле?
  • 31.
    Случайная история проread() • Otto, “совершенно очевидно, что…” • read() это лишнее копирование, медленно • mmap() почитай прямой доступ, быстро • А как на самом деле? • Несколько лет, несколько попыток • Ни разу не смогли измерить эффект, но вот!!! • [the rarity] • 10 msec, 1 msec, 0.1 msec
  • 32.
    Случайная история проMySQL • Как нам побыстрее сделать SELECT *? • Nove, “совершенно очевидно, что…” • MyISAM это быстро, линейный тупой файл • InnoDB это медленно, транзакции и все дела • По размеру файла и то сразу всё видать! • А как на самом деле?
  • 33.
    Случайная история проMySQL • Как нам побыстрее сделать SELECT *? • Nove, “совершенно очевидно, что…” • MyISAM это быстро, линейный тупой файл • InnoDB это медленно, транзакции и все дела • По размеру файла и то сразу всё видать! • А как на самом деле? • 54 MB/sec SELECT title, data FROM myisam • 192 MB/sec SELECT title, data FROM innodb
  • 34.
  • 35.
  • 36.
    ИТОГО • Производительность –странная штука • “Все знают, что это так” – не работает • “Интуитивно понятно, что” – не работает • “Они проделали бенчмарк” – не работает • Ни на микроуровне, ни на макроуровне
  • 38.
  • 39.
  • 41.
  • 42.
    Вопросы? (Можно подумать, яуложусь в 40 мин.) shodan@sphinxsearch.com