ARM, который мы заслужили
Когда люди только в последнее десятилетие смотрели на SIMD x86_64, параллельно развивался AArch64, на который очень мало обращали внимания. Сейчас обращают больше и хочется рассказать одну историю, о которой редко говорят. И вообще, про ARM никто не пишет, надо исправляться!
У AArch64 намного более серьёзный instruction set, когда дело доходит до conditional moves. В x86_64 есть
И в целом всё, есть только cmov. А вот в AArch64 аналогом является
oper может быть равно eq, ne и так далее. То есть csel if equal, csel if not equal и так далее.
Но помимо conditional select есть ещё многие другие, такие как conditional select increase/inverse/negate:
Но по мне самая недооцененная иструкция это conditional compare, дада, выглядит оно так
То есть можно манипулировать флагами сравнений после cmp через и таким образом проносить флаги дальше. Скажем, чтобы сравнить 16 байт между собой можно сделать
Такой трюк можно использовать, чтобы проносить результаты сравнений. Учитывая то, что ccmp занимает 1 цикл на Neoverse-n1 как и cmp, то это помогает сравнивать регионы памяти с тем же ptest/movmask на x86.
Вообще одно из самых сложных отличий SIMD x86 и ARM Neon заключается в movemask инструкции. Если коротко, для 16 байт оно забирает верхний бит каждого байта (если суффикс b) и обычно используется так
На Arm такой инструкции нет и эмулируется 3-4 инструкциями. Это огромная боль, когда ты хочешь мигрировать одно на другое, теряешь много перфа.
В итоге это один трюк как на AArch64 за примерно такое же количество циклов сравнивать регионы памяти.
Можете оценить сколько movemask используется в ClickHouse [2] -- дада, я сделал огромный issue, чтобы наконец-то починить все перформанс проблемы на AArch64.
В след раз расскажу ещё пару трюков, так как через movemask можно ещё находить первый несовпавший бит в коде выше через всякие count trailing zeros, и текущий трюк уже не работает.
Полезные ссылки.
[1] Этот трюк используется в memcmp в glibc
[2] Можете оценить сколько movemask используется в ClickHouse
[3] Оптимизации через csinc в Google Snappy
[4] Neoverse-N1 optimization guide
[5] Movemask на x86
[6] Как я чинил movemask на AArch64 в Vectorscan, который выдавал некорректные результаты
Когда люди только в последнее десятилетие смотрели на SIMD x86_64, параллельно развивался AArch64, на который очень мало обращали внимания. Сейчас обращают больше и хочется рассказать одну историю, о которой редко говорят. И вообще, про ARM никто не пишет, надо исправляться!
У AArch64 намного более серьёзный instruction set, когда дело доходит до conditional moves. В x86_64 есть
cmov
, когда вы двигаете регистр в другой в зависимости от результатов предущего сравненияcmp %rax, %rcx
cmovzq $REG1, $REG2 # move reg1 into reg2 if flag ZF was set
И в целом всё, есть только cmov. А вот в AArch64 аналогом является
csel
, conditional select.cmp $something1, $something2
csel $reg1, $reg2, $reg3, oper # reg1 = oper ? reg2 : reg3
oper может быть равно eq, ne и так далее. То есть csel if equal, csel if not equal и так далее.
Но помимо conditional select есть ещё многие другие, такие как conditional select increase/inverse/negate:
csinc $reg1, $reg2, $reg3, oper # reg1 = oper ? reg2 : (reg3 + 1)
csinv $reg1, $reg2, $reg3, oper # reg1 = oper ? reg2 : ~reg3
csneg $reg1, $reg2, $reg3, oper # reg1 = oper ? reg2 : -reg3
Но по мне самая недооцененная иструкция это conditional compare, дада, выглядит оно так
cmp $something1, $something2
ccmp $reg1, $reg2, $flags, oper # flags = oper ? (cmp $reg1, $reg) : $flags
То есть можно манипулировать флагами сравнений после cmp через и таким образом проносить флаги дальше. Скажем, чтобы сравнить 16 байт между собой можно сделать
ldp x3, x4, [src1] # load 8 byte into x3, load 8 byte into x4
ldp x5, x6, [src2] # load 8 byte into x5, load 8 byte into x6
cmp x3, x5 # compare x3 and x5
ccmp x4, x6, 0, eq # if eq, compare x4, x6, otherwise flags are 0
b.ne L_SOMEWHERE # if flags are zero, branch
Такой трюк можно использовать, чтобы проносить результаты сравнений. Учитывая то, что ccmp занимает 1 цикл на Neoverse-n1 как и cmp, то это помогает сравнивать регионы памяти с тем же ptest/movmask на x86.
Вообще одно из самых сложных отличий SIMD x86 и ARM Neon заключается в movemask инструкции. Если коротко, для 16 байт оно забирает верхний бит каждого байта (если суффикс b) и обычно используется так
pcmpeqb %xmm1, %xmm2 # compare 2 regions, those who are equal, set to 0xff, otherwise 0
pmovmskb %xmm2, %ecx # move high bits, those who are equal, set to 1
cmp %ecx, 0xffff # compare
jne L_SOMEWHERE # jump
На Arm такой инструкции нет и эмулируется 3-4 инструкциями. Это огромная боль, когда ты хочешь мигрировать одно на другое, теряешь много перфа.
В итоге это один трюк как на AArch64 за примерно такое же количество циклов сравнивать регионы памяти.
Можете оценить сколько movemask используется в ClickHouse [2] -- дада, я сделал огромный issue, чтобы наконец-то починить все перформанс проблемы на AArch64.
В след раз расскажу ещё пару трюков, так как через movemask можно ещё находить первый несовпавший бит в коде выше через всякие count trailing zeros, и текущий трюк уже не работает.
Полезные ссылки.
[1] Этот трюк используется в memcmp в glibc
[2] Можете оценить сколько movemask используется в ClickHouse
[3] Оптимизации через csinc в Google Snappy
[4] Neoverse-N1 optimization guide
[5] Movemask на x86
[6] Как я чинил movemask на AArch64 в Vectorscan, который выдавал некорректные результаты
GitHub
[Umbrella] Make ClickHouse great on AArch64 · Issue #37005 · ClickHouse/ClickHouse
Objective Improve performance of ClickHouse for AArch64 and share the knowledge on how to do that. Plan This is list of what can be done to improve performance for AArch64. Contrib/third_party libr...
Пост, на этот раз про распределённые системы. Я не хочу делать вид, что занимаюсь только низкоуровневыми вещами, тем более на работе я занимаюсь распределёнными системами full time, а вот это всё мои хобби сверху (почти :-)).
В MapReduce/Spark часто возникают проблемы stragglers. Когда какая-то машина тупит, и дальше становится непонятно, что происходит:
1. Попался кусок данных, который невозможно долго процессится? Банальный пример: обрабатываете youtube id и встретилось 10 часовое видео
2. Это Shuffle/Reduce операция, где есть так называемые hot keys? То есть есть ключ, у которого большая порция всего, когда вы делаете Group By операции?
3. А может быть просто машина тупит?
4. А может быть данные перекошены и хоть и нет никакой записи, которая долго выполняется, а шард долго работает?
В общем, практика показывает, что самые частые проблемы возникают в пунктах 2 и 3. Пункт 2 можно детектить алгоритмически, а вот понять пункт 3 уже становится очень сложно, потому что неясно, имеет ли это что-то общее с п 1 или 4.
Придумали решение с так называемыми backups, или по-другому их называют, speculative execution: давайте если какой-то кусок работы тупит, мы сделаем копию и запроцессим два одинаковых куска данных, посмотрим, кто первый закончит.
Обычно это помогает с п.3 и достаточно хорошо работает.
Какие бывают стратегии?
Quantile backup или End-game-backup
Дана квантиль X, мы сделаем две реплики всех после процессинга всех X шардов. Скажем, если X=0.75 (дефолт у Spark), то после 75% всех выполненных шардов, мы оставшихся 25% всех самых медленных задуплицируем.
Skewed backup
Дано число X > 1, мы запускаем backup у тех, кто работает в X раз дольше медианного времени. Дефолт у Spark 1.5.
Min threshold
Чтобы применить что-то выше, нужно подождать ещё какое-то время и убедиться, что действительно надо запускать. Скажем, одну минуту подождать, чтобы запустить skewed backup или end-game backup. В spark этого нет вообще.
Тут вот LinkedIn в своём докладе про Speculative Execution в Spark написал, что правильная policy включения backups принесла им 13-40% меньше времени выполнения и даже сэкономила ресурсы. То, что они поняли:
* Дефолтные значения агрессивны. End-game backup решили включать на 90%, а не на 75% (если честно, моя интуиция тоже так говорит)
* Skewed backup. X = 1.5 тоже было агрессивным. Они поставили 4. По мне многовато, мой опыт говорит в районе 2.5-3 обычно бывает оптимально. Зависит от пайплайна и достаточно легко выучивается по истории.
* Min threshold важен. Мелкие джобы слишком много плодили различных workers и кластер превращался в мусор. Заимплементировали в spark, чтобы подождать 30 секунд для включения skewed/end-game backups
* Сильно улучшили variance, spark/mapreduce всегда страдал от погоды на кластере, тут получше получается, понятно почему, утилизируется кластер намного лучше.
* Даже RAM+CPU уменьшилось (хотя казалось бы, мы включаем доп работу), скорее всего из-за простоя
В общем, backup execution, hedged requests, также как и power of two choices балансировка сильно экономит вам перформанса, если правильно сварить. По мне, некоторые вещи можно выучивать исторически, смотреть как кластер себя ведёт и тд, но то, как обучать перформанс повторяющихся джобов, мы поговорим отдельно.
Распределенные системы такие, да. Speculative execution очень приятно писать в коде, кстати, по себе знаю :) И очень тяжело дебагать, если что-то пошло не так (true story, зафакапили десяток пайплайнов).
В MapReduce/Spark часто возникают проблемы stragglers. Когда какая-то машина тупит, и дальше становится непонятно, что происходит:
1. Попался кусок данных, который невозможно долго процессится? Банальный пример: обрабатываете youtube id и встретилось 10 часовое видео
2. Это Shuffle/Reduce операция, где есть так называемые hot keys? То есть есть ключ, у которого большая порция всего, когда вы делаете Group By операции?
3. А может быть просто машина тупит?
4. А может быть данные перекошены и хоть и нет никакой записи, которая долго выполняется, а шард долго работает?
В общем, практика показывает, что самые частые проблемы возникают в пунктах 2 и 3. Пункт 2 можно детектить алгоритмически, а вот понять пункт 3 уже становится очень сложно, потому что неясно, имеет ли это что-то общее с п 1 или 4.
Придумали решение с так называемыми backups, или по-другому их называют, speculative execution: давайте если какой-то кусок работы тупит, мы сделаем копию и запроцессим два одинаковых куска данных, посмотрим, кто первый закончит.
Обычно это помогает с п.3 и достаточно хорошо работает.
Какие бывают стратегии?
Quantile backup или End-game-backup
Дана квантиль X, мы сделаем две реплики всех после процессинга всех X шардов. Скажем, если X=0.75 (дефолт у Spark), то после 75% всех выполненных шардов, мы оставшихся 25% всех самых медленных задуплицируем.
Skewed backup
Дано число X > 1, мы запускаем backup у тех, кто работает в X раз дольше медианного времени. Дефолт у Spark 1.5.
Min threshold
Чтобы применить что-то выше, нужно подождать ещё какое-то время и убедиться, что действительно надо запускать. Скажем, одну минуту подождать, чтобы запустить skewed backup или end-game backup. В spark этого нет вообще.
Тут вот LinkedIn в своём докладе про Speculative Execution в Spark написал, что правильная policy включения backups принесла им 13-40% меньше времени выполнения и даже сэкономила ресурсы. То, что они поняли:
* Дефолтные значения агрессивны. End-game backup решили включать на 90%, а не на 75% (если честно, моя интуиция тоже так говорит)
* Skewed backup. X = 1.5 тоже было агрессивным. Они поставили 4. По мне многовато, мой опыт говорит в районе 2.5-3 обычно бывает оптимально. Зависит от пайплайна и достаточно легко выучивается по истории.
* Min threshold важен. Мелкие джобы слишком много плодили различных workers и кластер превращался в мусор. Заимплементировали в spark, чтобы подождать 30 секунд для включения skewed/end-game backups
* Сильно улучшили variance, spark/mapreduce всегда страдал от погоды на кластере, тут получше получается, понятно почему, утилизируется кластер намного лучше.
* Даже RAM+CPU уменьшилось (хотя казалось бы, мы включаем доп работу), скорее всего из-за простоя
В общем, backup execution, hedged requests, также как и power of two choices балансировка сильно экономит вам перформанса, если правильно сварить. По мне, некоторые вещи можно выучивать исторически, смотреть как кластер себя ведёт и тд, но то, как обучать перформанс повторяющихся джобов, мы поговорим отдельно.
Распределенные системы такие, да. Speculative execution очень приятно писать в коде, кстати, по себе знаю :) И очень тяжело дебагать, если что-то пошло не так (true story, зафакапили десяток пайплайнов).
YouTube
Databricks
Databricks is the Data and AI company. More than 10,000 organizations worldwide — including Block, Comcast, Conde Nast, Rivian, and Shell, and over 60% of the Fortune 500 — rely on the Databricks Data Intelligence Platform to take control of their data and…
Testing on the Toilet
До года так 2005 в Google не было принято писать тесты. Компания переживала бурный рост, а хоть туда уже приходили лучшие инженеры, на тесты как-то время не хватало. Некоторые из разработчиков были недовольны таким положением вещей, и родилась какая-то абсолютно гениальная идея, с которой все единогласно согласились, и надо было лишь правильно ее исполнить.
Testing on the Toilet (TotT) -- одностраничные листовки, расклеенные в туалетных кабинках офисов Google и дающие разработчикам советы о том, как лучше тестировать их код, -- это наш, можно сказать, институт, на котором держится Google. Они упоминались в Washington Post, New York Times и единогласно были высмеяны и признаны воплощением культуры Гугла.
Как и почти все вещи в Google, TotT возник в результате попытки решить одну конкретную проблему. Код стало невозможно писать из-за слишком большого количества багов. В середине 2005 года была создана группа Unittest, чтобы обучать разработчиков, как тестировать свой код. В то время написание тестов не было прям уж нормой в Google. Члены группы начали писать Codelabs (лабораторные пошаговые работы), организовывать Fixit weeks (когда все в команде чинят flaky тесты) и проводить еженедельные лекции для Noogler (New Googler) о том, как важно писать тесты.
Как я бы сказал по-английски: "This trained the Nooglers". А что делать с теми, кого уже наняли? Во время митинга в конце марта 2006 г. один из директоров предложил идею о расклеивании листовок в публичных местах. А куда все ходят точно хотя бы раз в день? Столовые и туалеты. Кафе не были хорошим решением, так как фокус всегда был смещен на еду и общение. Остаются уборные. Что ж. Кто-то посмеялся, но никто не задал вопроса, а нужно ли это делать. В итоге это вошло в OKR, и листовки стали появляться во всех туалетах офисов Google.
Один из инженеров написал первый эпизод: "Better stubbing in Python". Его наклеили везде в офисах Долины и Лондона. Кто-то подхватил и рассказал об этом всем сокомандникам, пошло сарафанное радио. Это было необычно, и все согласились, что это может решить их проблему. TotT распространялся с немыслимой быстротой.
Ana Ulin стала лидером этой программы, когда она добровольна взяла ответственность за вычитку и качество материала. Так идея была подхвачена, на неё нашлись правильные и нужные люди. Это ещё одна часть культуры Google -- если ты что-то делаешь и делаешь это хорошо, ты теперь за это ответственен. Те, кто обожал тестирование, писали свои методы как писать правильные тесты на С++, как работать с Unicode. Абсолютно все офисы стали подхватывать это, и даже в планировки новой постройки стали рисовать места, где будут наклеивать эти листовки.
Со временем многие авторы начали писать свой контент (в том числе появились разделения на Tips of the Week, которые появлялись в блогах), и люди прислушивались к советам. Начались обсуждения, бесконечные дебаты, появились арбитры, но самое главное -- были сообщения о положительных результатах, что теперь кто-то научился правильно писать на Python или смог обойти баг с памятью в C++. Рекламировались и внедрялись новые инструменты и методы. Качество, скейл рос. Люди стали цитировать в код ревью эпизоды, люди доверяли этому источнику. Пусть решения не были идеальными, но все смогли о чём-то договориться.
TotT явно оказал сильное положительное влияние на инженерные практики. Ничего так не сработало как этот изначальный толчок -- необычно, мило, воодушевляюще, а главное, очень полезно.
Несмотря на все изменения, уже сколько раз поменявшихся лидеров программы, TotT уже 17 лет выпускает эпизоды. Появились отдельный культы и программы, которые пишутся для всех технологий. К сожалению, всё не повесишь в туалетах, а некоторые стали невозможными для публичных глаз :)
Google совершенно случайно нашёл решение, как продвигать практики, писать лучше код. Кто бы мог подумать, что можно сказать, что какая-то часть культуры Google стала богаче и сильнее из-за каких-то там туалетов.
Больше и первые эпизоды здесь:
https://mike-bland.com/2011/10/25/testing-on-the-toilet.html
До года так 2005 в Google не было принято писать тесты. Компания переживала бурный рост, а хоть туда уже приходили лучшие инженеры, на тесты как-то время не хватало. Некоторые из разработчиков были недовольны таким положением вещей, и родилась какая-то абсолютно гениальная идея, с которой все единогласно согласились, и надо было лишь правильно ее исполнить.
Testing on the Toilet (TotT) -- одностраничные листовки, расклеенные в туалетных кабинках офисов Google и дающие разработчикам советы о том, как лучше тестировать их код, -- это наш, можно сказать, институт, на котором держится Google. Они упоминались в Washington Post, New York Times и единогласно были высмеяны и признаны воплощением культуры Гугла.
Как и почти все вещи в Google, TotT возник в результате попытки решить одну конкретную проблему. Код стало невозможно писать из-за слишком большого количества багов. В середине 2005 года была создана группа Unittest, чтобы обучать разработчиков, как тестировать свой код. В то время написание тестов не было прям уж нормой в Google. Члены группы начали писать Codelabs (лабораторные пошаговые работы), организовывать Fixit weeks (когда все в команде чинят flaky тесты) и проводить еженедельные лекции для Noogler (New Googler) о том, как важно писать тесты.
Как я бы сказал по-английски: "This trained the Nooglers". А что делать с теми, кого уже наняли? Во время митинга в конце марта 2006 г. один из директоров предложил идею о расклеивании листовок в публичных местах. А куда все ходят точно хотя бы раз в день? Столовые и туалеты. Кафе не были хорошим решением, так как фокус всегда был смещен на еду и общение. Остаются уборные. Что ж. Кто-то посмеялся, но никто не задал вопроса, а нужно ли это делать. В итоге это вошло в OKR, и листовки стали появляться во всех туалетах офисов Google.
Один из инженеров написал первый эпизод: "Better stubbing in Python". Его наклеили везде в офисах Долины и Лондона. Кто-то подхватил и рассказал об этом всем сокомандникам, пошло сарафанное радио. Это было необычно, и все согласились, что это может решить их проблему. TotT распространялся с немыслимой быстротой.
Ana Ulin стала лидером этой программы, когда она добровольна взяла ответственность за вычитку и качество материала. Так идея была подхвачена, на неё нашлись правильные и нужные люди. Это ещё одна часть культуры Google -- если ты что-то делаешь и делаешь это хорошо, ты теперь за это ответственен. Те, кто обожал тестирование, писали свои методы как писать правильные тесты на С++, как работать с Unicode. Абсолютно все офисы стали подхватывать это, и даже в планировки новой постройки стали рисовать места, где будут наклеивать эти листовки.
Со временем многие авторы начали писать свой контент (в том числе появились разделения на Tips of the Week, которые появлялись в блогах), и люди прислушивались к советам. Начались обсуждения, бесконечные дебаты, появились арбитры, но самое главное -- были сообщения о положительных результатах, что теперь кто-то научился правильно писать на Python или смог обойти баг с памятью в C++. Рекламировались и внедрялись новые инструменты и методы. Качество, скейл рос. Люди стали цитировать в код ревью эпизоды, люди доверяли этому источнику. Пусть решения не были идеальными, но все смогли о чём-то договориться.
TotT явно оказал сильное положительное влияние на инженерные практики. Ничего так не сработало как этот изначальный толчок -- необычно, мило, воодушевляюще, а главное, очень полезно.
Несмотря на все изменения, уже сколько раз поменявшихся лидеров программы, TotT уже 17 лет выпускает эпизоды. Появились отдельный культы и программы, которые пишутся для всех технологий. К сожалению, всё не повесишь в туалетах, а некоторые стали невозможными для публичных глаз :)
Google совершенно случайно нашёл решение, как продвигать практики, писать лучше код. Кто бы мог подумать, что можно сказать, что какая-то часть культуры Google стала богаче и сильнее из-за каких-то там туалетов.
Больше и первые эпизоды здесь:
https://mike-bland.com/2011/10/25/testing-on-the-toilet.html
Mike Bland
Testing on the Toilet - Mike Bland
The Testing Grouplet's weekly publication for spreading testing news and views throughout Google, in the most opportune of places
Что-то я ничего не писал почти 2 недели. А в целом потому что никаких красивых историй не происходило, не хотелось читать статьи, а работать надо было по 60-70+ часов в неделю. Будем менять, но историю по перформансу я вам привёз, конечно же.
В Google вместо LZ4 мы используем в большинстве мест Snappy, очень похожий на LZ4 кодек, тоже формат вида двух операций
1. Скопировать байты (литералы в терминах LZ4)
2. Скопировать по оффсету те байты, что уже были разжаты (матчи в терминах LZ4)
Отличия Snappy от LZ4 в том, что есть байты теги, мол, инструкции, что надо делать (копировать байты или копировать по оффсету), когда как в LZ4 они чередуются.
LZ4 доказал со временем, что у него скорость разжатия получше, но легаси, все дела, вряд ли мы избавимся от Snappy даже за десять лет. Используется везде правда.
И код понятное дело очень приложенный. Я комитил много туда оптимизаций, мы нашли удивительную ещё одну.
Когда мы копируем байты по оффсету (в этом месте мы знаем, что, копирование происходит меньше чем на 64 байта, так как размер в теге инструкции всегда запакован в 6 битах), то у нас написан такой код
memmove на 64 идёт, потому что это четыре 16 байтных load/store и не вызывается дополнительной функции. Так делать можно, потому что аллоцируют чуть больше, чтобы записать после конца.
На самом деле в идеале код мог бы выглядеть как
А если ещё подумать и почитать кода, то memmove можно заменить на memcpy. Для 64 версии используется memmove, так как указатели могут друг на друга накладываться с размером 64, но не с размером len.
Мы решили посмотреть, какое раcпределение этих len, оказалось, что почти все под 32, большинство под 16 (вы тоже можете из открытых бенчмарков). А мы аж целых 64 копируем для худшего случая.
То есть нам примерно дано:
Well, мы попробовали
Мы получили смешанные результаты
Skylake: +3% on average
Cascade Lake: +2% on average
AMD Zen 2: +20% on average
AMD Zen 3: +25% on average
Neoverse N1 (ARM): +14% on average
Neoverse N2 (ARM): +11% on average
И дальше возникает интересный вопрос, что ж случилось. Я перепроверил и оказалось, что действительно AMD новых поколений разжимают медленнее, чем Intel старых, но сжимают зато быстрее и в целом считаются CPU так процентов на 20 побыстрее.
Каким-то чудом Intel научился понимать, что записи по кешлиниям с байта 32 по 64 очень редко или поздно используются, а вот AMD и ARMы так не научились.
Конечно же, никто нам не расскажет, что произошло. Но это самый большой гэп между Intel и AMD, который я когда либо видел. Увидеть 25% прирост на одном и только 2% на другом это прям удивляет.
И всё ещё удивительно, что тут есть оптимизации. На скейле это очень много денег.
Чем я ещё занимался?
ZSTD соптимизировал для ARMов (в том числе ваших макбуках) на 5%. Готовится огромная история, что произошло, потому что я выбил лучше перф процентов на 20 на Apple M1 по таким функциям как memcmp и так далее. Выбил +5% перфа для хэш таблиц Google Abseil. И кажется ещё дойду до SIMDJSON. Однозначный вывод сделал, ни в Apple, ни в ARM не сидят инженеры, которые понимают за software performance своей архитектуры. Либо я открыл какой-то ящик пандоры. Пока я валидирую у своих коллег, что я не сошёл с ума, пишу уже неделю блог. Какой-то sneak peek вариант смотрите в ZSTD. Да, снова ассемблер армов, потому что это красиво, мать вашу. Я нанял себе аниматора, потому что понял, что про SIMD надо рассказывать с картинками.
В Google вместо LZ4 мы используем в большинстве мест Snappy, очень похожий на LZ4 кодек, тоже формат вида двух операций
1. Скопировать байты (литералы в терминах LZ4)
2. Скопировать по оффсету те байты, что уже были разжаты (матчи в терминах LZ4)
Отличия Snappy от LZ4 в том, что есть байты теги, мол, инструкции, что надо делать (копировать байты или копировать по оффсету), когда как в LZ4 они чередуются.
LZ4 доказал со временем, что у него скорость разжатия получше, но легаси, все дела, вряд ли мы избавимся от Snappy даже за десять лет. Используется везде правда.
И код понятное дело очень приложенный. Я комитил много туда оптимизаций, мы нашли удивительную ещё одну.
Когда мы копируем байты по оффсету (в этом месте мы знаем, что, копирование происходит меньше чем на 64 байта, так как размер в теге инструкции всегда запакован в 6 битах), то у нас написан такой код
const void* from =
tag_type ? reinterpret_cast<void*>(op_base + delta) : old_ip;
memmove(op_base + op, from, 64);
ip += len;
memmove на 64 идёт, потому что это четыре 16 байтных load/store и не вызывается дополнительной функции. Так делать можно, потому что аллоцируют чуть больше, чтобы записать после конца.
На самом деле в идеале код мог бы выглядеть как
memmove(op_base + op, from, len);
А если ещё подумать и почитать кода, то memmove можно заменить на memcpy. Для 64 версии используется memmove, так как указатели могут друг на друга накладываться с размером 64, но не с размером len.
Мы решили посмотреть, какое раcпределение этих len, оказалось, что почти все под 32, большинство под 16 (вы тоже можете из открытых бенчмарков). А мы аж целых 64 копируем для худшего случая.
То есть нам примерно дано:
memcpy(dst, src, len);
len <= 64
len <= 32 в 99% случаях, len <= 16 в 95% случаях
[dst, dst + len) и [src, src + len) непересекающиеся, но если + 64, то могут
dst+64 валидный регион памяти
Well, мы попробовали
// Если len <= 32, это правильно.
memmove(dst, src, 32);
// Если len > 32, мы не перезаписали байты так как [dst, dst + 32) и [src, src + 32) не пересекаются.
if (UNLIKELY(len > 32)) {
memmove(dst + 32, static_cast<const uint8_t*>(src) + 32, 32);
}
Мы получили смешанные результаты
Skylake: +3% on average
Cascade Lake: +2% on average
AMD Zen 2: +20% on average
AMD Zen 3: +25% on average
Neoverse N1 (ARM): +14% on average
Neoverse N2 (ARM): +11% on average
И дальше возникает интересный вопрос, что ж случилось. Я перепроверил и оказалось, что действительно AMD новых поколений разжимают медленнее, чем Intel старых, но сжимают зато быстрее и в целом считаются CPU так процентов на 20 побыстрее.
Каким-то чудом Intel научился понимать, что записи по кешлиниям с байта 32 по 64 очень редко или поздно используются, а вот AMD и ARMы так не научились.
Конечно же, никто нам не расскажет, что произошло. Но это самый большой гэп между Intel и AMD, который я когда либо видел. Увидеть 25% прирост на одном и только 2% на другом это прям удивляет.
И всё ещё удивительно, что тут есть оптимизации. На скейле это очень много денег.
Чем я ещё занимался?
ZSTD соптимизировал для ARMов (в том числе ваших макбуках) на 5%. Готовится огромная история, что произошло, потому что я выбил лучше перф процентов на 20 на Apple M1 по таким функциям как memcmp и так далее. Выбил +5% перфа для хэш таблиц Google Abseil. И кажется ещё дойду до SIMDJSON. Однозначный вывод сделал, ни в Apple, ни в ARM не сидят инженеры, которые понимают за software performance своей архитектуры. Либо я открыл какой-то ящик пандоры. Пока я валидирую у своих коллег, что я не сошёл с ума, пишу уже неделю блог. Какой-то sneak peek вариант смотрите в ZSTD. Да, снова ассемблер армов, потому что это красиво, мать вашу. Я нанял себе аниматора, потому что понял, что про SIMD надо рассказывать с картинками.
GitHub
GitHub - google/snappy: A fast compressor/decompressor
A fast compressor/decompressor. Contribute to google/snappy development by creating an account on GitHub.
Мы делаем llvm::libc, и тут есть забавная история. Достаточно понятно, что в libc самые используемые функции на проде это memcpy, memcmp, memmove, strlen и тд, те, которые работают с памятью, так как они встречаются в векторах/строках и тд. Я сотню раз видел посты, где люди ускоряли эти функции для больших значений, мол, смотрите, мой memcmp работает быстрее на сотню мегабайт в секунду или поиск символа на пару десятков.
К сожалению, в реальности никто не сравнивает большие отрезки массивов, так редко бывает, соответственно ускорения от таких постов хайп, нежели реально помогает (в чем нет ничего плохого, идеи бывают хорошие).
Мы решили опубликовать распределения разных workload'ов у таких функций. Посмотреть их можно здесь. Но если проанализировать в среднем, до 128 байт где-то 99% вызовов, остальное очень редкое. Учитывая это, для оптимального перформанса таких функций, лучше уметь в Profile Guided Optimization, который понимает, что можно во многих местах кода инлайнить маленькие отрезки. Отчасти поэтому мы в Google и решили делать libc со статической линковкой, потому что динамика не позволяет проворачивать пост компиляторные оптимизации. А все SIMD развороты циклов намного меньше решают.
Такой эффект в перформансе мы называем оверфитом, скажем, вы пишете две реализации, одна берет значения из таблички, другая нет, из таблички на бенчмарках будет работать быстрее из-за кешей. А далее важно себе ответить на вопрос -- будет ли этот кеш постоянным
1. Если у вас поисковый шард -- это правда, кеш надо прогреть
2. Если у вас табличка для конвертации 2 байт в десятичное представление, ваша программа вряд ли только и конвертирует. Тру стори, ругался очень долго, что это плохо, но забил, так как там маленькая opportunity.
И в целом мой совет -- если вы видите какой-то бенчмарк, который зависит от данных, и ну уж очень важен, или вы хотите продемонстрировать, что что-то ускорили, имейте в виду и проверяйте несколько вещей:
1. Всякие табличные значения очень хорошо идут в кеш
2. Branch predictor очень хорошо обучается, какое распределение данных вы даёте функции/программе? Можете ли вы дать лучше/ближе к реальности?
Пример: поисковый шард, в запросах всегда есть тренды (иногда мимолётные, скажем, какое-то событие произошло), и вы никогда не знаете, может быть в этом шарде было настолько важные для тренда запросов документы, что запросы на другом шарде дают другой перф?
К сожалению, синтетические бенчмарки шардов никогда не давали точных ускорений, всегда надо было смотреть уже в проде. К счастью, разница была всегда небольшой. Пару раз ловили оверфит, когда оптимизации были именно для кеша шарда. После этого мы начали в поиске думать о том, что в синтетике мы никогда "не прогреем данные так как в проде".
Но это дало понимание, что никогда, ни в коем случае не делать региональные/факторные распределения документов по шардам в больших системах. Эта штука закончится неоднородными решениями в том числе из-за перфа. Региональные кеши -- ок, региональные last resort базы -- нет, спасибо. Как можно равномернее распределять лучше всего.
Другой пример:
В SimdJson очень много используются таблички. В бенчмарках они очень прогретые в L1 кешах. Интересно сколько перфа теряется, когда эти таблицы привносят кеш миссы. Это прекрасная идея для поста, чтобы так поубавить градус фразы "Парсим X гигабайт в секунду". Я ставлю минимум 5-10% теряется, что в целом, не велика потеря, но не даёт полной картины реального мира.
Поэтому в оптимизациях я люблю branch free code, потому что о бранчах в горячих местах порой сложно спекулировать.
Ещё вот в наших гугловских хэштаблицах есть бенчмарки с суффиксом _Hot/_Cold. К сожалению, померить распределение прогретости достаточно тяжело, но это хотя бы что-то, что помогает не оверфититься по кешам.
Как вариант посмотреть для SimdJson -- вытащить таблицы из глобальной памяти и поместить их в динамическую, сделать cold benchmark (создаются тысячи инстансов класса и десереализует случайный).
Мораль: не оверфитьте свои оптимизации под бенчмарк, это может плохо закончиться, думайте о данных тоже.
К сожалению, в реальности никто не сравнивает большие отрезки массивов, так редко бывает, соответственно ускорения от таких постов хайп, нежели реально помогает (в чем нет ничего плохого, идеи бывают хорошие).
Мы решили опубликовать распределения разных workload'ов у таких функций. Посмотреть их можно здесь. Но если проанализировать в среднем, до 128 байт где-то 99% вызовов, остальное очень редкое. Учитывая это, для оптимального перформанса таких функций, лучше уметь в Profile Guided Optimization, который понимает, что можно во многих местах кода инлайнить маленькие отрезки. Отчасти поэтому мы в Google и решили делать libc со статической линковкой, потому что динамика не позволяет проворачивать пост компиляторные оптимизации. А все SIMD развороты циклов намного меньше решают.
Такой эффект в перформансе мы называем оверфитом, скажем, вы пишете две реализации, одна берет значения из таблички, другая нет, из таблички на бенчмарках будет работать быстрее из-за кешей. А далее важно себе ответить на вопрос -- будет ли этот кеш постоянным
1. Если у вас поисковый шард -- это правда, кеш надо прогреть
2. Если у вас табличка для конвертации 2 байт в десятичное представление, ваша программа вряд ли только и конвертирует. Тру стори, ругался очень долго, что это плохо, но забил, так как там маленькая opportunity.
И в целом мой совет -- если вы видите какой-то бенчмарк, который зависит от данных, и ну уж очень важен, или вы хотите продемонстрировать, что что-то ускорили, имейте в виду и проверяйте несколько вещей:
1. Всякие табличные значения очень хорошо идут в кеш
2. Branch predictor очень хорошо обучается, какое распределение данных вы даёте функции/программе? Можете ли вы дать лучше/ближе к реальности?
Пример: поисковый шард, в запросах всегда есть тренды (иногда мимолётные, скажем, какое-то событие произошло), и вы никогда не знаете, может быть в этом шарде было настолько важные для тренда запросов документы, что запросы на другом шарде дают другой перф?
К сожалению, синтетические бенчмарки шардов никогда не давали точных ускорений, всегда надо было смотреть уже в проде. К счастью, разница была всегда небольшой. Пару раз ловили оверфит, когда оптимизации были именно для кеша шарда. После этого мы начали в поиске думать о том, что в синтетике мы никогда "не прогреем данные так как в проде".
Но это дало понимание, что никогда, ни в коем случае не делать региональные/факторные распределения документов по шардам в больших системах. Эта штука закончится неоднородными решениями в том числе из-за перфа. Региональные кеши -- ок, региональные last resort базы -- нет, спасибо. Как можно равномернее распределять лучше всего.
Другой пример:
В SimdJson очень много используются таблички. В бенчмарках они очень прогретые в L1 кешах. Интересно сколько перфа теряется, когда эти таблицы привносят кеш миссы. Это прекрасная идея для поста, чтобы так поубавить градус фразы "Парсим X гигабайт в секунду". Я ставлю минимум 5-10% теряется, что в целом, не велика потеря, но не даёт полной картины реального мира.
Поэтому в оптимизациях я люблю branch free code, потому что о бранчах в горячих местах порой сложно спекулировать.
Ещё вот в наших гугловских хэштаблицах есть бенчмарки с суффиксом _Hot/_Cold. К сожалению, померить распределение прогретости достаточно тяжело, но это хотя бы что-то, что помогает не оверфититься по кешам.
Как вариант посмотреть для SimdJson -- вытащить таблицы из глобальной памяти и поместить их в динамическую, сделать cold benchmark (создаются тысячи инстансов класса и десереализует случайный).
Мораль: не оверфитьте свои оптимизации под бенчмарк, это может плохо закончиться, думайте о данных тоже.
1. Интересно почитать о том, насколько быстрой можно сделать коммуникацию по памяти между процессами в Linux
https://mazzo.li/posts/fast-pipes.html
Если коротко, то используется [vm]splice системные вызовы с большими страницами. Интересный факт заключается в том, что эти вызовы в какой-то степени "дарят" страницы другим процессам и тот, кто их создал, не имеет к ним доступа. Бывают полезные кейсы, несколько раз слышал трейдинг их использует, чтобы правильно отдавать память процессам для обработки. Но правильно их использовать замучиться надо ещё. Поэтому я чаще видел как делают shared memory, контролируя весь стек.
Интересный вопрос, а можно ли что-то придумать, чтобы эти страницы помечались copy on write. Мое знание Linux говорит, что да, такой флажок есть, наверное, что-то даже можно сварить из select+poll, но уже становится достаточно тяжело. Отличная идея для io_uring, как по мне :)
Тем не менее, написано понятным языком, и можно узнать много нового.
2. https://www.forrestthewoods.com/blog/benchmarking-malloc-with-doom3/
Проводят benchmark и гистограммы вызовов аллокатора памяти. Не приводят аргументов, но цифры очень похожи на правду. Медиана в 25 наносекунд, 99.9 где-то 20 микросекунд, а когда ядро решает потормозить, уйти в себя, все 500 микросекунд. Отлично почитать, чтобы понимать, насколько быстро современные аллокаторы отдают память.
3. https://arxiv.org/pdf/2205.05982.pdf
Мы тут выложили SIMD сортировку. Честно? Мне не понравилось, идея ок, сравнение и ускорения только для чисел. Невозможно задеплоить в прод адекватно, сортировка чисел не так много цпу занимает. В итоге ни в стандартную библиотеку не включить, ещё и требует зависимости большой SIMD библиотеки. Я не в восторге, так как результат никогда не пойдет в масштабный прод, но если хочется почитать и проникнуться очередными трюками SIMD, welcome.
Я начинаю как-то дифференциацировать оптимизации: либо в тексте должны быть хорошие идеи, которые чему-то учат, либо оптимизации должны уже кому-то помочь. Если первый факт слабый, а второй никому не помог, то я теряю интерес практически моментально.
4. Возможно в ближайшее время будут посты попроще или поменьше. Точно есть ещё на пару недель и один огромный пост, а дальше как-то список закончился. Я расту в Google, мне тяжело, я едва успеваю делать что-то вне работы, плюс ещё полно интересных вещей в обычной смертной жизни происходит. Никогда не думал, что это будет настолько тяжёлая проблема, скейл команды и продукта (а ещё и себя самого), мы растем по количеству пользователей, всем метрикам, а из-за этого количество всех corner case багов увеличивается, нагрузка на support увеличивается и просто уже не хватает ни времени, ни сил. В прошлый раз, когда я чувствовал себя так, я уходил из проекта. На этот раз хочется встретиться с этим и попытаться вытащить продукт на следующий уровень хотя бы 2х по всем параметрам. А 2х по меркам Google это, конечно, очень сложно, и, наверное, самое сложное это community и поддержка проекта, технически уж справимся. Новые испытания, uncomfortably exciting! :)
https://mazzo.li/posts/fast-pipes.html
Если коротко, то используется [vm]splice системные вызовы с большими страницами. Интересный факт заключается в том, что эти вызовы в какой-то степени "дарят" страницы другим процессам и тот, кто их создал, не имеет к ним доступа. Бывают полезные кейсы, несколько раз слышал трейдинг их использует, чтобы правильно отдавать память процессам для обработки. Но правильно их использовать замучиться надо ещё. Поэтому я чаще видел как делают shared memory, контролируя весь стек.
Интересный вопрос, а можно ли что-то придумать, чтобы эти страницы помечались copy on write. Мое знание Linux говорит, что да, такой флажок есть, наверное, что-то даже можно сварить из select+poll, но уже становится достаточно тяжело. Отличная идея для io_uring, как по мне :)
Тем не менее, написано понятным языком, и можно узнать много нового.
2. https://www.forrestthewoods.com/blog/benchmarking-malloc-with-doom3/
Проводят benchmark и гистограммы вызовов аллокатора памяти. Не приводят аргументов, но цифры очень похожи на правду. Медиана в 25 наносекунд, 99.9 где-то 20 микросекунд, а когда ядро решает потормозить, уйти в себя, все 500 микросекунд. Отлично почитать, чтобы понимать, насколько быстро современные аллокаторы отдают память.
3. https://arxiv.org/pdf/2205.05982.pdf
Мы тут выложили SIMD сортировку. Честно? Мне не понравилось, идея ок, сравнение и ускорения только для чисел. Невозможно задеплоить в прод адекватно, сортировка чисел не так много цпу занимает. В итоге ни в стандартную библиотеку не включить, ещё и требует зависимости большой SIMD библиотеки. Я не в восторге, так как результат никогда не пойдет в масштабный прод, но если хочется почитать и проникнуться очередными трюками SIMD, welcome.
Я начинаю как-то дифференциацировать оптимизации: либо в тексте должны быть хорошие идеи, которые чему-то учат, либо оптимизации должны уже кому-то помочь. Если первый факт слабый, а второй никому не помог, то я теряю интерес практически моментально.
4. Возможно в ближайшее время будут посты попроще или поменьше. Точно есть ещё на пару недель и один огромный пост, а дальше как-то список закончился. Я расту в Google, мне тяжело, я едва успеваю делать что-то вне работы, плюс ещё полно интересных вещей в обычной смертной жизни происходит. Никогда не думал, что это будет настолько тяжёлая проблема, скейл команды и продукта (а ещё и себя самого), мы растем по количеству пользователей, всем метрикам, а из-за этого количество всех corner case багов увеличивается, нагрузка на support увеличивается и просто уже не хватает ни времени, ни сил. В прошлый раз, когда я чувствовал себя так, я уходил из проекта. На этот раз хочется встретиться с этим и попытаться вытащить продукт на следующий уровень хотя бы 2х по всем параметрам. А 2х по меркам Google это, конечно, очень сложно, и, наверное, самое сложное это community и поддержка проекта, технически уж справимся. Новые испытания, uncomfortably exciting! :)
mazzo.li
How fast are Linux pipes anyway?
Pipes are ubiquitous in Unix --- but how fast can they go on Linux? In this post we'll iteratively improve a simple pipe-writing benchmark from 3.5GiB/s to 65GiB/s, guided by Linux `perf`.
Вообще иногда нахожу какие-то моменты в своей инженерной практике магическими. Я выпал из реальности на пару дней и совершенно не мог двигаться. Последний ~месяц ничего хорошего не происходило в моей жизни в работе.
Зато вчера я занёрдснайпил экспертов ARM и лида Apple одним коммитом :)
Вообще интересный тред от Хектора про Розетту и эмуляцию x86 на Apple M1 (уже и M2)
https://mobile.twitter.com/marcan42/status/1534053625110351872
Коротко: Rosetta хорошо работает не потому что Apple сделали фичей под себя, а потому что хорошо написали розетту и заимплементировали первыми FEAT_* стандарты (в том числе для флагов FEAT_AFP)
А коммит оказался тот самый из наших хеш-таблиц.
Потом Dougall внимательно прочитал и предложил ещё более лучшую версию:
https://twitter.com/dougallj/status/1534213050944802816
Итог: я показал как можно использовать редкие инструкции под ARM, Dougall нашёл ещё более оптимизированный способ, мы сконтачились, созвонимся в ближ пару дней и возможно познакомлюсь с Хектором (легенда же!)
Зато вчера я занёрдснайпил экспертов ARM и лида Apple одним коммитом :)
Вообще интересный тред от Хектора про Розетту и эмуляцию x86 на Apple M1 (уже и M2)
https://mobile.twitter.com/marcan42/status/1534053625110351872
Коротко: Rosetta хорошо работает не потому что Apple сделали фичей под себя, а потому что хорошо написали розетту и заимплементировали первыми FEAT_* стандарты (в том числе для флагов FEAT_AFP)
А коммит оказался тот самый из наших хеш-таблиц.
Потом Dougall внимательно прочитал и предложил ещё более лучшую версию:
https://twitter.com/dougallj/status/1534213050944802816
Итог: я показал как можно использовать редкие инструкции под ARM, Dougall нашёл ещё более оптимизированный способ, мы сконтачились, созвонимся в ближ пару дней и возможно познакомлюсь с Хектором (легенда же!)
И в добавок ко всему этому: на работе коллега доделал очень крутую штуку, которую мы не знали как сделать 3 месяца, а один очень талантливый разраб из соседней команды захотел поработать с нами.
Почему хорошие вещи происходят в один день, особенно когда я на полном дне? Я чувствую какой-то подвох/закономерность, что чтобы приключения происходили вокруг тебя, надо обязательно себя кинуть в стену желательно на несколько недель. Хорошие вещи происходят очень хаотично, очень часто в один день как будто что-то прорывает. Weird
P.S. Я в этом году сижу в програмном комитете C++ Zero Cost Conf. Эта конференция про перформанс C++ и любые практические вещи. Мы ищем доклады, подавайтесь, это простой способ поработать со мной над материалом. Даты ещё не определены, где-то июль, насколько я понимаю.
Почему хорошие вещи происходят в один день, особенно когда я на полном дне? Я чувствую какой-то подвох/закономерность, что чтобы приключения происходили вокруг тебя, надо обязательно себя кинуть в стену желательно на несколько недель. Хорошие вещи происходят очень хаотично, очень часто в один день как будто что-то прорывает. Weird
P.S. Я в этом году сижу в програмном комитете C++ Zero Cost Conf. Эта конференция про перформанс C++ и любые практические вещи. Мы ищем доклады, подавайтесь, это простой способ поработать со мной над материалом. Даты ещё не определены, где-то июль, насколько я понимаю.
Одна из самых сложных задач для меня в последнее время это рост нашей инфраструктуры. Очень забавно за этим наблюдать, потому что я обычно люблю что-то строить с нуля, а поддерживать намного все сложнее, но менее важным оно не становится. В том же Гугле просто невероятное количество сил убито на поддержку, но, наверное, так в каждом инженерном деле. Вещи ломаются, тут должны быть умные слова про энтропию, а их надо чинить. И надо сильно верить, что вещи должны работать постоянно, чтобы уметь поддерживать продукт. Не как мой провайдер, который решает раз в 3 недели отключить меня на 4 часа.
Для меня в инфраструктурных командах есть 3 стадии: построение, поддержка и неизбежный рост или смерть. Когда вы строите, но у вас нет пользователей, это самое блаженное время, его надо ценить и беречь. Вы можете ошибаться, экспериментировать и знать, как лучше что-то сделать.
Когда у вас появляются пользователи, вы очевидно на поддержке и у вас начинаются проблемы с обратной совместимостью, багами, краевыми случаями. Дальше, как сказал, продукт или растет, или умирает.
Мы закопали MapReduce, об этом даже говорили на одной встрече Cloud, потому что невозможно было поддерживать. Продукт умер, вкопано десятки тысяч часов, но оно закопано. Написали новый движок, и... до сих пор растем.
По суппорту тоже много мыслей. Традиционно один человек в неделю справлялся с нагрузкой, и это было нормально.
Но количество пользователей то растёт. И мы вот достигли точки нашей инфраструктуры, когда уже человек не справляется.
Дальше интересный вопрос, что с этим стоит делать. Можно двух человек ставить на ротации, а потом и трёх и так далее. Только вот команда так не растёт экспоненциально. А ещё люди меняются, экспертиза во многих местах может быть потеряна.
Это тончайший баланс, как растущую инфраструктуру поддерживать, потому что когда одного человека в день на поддержке становится не достаточно, то на самом деле уже поздно.
Хоть это и показывает, что продукт достаточно успешен, но дальнейший рост ограничен пропускной способностью команды, это в итоге выльется, что все будут только на поддержке. Экспонента покажет, что это наступает ой как быстро.
Это сложный вопрос, что надо делать. Я работаю в Гугле, у нас были такие очень сложные эксперименты роста.
* Документация документацией, но практика показала, что намного лучше читают "best practices", советы, написанные человеческим языком о том, как использовать инфраструктуру. Примеры, tips of the week, testing on the toilet -- позволяют намного больше репликации знаний и снижают фактор вопросов экспоненциально. Документация нужна экспертам, чтобы знать все гарантии, но ее нельзя игнорировать.
* Office hours, кейсы дебага онлайн на аудиторию позволяют погрузиться, посмотреть как стоит подходить к проблемам, это тоже понижает фактор вопросов.
* Процесс построения community. Это так называемые readability -- когда чтобы вкоммитить код, нужно получить зелёную галочку от эксперта по языку, а ещё лучше, когда эксперты меняются. Каждый новый эксперт -- multiplicative фактор, он учит много других многим знаниям, оно передается дальше. Получается иерархия, на верху команда, ответственная за продукт, дальше лучшие эксперты, потом чуть похуже, но все они распространены в командах, и значит на верхушку доносятся намного меньше проблем.
* Self-debug. Когда инфраструктура позволяет анализировать проблемы сама -- лучше компиляторные ошибки, понятные логи, интуитивные инструкции. Это самая инновационная и богатая на идеи тема, помогает хорошо, но иногда сложные вещи просто сложные :(
Я начинаю чувствовать, что если всем этим не заниматься одновременно, видя, что инфраструктура растет, в один момент будет поздно. В целом он произошел, сейчас я много времени провожу, чтобы начать создавать community, потому что нас не хватает. Ещё интереснее, как скейлить Cloud, внутри то мы можем все правильно сделать, а снаружи...
Мне страшно, потому что это фундаментально человеческое, страшно, что нет достаточно authority. Страшнее только команду растить :)
Все ещё читаю Google SWE book, там мы об этом много писали.
Для меня в инфраструктурных командах есть 3 стадии: построение, поддержка и неизбежный рост или смерть. Когда вы строите, но у вас нет пользователей, это самое блаженное время, его надо ценить и беречь. Вы можете ошибаться, экспериментировать и знать, как лучше что-то сделать.
Когда у вас появляются пользователи, вы очевидно на поддержке и у вас начинаются проблемы с обратной совместимостью, багами, краевыми случаями. Дальше, как сказал, продукт или растет, или умирает.
Мы закопали MapReduce, об этом даже говорили на одной встрече Cloud, потому что невозможно было поддерживать. Продукт умер, вкопано десятки тысяч часов, но оно закопано. Написали новый движок, и... до сих пор растем.
По суппорту тоже много мыслей. Традиционно один человек в неделю справлялся с нагрузкой, и это было нормально.
Но количество пользователей то растёт. И мы вот достигли точки нашей инфраструктуры, когда уже человек не справляется.
Дальше интересный вопрос, что с этим стоит делать. Можно двух человек ставить на ротации, а потом и трёх и так далее. Только вот команда так не растёт экспоненциально. А ещё люди меняются, экспертиза во многих местах может быть потеряна.
Это тончайший баланс, как растущую инфраструктуру поддерживать, потому что когда одного человека в день на поддержке становится не достаточно, то на самом деле уже поздно.
Хоть это и показывает, что продукт достаточно успешен, но дальнейший рост ограничен пропускной способностью команды, это в итоге выльется, что все будут только на поддержке. Экспонента покажет, что это наступает ой как быстро.
Это сложный вопрос, что надо делать. Я работаю в Гугле, у нас были такие очень сложные эксперименты роста.
* Документация документацией, но практика показала, что намного лучше читают "best practices", советы, написанные человеческим языком о том, как использовать инфраструктуру. Примеры, tips of the week, testing on the toilet -- позволяют намного больше репликации знаний и снижают фактор вопросов экспоненциально. Документация нужна экспертам, чтобы знать все гарантии, но ее нельзя игнорировать.
* Office hours, кейсы дебага онлайн на аудиторию позволяют погрузиться, посмотреть как стоит подходить к проблемам, это тоже понижает фактор вопросов.
* Процесс построения community. Это так называемые readability -- когда чтобы вкоммитить код, нужно получить зелёную галочку от эксперта по языку, а ещё лучше, когда эксперты меняются. Каждый новый эксперт -- multiplicative фактор, он учит много других многим знаниям, оно передается дальше. Получается иерархия, на верху команда, ответственная за продукт, дальше лучшие эксперты, потом чуть похуже, но все они распространены в командах, и значит на верхушку доносятся намного меньше проблем.
* Self-debug. Когда инфраструктура позволяет анализировать проблемы сама -- лучше компиляторные ошибки, понятные логи, интуитивные инструкции. Это самая инновационная и богатая на идеи тема, помогает хорошо, но иногда сложные вещи просто сложные :(
Я начинаю чувствовать, что если всем этим не заниматься одновременно, видя, что инфраструктура растет, в один момент будет поздно. В целом он произошел, сейчас я много времени провожу, чтобы начать создавать community, потому что нас не хватает. Ещё интереснее, как скейлить Cloud, внутри то мы можем все правильно сделать, а снаружи...
Мне страшно, потому что это фундаментально человеческое, страшно, что нет достаточно authority. Страшнее только команду растить :)
Все ещё читаю Google SWE book, там мы об этом много писали.
abseil.io
abseil / Software Engineering at Google
An open-source collection of core C++ library code
This media is not supported in your browser
VIEW IN TELEGRAM
На дворе 2022 год
Мы находим оптимизации в mem*/str* функциях на мажорных платформах в 20%, которые были доступны как лет 10
Да, это патч в glibc.
В целом история такая: на x86 достаточно легко переходить из векторного кода в скалярный -- если есть вектор сравнения на gif, то на x86 есть PMOVMSKB инструкция, дающая скалярную маску
На ARM такого нет, и все ломали голову как же так сделать. Даже в glibc инженеры Arm делали через 4 цикла и просаживая latency.
Поизучав NEON SIMD, я нашёл инструкцию shrn -- shift right and narrow. На гифке представлен shift right and narrow на 4. Теперь у нас есть маска, не 16 битная, но хотя бы 64, с которой уже можно скалярно работать.
Что произошло в итоге:
1. ZSTD на 5%
2. Хэштаблицы гугла на 3-8%
3. glibc на 10-20% для размеров <=128
4. ClickHouse -- string comparison and sorting на 15%
5. Через 2-3 недели ждите статью, да, это software optimization guide. 3 года назад я студентом увлёкся армами, а вот сейчас я буду писать гайды совместно с вендорами
Mic drop
Мы находим оптимизации в mem*/str* функциях на мажорных платформах в 20%, которые были доступны как лет 10
Да, это патч в glibc.
В целом история такая: на x86 достаточно легко переходить из векторного кода в скалярный -- если есть вектор сравнения на gif, то на x86 есть PMOVMSKB инструкция, дающая скалярную маску
На ARM такого нет, и все ломали голову как же так сделать. Даже в glibc инженеры Arm делали через 4 цикла и просаживая latency.
Поизучав NEON SIMD, я нашёл инструкцию shrn -- shift right and narrow. На гифке представлен shift right and narrow на 4. Теперь у нас есть маска, не 16 битная, но хотя бы 64, с которой уже можно скалярно работать.
Что произошло в итоге:
1. ZSTD на 5%
2. Хэштаблицы гугла на 3-8%
3. glibc на 10-20% для размеров <=128
4. ClickHouse -- string comparison and sorting на 15%
5. Через 2-3 недели ждите статью, да, это software optimization guide. 3 года назад я студентом увлёкся армами, а вот сейчас я буду писать гайды совместно с вендорами
Mic drop
Я тут обнаружил забавную историю, о которой не так много говорят. В Itanium ABI -- у Unix* подобных системах есть пункт про конструкторы и деструкторы классов, чтобы они возвращали void. Оно логично, конструкторы и деструкторы классов ничего не возвращают, зачем им вообще возвращаемые значения.
К сожалению, это приводит к достаточно интересным проблемам при конструировании базовых/деконструировании классов
Выглядит как:
Remember pointer to another register
call destructor of TObject
Move pointer to %rdi
call free memory
В итоге мы лишний раз вызываем сохранение указателя на объект, чтобы удалить память.
Вот в ARM и (внимание!) Fuchsia, WebAssembly ABI практически такой же как и Itanium кроме того факта, что конструкторы и деструкторы возвращают указатель на объект. Из-за этого можно делать tail call оптимизации:
call destructor of TObject
Move return value to %rdi
call free memory
И не надо сохранять никаких объектов до этого
Последняя операция возможна, так как регистры уже подготовлены и все объекты, память готовы к использованию. В первом случае такая операция невозможна, так как ABI не ждёт возврата. Это экономит код, немного перформанса.
С конструкторами чуть интереснее: оно позволяет экономить на перекладывания регистров в случае наследований
Fuchsia ABI позволит делать tail call из B сразу в A, так как все регистры уже подготовлены и указатель на память на начало B тот же, что и на начало A.
В clang вы можете включить опцию
Поэтому, я конечно же, предлагаю пропатчить clang одной строчкой здесь, чтобы разрешить использовать Fuchsia ABI. (просто return true;)
А сам код максимально приятный.
Итог: бинарь кликхауса в релиз режиме был 539854128, стал 520550564 байт, 3.6% размера. Запустился и вроде даже какие-то тесты прошёл.
Перф тесты надо бы запустить, но компилятор патчить и вставлять для этого пока лень. clang на 0.5-1% по тестам стал быстрее компилировать всё после его же бутстрапа.
Какие у этого проблемы? Если вы используете динамическую линковку с C++ библиотеками, будут проблемы, поэтому это работает только если вы контролируете статическую сборку.
Даже в ARM через gdb видны эти вещи
Такие вот проблемы с Itanium ABI, которые мы навряд ли уберем из-за legacy :)
[1] ARM ABI constructor/destructor return values
[2] Itanium ABI constructor/destructor return values note
К сожалению, это приводит к достаточно интересным проблемам при конструировании базовых/деконструировании классов
void destroy(TObject* object) {
delete object;
}
Выглядит как:
Remember pointer to another register
call destructor of TObject
Move pointer to %rdi
call free memory
В итоге мы лишний раз вызываем сохранение указателя на объект, чтобы удалить память.
Вот в ARM и (внимание!) Fuchsia, WebAssembly ABI практически такой же как и Itanium кроме того факта, что конструкторы и деструкторы возвращают указатель на объект. Из-за этого можно делать tail call оптимизации:
call destructor of TObject
Move return value to %rdi
call free memory
И не надо сохранять никаких объектов до этого
Последняя операция возможна, так как регистры уже подготовлены и все объекты, память готовы к использованию. В первом случае такая операция невозможна, так как ABI не ждёт возврата. Это экономит код, немного перформанса.
С конструкторами чуть интереснее: оно позволяет экономить на перекладывания регистров в случае наследований
class B { B(int) {...} }
class A : public class B { A(int x) : B(x) {...} }
Fuchsia ABI позволит делать tail call из B сразу в A, так как все регистры уже подготовлены и указатель на память на начало B тот же, что и на начало A.
В clang вы можете включить опцию
-fc++-abi=fuchsia -target x86_64-unknown-fuchsia
, но будут проблемы, чтобы скомпилировать что-то большое.Поэтому, я конечно же, предлагаю пропатчить clang одной строчкой здесь, чтобы разрешить использовать Fuchsia ABI. (просто return true;)
А сам код максимально приятный.
class FuchsiaCXXABI final : public ItaniumCXXABI {
public:
explicit FuchsiaCXXABI(CodeGen::CodeGenModule &CGM)
: ItaniumCXXABI(CGM) {}
private:
bool HasThisReturn(GlobalDecl GD) const override {
return isa<CXXConstructorDecl>(GD.getDecl()) ||
(isa<CXXDestructorDecl>(GD.getDecl()) &&
GD.getDtorType() != Dtor_Deleting);
}
};
Итог: бинарь кликхауса в релиз режиме был 539854128, стал 520550564 байт, 3.6% размера. Запустился и вроде даже какие-то тесты прошёл.
Перф тесты надо бы запустить, но компилятор патчить и вставлять для этого пока лень. clang на 0.5-1% по тестам стал быстрее компилировать всё после его же бутстрапа.
Какие у этого проблемы? Если вы используете динамическую линковку с C++ библиотеками, будут проблемы, поэтому это работает только если вы контролируете статическую сборку.
Даже в ARM через gdb видны эти вещи
class A {
public:
A() {}
~A() {}
};
(gdb) ptype A::A
type = class A {
public:
A(void);
~A(int);
} *(A * const)
(gdb) ptype A::~A
type = void *(A * const)
Такие вот проблемы с Itanium ABI, которые мы навряд ли уберем из-за legacy :)
[1] ARM ABI constructor/destructor return values
[2] Itanium ABI constructor/destructor return values note
GitHub
llvm-project/clang/include/clang/Basic/TargetCXXABI.h at 68884dde702c2306947f1c38e7e8a3a739b50412 · llvm/llvm-project
The LLVM Project is a collection of modular and reusable compiler and toolchain technologies. - llvm/llvm-project
Оооо, мы наконец-то довыкладывали тут Google Fleetbench
https://github.com/google/fleetbench
Это бенчмарки важные для нас в Google (репрезентативные насколько это возможно). Там сейчас только хэшмапы и протобуфы, но мы со временем добавим больше всякой всячины, типа стандартной библиотеки, компрессоров, в том числе и видео. Возможно пару бенчмарков распределённых систем in a long run :)
Зачем? Мы считаем, что наш стек должен быть быстрым, мы платим деньги за CPU/RAM/Latency. Хотите написать статью/предложить оптимизацию в протобуфах/хэшмапах? Сравните с этим репозиторием, и мы с намного большей вероятностью посмотрим и включим ваш патч (а может даже и наймём)
Если вы из Intel/AMD/ARM/$ВАШ_ЛЮБИМЫЙ_ВЕНДОР и хотите сделать чип, чтобы мы заинтересовались и его купили? Сравнивайте перформанс в этом репозитории. Придумали большую controversal макро компиляторную оптимизацию в LLVM? Возьмите наш репозиторий как ещё одну точку для сравнения.
Ну и в целом мы достаточно хорошо настроены на то, чтобы делиться тем, что нам важно в плане перфа, OSS и мы сильно выигрываем от более оптимизированных библиотек/програм. Как и все остальные тоже.
Из забавного, вы можете посмотреть на самые прожорливые обезличенные схемы протобуфов.
https://github.com/google/fleetbench
Это бенчмарки важные для нас в Google (репрезентативные насколько это возможно). Там сейчас только хэшмапы и протобуфы, но мы со временем добавим больше всякой всячины, типа стандартной библиотеки, компрессоров, в том числе и видео. Возможно пару бенчмарков распределённых систем in a long run :)
Зачем? Мы считаем, что наш стек должен быть быстрым, мы платим деньги за CPU/RAM/Latency. Хотите написать статью/предложить оптимизацию в протобуфах/хэшмапах? Сравните с этим репозиторием, и мы с намного большей вероятностью посмотрим и включим ваш патч (а может даже и наймём)
Если вы из Intel/AMD/ARM/$ВАШ_ЛЮБИМЫЙ_ВЕНДОР и хотите сделать чип, чтобы мы заинтересовались и его купили? Сравнивайте перформанс в этом репозитории. Придумали большую controversal макро компиляторную оптимизацию в LLVM? Возьмите наш репозиторий как ещё одну точку для сравнения.
Ну и в целом мы достаточно хорошо настроены на то, чтобы делиться тем, что нам важно в плане перфа, OSS и мы сильно выигрываем от более оптимизированных библиотек/програм. Как и все остальные тоже.
Из забавного, вы можете посмотреть на самые прожорливые обезличенные схемы протобуфов.
GitHub
GitHub - google/fleetbench: Benchmarking suite for Google workloads
Benchmarking suite for Google workloads. Contribute to google/fleetbench development by creating an account on GitHub.
Релиз: Arm машинки в Google Cloud. Учитывая, сколько я тут писал про Arm (и я себя ещё останавливал), то, наверное, не сложно было догадаться :). Но это штука важна, прочитайте, пожалуйста, тут и про Apple будет.
https://cloud.google.com/blog/products/compute/tau-t2a-is-first-compute-engine-vm-on-an-arm-chip
Мы третьи (AWS, Azure), мы отстаем. Мне стыдно за это, почему так получилось не хочу говорить, мы просто дураки, вот и все. Пытаюсь оправдать себя тем, что если мы просто будем достаточно зарабатывать на этом, то не важно, третьи мы или нет.
И в целом Google никогда не был hardware компанией, понятное дело, мы плохо продаем и делаем железки, как тут говорят: "Google's DNA is software". Чип, который мы релизнули это Ampere Altra на Neoverse N1. Страшные имена, но это чип пока не наш.
Что делаю я в этом проекте? Занимаюсь перформансом и изредка нахождением всяких багов внутри чипа, деплоем на внутреннем облаке и вообще смотрю в будущее software-hardware codesign. Получилось занять нишу, где я общаюсь с обеими группами и перевожу один язык на другой. Оказывается это тяжело, поэтому мало кто занимается.
А теперь мое полотно и мнение:
Никто из Amazon/Microsoft практически не занимался software перфом на армах. Без шуток, учитывая, что я находил, рыская по форумам и git blame, кто что коммитил, я видел только несколько амазоновских коммитов в JVM, но они практически даже не притронулись к компиляторам или софту.
А кто, впрочем, занимается перфом Arm? По факту, сами Arm, Apple и ... Android люди из Google/Samsung (достаточно лениво, телефоны другая задача совершенно). Я видел ещё порой интересные коммиты от Huawei, Alibaba и Tencent.
То есть Китай, Apple и теперь мы подключились. Учитывая, что чип из коробки приносит достаточно сильно perf/$ экономию (10-20%), я был ОЧЕНЬ удивлен, увидев практически ноль вложений в софт.
Мы серьезно улучшили некоторые открытые бенчмарки, потому что мы пришли и пооптимизировали какие-то стандартные вещи по типу сравнений строк, компрессоров, хэштаблиц, собрали ядро правильно. Я сильно погрузился в ассемблер, SIMD, компиляторы, memory models, concurrency protocols, LLC eviction policies и тому подобное.
Я точно могу сказать, что gap в софте огромный. x86 уже 15 лет учит своих пользователей писать код через гайды, когда как Arm не учил практически никого. И это один из факторов, в который я верю, что он будет только расти. Выиграть ещё 2-3% в perf/$ каждый год в течение 2-3 лет из-за софта реально. Наш известный lemire выкладывает всякие проверки на utf8, ваш верный господин @Danlark приходит и поправляет имплементации на Arm https://github.com/simdutf/simdutf/pull/145 на пару десятков процентов.
Apple сделала некоторые вещи по-другому, меня это и удивило, и заставило задуматься. Нет, они не шаманы, нет, они не сделали свои инструкции или спец Memory model (но M1 с TSO), они лишь отошли от гайдов Arm, зная какие инструкции им нужны для крутого перфа, как впаять память поближе к сокетам. Самое важное изобретение -- Rosetta 2, настолько хороший JIT написали действительно знающие компиляторы люди -- команда J.F. Bastien. Они чуть чуть поиграли грязно, отойдя от гайдов, но и в х86 у нас полотно различных железок. M1/M2 чип, сделанный хорошо с хорошим софтом, но чтобы сделать хорошо, надо начать лет 10 назад. Apple смогли сыграть в долгую, и за это им уважение.
В Google мы пока можем предложить историю софта. Мне плевать, если мы соптимизируем для всех (e.g., OSS), конкуренция вендоров поможет развиться всем.
Если будут вопросы по нашим виртуалкам, или вы клиент и хотите перфа на Armах в нашем клауде, автор к вашим услугам, дропните сообщение @Danlark, и мы все устроим, официально и с моим энтузиамом, чтобы ваш софт работал нормально.
https://cloud.google.com/blog/products/compute/tau-t2a-is-first-compute-engine-vm-on-an-arm-chip
Мы третьи (AWS, Azure), мы отстаем. Мне стыдно за это, почему так получилось не хочу говорить, мы просто дураки, вот и все. Пытаюсь оправдать себя тем, что если мы просто будем достаточно зарабатывать на этом, то не важно, третьи мы или нет.
И в целом Google никогда не был hardware компанией, понятное дело, мы плохо продаем и делаем железки, как тут говорят: "Google's DNA is software". Чип, который мы релизнули это Ampere Altra на Neoverse N1. Страшные имена, но это чип пока не наш.
Что делаю я в этом проекте? Занимаюсь перформансом и изредка нахождением всяких багов внутри чипа, деплоем на внутреннем облаке и вообще смотрю в будущее software-hardware codesign. Получилось занять нишу, где я общаюсь с обеими группами и перевожу один язык на другой. Оказывается это тяжело, поэтому мало кто занимается.
А теперь мое полотно и мнение:
Никто из Amazon/Microsoft практически не занимался software перфом на армах. Без шуток, учитывая, что я находил, рыская по форумам и git blame, кто что коммитил, я видел только несколько амазоновских коммитов в JVM, но они практически даже не притронулись к компиляторам или софту.
А кто, впрочем, занимается перфом Arm? По факту, сами Arm, Apple и ... Android люди из Google/Samsung (достаточно лениво, телефоны другая задача совершенно). Я видел ещё порой интересные коммиты от Huawei, Alibaba и Tencent.
То есть Китай, Apple и теперь мы подключились. Учитывая, что чип из коробки приносит достаточно сильно perf/$ экономию (10-20%), я был ОЧЕНЬ удивлен, увидев практически ноль вложений в софт.
Мы серьезно улучшили некоторые открытые бенчмарки, потому что мы пришли и пооптимизировали какие-то стандартные вещи по типу сравнений строк, компрессоров, хэштаблиц, собрали ядро правильно. Я сильно погрузился в ассемблер, SIMD, компиляторы, memory models, concurrency protocols, LLC eviction policies и тому подобное.
Я точно могу сказать, что gap в софте огромный. x86 уже 15 лет учит своих пользователей писать код через гайды, когда как Arm не учил практически никого. И это один из факторов, в который я верю, что он будет только расти. Выиграть ещё 2-3% в perf/$ каждый год в течение 2-3 лет из-за софта реально. Наш известный lemire выкладывает всякие проверки на utf8, ваш верный господин @Danlark приходит и поправляет имплементации на Arm https://github.com/simdutf/simdutf/pull/145 на пару десятков процентов.
Apple сделала некоторые вещи по-другому, меня это и удивило, и заставило задуматься. Нет, они не шаманы, нет, они не сделали свои инструкции или спец Memory model (но M1 с TSO), они лишь отошли от гайдов Arm, зная какие инструкции им нужны для крутого перфа, как впаять память поближе к сокетам. Самое важное изобретение -- Rosetta 2, настолько хороший JIT написали действительно знающие компиляторы люди -- команда J.F. Bastien. Они чуть чуть поиграли грязно, отойдя от гайдов, но и в х86 у нас полотно различных железок. M1/M2 чип, сделанный хорошо с хорошим софтом, но чтобы сделать хорошо, надо начать лет 10 назад. Apple смогли сыграть в долгую, и за это им уважение.
В Google мы пока можем предложить историю софта. Мне плевать, если мы соптимизируем для всех (e.g., OSS), конкуренция вендоров поможет развиться всем.
Если будут вопросы по нашим виртуалкам, или вы клиент и хотите перфа на Armах в нашем клауде, автор к вашим услугам, дропните сообщение @Danlark, и мы все устроим, официально и с моим энтузиамом, чтобы ваш софт работал нормально.
Google Cloud Blog
Tau T2A is first Compute Engine VM to run on Arm | Google Cloud Blog
The Tau T2A is Google Cloud’s first VM family based on the Arm architecture and designed for organizations building cloud-native, scale-out workloads.
https://www.meetup.com/clickhouse-london-user-group/events/286891586/
Буду выступать на митапе ClickHouse в среду 20 июля в Лондоне, расскажу про оптимизации Arm для CH, и в целом про Arm чуть чуть
(Опять меня легко купили на рекламу ClickHouse)
Приходите, если можете
Буду выступать на митапе ClickHouse в среду 20 июля в Лондоне, расскажу про оптимизации Arm для CH, и в целом про Arm чуть чуть
(Опять меня легко купили на рекламу ClickHouse)
Приходите, если можете
Meetup
Login to Meetup | Meetup
Not a Meetup member yet? Log in and find groups that host online or in person events and meet people in your local community who share your interests.
https://github.com/carbon-language/carbon-lang
Выложили мои коллеги из Google новый язык, совершенно в проекте не участвовал, но очень интересно приживется ли такой язык и нужен ли он обществу
Идея: есть С++, его все не любят. Есть Rust, его как-то все настойчиво любят, но у него приоритеты больше по safety и отсутствия багов. Из-за этого страдает перф
Carbon хочется втиснуться посерединке. Он все ещё небезопасный, зато с коробочными санитайзерами. Такой же быстрый, как и С++, но с модулями, без исключений, c дженериками, без ABI совместимости, с тулингом от LLVM, *с тулзой для миграции с С++* (почти рабочей 🙃).
Можете называть его форком С++, можете называть его неудачной попыткой в Rust. Мне будет интересно, приживется или нет. Может он умрет через полгода, а, может, и будет процветать. Деталей сам знаю мало, пошел читать
Выложили мои коллеги из Google новый язык, совершенно в проекте не участвовал, но очень интересно приживется ли такой язык и нужен ли он обществу
Идея: есть С++, его все не любят. Есть Rust, его как-то все настойчиво любят, но у него приоритеты больше по safety и отсутствия багов. Из-за этого страдает перф
Carbon хочется втиснуться посерединке. Он все ещё небезопасный, зато с коробочными санитайзерами. Такой же быстрый, как и С++, но с модулями, без исключений, c дженериками, без ABI совместимости, с тулингом от LLVM, *с тулзой для миграции с С++* (почти рабочей 🙃).
Можете называть его форком С++, можете называть его неудачной попыткой в Rust. Мне будет интересно, приживется или нет. Может он умрет через полгода, а, может, и будет процветать. Деталей сам знаю мало, пошел читать
GitHub
GitHub - carbon-language/carbon-lang: Carbon Language's main repository: documents, design, implementation, and related tools.…
Carbon Language's main repository: documents, design, implementation, and related tools. (NOTE: Carbon Language is experimental; see README) - carbon-language/carbon-lang
Ваш PlayStation 5 умножает числа на три в два раза медленнее, чем ваш ноутбук
После релиза Arm машин я попал в дофаминовую яму из которой я стараюсь выходить всякой рутиной, где я помогал разным проектам (в том числе и всякими оптимизациями в Arm). За последнее время я решил перечитать много литературы, писал много документов и мало кода. It's fine, sometimes there are weeks when you understand completely nothing. Две истории, одна стратегическая, другая практическая, но они связаны.
Я понял, что когда делаю свою работу, я очень мало времени уделяю тулингу в перформансе. Скажем, я делаю процентов 20 анализа данных, что медленно, почему медленно, процентов 40 как это соптимизировать, остальные 40 на то, а как эту историю можно обобщить. Скажем, я недавно писал, как одна доп кешлиния в AMD не выучивается процессором, что она не так уж и важна, дальше вопрос, а в чем различия AMD и Intel, если уж посмотреть глобально. Бенчмарки делались на Intel долго, интересно, есть ли пробелы у AMD, можно ли унифицировать, чтобы всем было хорошо. Нашёл ещё одну историю, но она сильно другая:
На x86 умножение на 3 устроено через старую иструкцию LEA (Load Effective Address). Если коротко, эта иструкция умеет вычислять выражения REG1+X*REG2+Y, где X -- 0, 1, 2, 4, 8, а Y не очень большая константа. В итоге чтобы умножить на 3 надо взять REG1==REG2, X = 2. То есть это сумма X и (X << 1), вполне логично и legacy, которое мы за собой оставили. Инструкции mul/imul работают дольше.
Посмотрев на таблицу инструкций я нашёл, что AMD Zen 2 имеет latency 2, когда как абсолютно все Intel latency 1. Proof.
Нашёл пару мест в коде и поправил на что-то более унифицированное, выиграл немного.
Я назвал этот пост ваш PlayStation 5 умножает числа на 3 в два раза медленнее, чем ваш ноутбук для лайков и хайпа. Вопрос, который мне задали несколько раз:
$AUTHOR_NAME, как ты эту херню находишь?
Я скачал все таблицы инструкций и просто подиффал самую большую разницу. И просто сидел изучал, что из этого выходит. И да, что-то вышло. Есть ещё пару различий
* pclmuldqd -- Zen2-3 имеет latency 4, Intel 7. Это означает, что CRC чексуммы надо вычислять по-другому, Erasure кодеки работают медленнее, нашёл CRC вычисление, сравнил, действительно медленнее
* Ещё некоторый SIMD Memory load, но паттерн доступа к памяти проявился и тогда
OH MY GOD, ИСТОРИЯ ПРО КЕШЛИНИЮ MAKES SOME SENSE. Конечно, не сразу, конечно, чтобы её найти съесть много всего, внимательно смотреть. Главный вопрос, который я себе задаю -- а может ли это быть важно? И да, очень часто можно найти на скейле любую историю.
Но я понял, что тулингом/анализом надо заниматься плотно, может быть, даже 50%, ты открываешь интересные инсайты про те или иные истории.
Большой человек из Netflix (а сейчас из Intel) не зря занимался визуализацией через flame graphs, это тулинг, позволяющий что-то понять, мультипликативный фактор новых открытий и понимания. В Google мы семплируем весь прод каждый день, чтобы открыть понимание, происходит ли что-то (а что-то вообще происходит?).
В ближ время хочу заняться двумя вещами в тулинге: иллюстрация всех ассемблерных инструкций, а также хочу написать свой сжимающий кодек отсортированных с random access и поиском, работа Lemire про FastPFor мне кажется слишком простой, а я слишком много знаю про эту тему. Посмотрим, насколько я загорюсь. Сил не хватает, автор походу постарел, гореть чем-то почему-то стало тяжелее за последний год.
След статья про распределённые системы, обещаю :)
После релиза Arm машин я попал в дофаминовую яму из которой я стараюсь выходить всякой рутиной, где я помогал разным проектам (в том числе и всякими оптимизациями в Arm). За последнее время я решил перечитать много литературы, писал много документов и мало кода. It's fine, sometimes there are weeks when you understand completely nothing. Две истории, одна стратегическая, другая практическая, но они связаны.
Я понял, что когда делаю свою работу, я очень мало времени уделяю тулингу в перформансе. Скажем, я делаю процентов 20 анализа данных, что медленно, почему медленно, процентов 40 как это соптимизировать, остальные 40 на то, а как эту историю можно обобщить. Скажем, я недавно писал, как одна доп кешлиния в AMD не выучивается процессором, что она не так уж и важна, дальше вопрос, а в чем различия AMD и Intel, если уж посмотреть глобально. Бенчмарки делались на Intel долго, интересно, есть ли пробелы у AMD, можно ли унифицировать, чтобы всем было хорошо. Нашёл ещё одну историю, но она сильно другая:
На x86 умножение на 3 устроено через старую иструкцию LEA (Load Effective Address). Если коротко, эта иструкция умеет вычислять выражения REG1+X*REG2+Y, где X -- 0, 1, 2, 4, 8, а Y не очень большая константа. В итоге чтобы умножить на 3 надо взять REG1==REG2, X = 2. То есть это сумма X и (X << 1), вполне логично и legacy, которое мы за собой оставили. Инструкции mul/imul работают дольше.
Посмотрев на таблицу инструкций я нашёл, что AMD Zen 2 имеет latency 2, когда как абсолютно все Intel latency 1. Proof.
Нашёл пару мест в коде и поправил на что-то более унифицированное, выиграл немного.
Я назвал этот пост ваш PlayStation 5 умножает числа на 3 в два раза медленнее, чем ваш ноутбук для лайков и хайпа. Вопрос, который мне задали несколько раз:
$AUTHOR_NAME, как ты эту херню находишь?
Я скачал все таблицы инструкций и просто подиффал самую большую разницу. И просто сидел изучал, что из этого выходит. И да, что-то вышло. Есть ещё пару различий
* pclmuldqd -- Zen2-3 имеет latency 4, Intel 7. Это означает, что CRC чексуммы надо вычислять по-другому, Erasure кодеки работают медленнее, нашёл CRC вычисление, сравнил, действительно медленнее
* Ещё некоторый SIMD Memory load, но паттерн доступа к памяти проявился и тогда
OH MY GOD, ИСТОРИЯ ПРО КЕШЛИНИЮ MAKES SOME SENSE. Конечно, не сразу, конечно, чтобы её найти съесть много всего, внимательно смотреть. Главный вопрос, который я себе задаю -- а может ли это быть важно? И да, очень часто можно найти на скейле любую историю.
Но я понял, что тулингом/анализом надо заниматься плотно, может быть, даже 50%, ты открываешь интересные инсайты про те или иные истории.
Большой человек из Netflix (а сейчас из Intel) не зря занимался визуализацией через flame graphs, это тулинг, позволяющий что-то понять, мультипликативный фактор новых открытий и понимания. В Google мы семплируем весь прод каждый день, чтобы открыть понимание, происходит ли что-то (а что-то вообще происходит?).
В ближ время хочу заняться двумя вещами в тулинге: иллюстрация всех ассемблерных инструкций, а также хочу написать свой сжимающий кодек отсортированных с random access и поиском, работа Lemire про FastPFor мне кажется слишком простой, а я слишком много знаю про эту тему. Посмотрим, насколько я загорюсь. Сил не хватает, автор походу постарел, гореть чем-то почему-то стало тяжелее за последний год.
След статья про распределённые системы, обещаю :)
Telegram
Experimental chill
Что-то я ничего не писал почти 2 недели. А в целом потому что никаких красивых историй не происходило, не хотелось читать статьи, а работать надо было по 60-70+ часов в неделю. Будем менять, но историю по перформансу я вам привёз, конечно же.
В Google вместо…
В Google вместо…
Опять наврал, но на этот раз, пусть будет, #челоперф, продуктивность людей тоже сильно влияет
Забавно наблюдать за собой в карьерном плане. В последние 3 дня я был очень тревожным, прям до невозможности, я заболел и была температура.
Выписал от чего такая тревожность возникла, помимо всяких личных мелких проблем, которые не вызывают такой уровень, выписал парочку:
1. Я разломал прод.
2. Я почему-то начал бояться, что не вывезу одну задачу. И если честно, я её реально не вывожу. Она важная, а я не знаю как её решить уже несколько месяцев, хотя я придумал идею аж два года назад. Страшно увидеть, что идея не работает :)
Казалось бы, мелочи, кто не ломал, и бывают сложные задачи. Притворяться, что у меня идеальная карьера не хочется и это, как бэээ, не правда. Успецкий мужицкий патриархальный успех с промо каждые 6 месяцев приправленный L7 до 28 лет и хатой в центре Лондона это не про меня. И меня, кстати, скорёжило недавно от вопроса, мол, когда следующее промо. Почему это логично меня спросить, что я как будто, должен этого хотеть, я не знаю. Всё само придёт, я просто это знаю, я не оптимизирую свою карьеру очень сильно, и никому не советую, это опасный риск.
Короче, я просто херовенько переживаю неудачи. Помогают народные методы, как
* Поговорить с менеджером
* Притормозить
* Взять отпуск
* Пописать код на Rust
* Починить прод, который сломал, хотя бы откатить
* Покушать вкусной еды
Ну и дальше ковырять, почему задача кажется невыполнимой. Потом возникают всякие интересные особенности
* Мне очень комфортно работать в командах 2-3 человека. Проект/задача включает в себя 5 человек, кто-то да работает не так, как хочу, не делает чего-то. Мне не хватает ни сил, ни желания руководить этим хаосом, когда люди не очень стабильны. Среди 5 людей очень часто кто-то не очень стабилен.
* Люблю близкое общение. Меня похвалили за менторство джунов. В команде на много людей работаю плохо. Check
* Когда у меня отнимают код и заставляют писать много псевдотехнических доков/рисовать презентации, хочется прям выбросить ноутбук и не приходить на работу. Check
Поболтали, поняли, что на самом деле брать много людей пока не надо. Можно расти работая близко с парой людей над проектами. Хочешь Individual Contributor Track -- пожалуйста, дорога посложнее, зато ты счастливее.
Да, счастливее. Буквально за полдня температура снизилась, завтра отдохну до конца и пойду снова херачить, может, дам проекту провалиться. It's fine. Пусть горит, я знаю как ещё в ста местах принести пользы. Пока бизнес позволяет это, это нормально.
Я давно замечаю, что люди самые продуктивные, когда всё в порядке с ментальным ощущением себя в мире. Никаких "через себя", "да ты что не мужик/слабый". Да, мир сложнее, и это утопия, если все такими будут. Тем не менее, искать пути сделать себя спокойнее по жизни важны. Расту, учусь, как ладить с собой.
А теперь учу как разломать прод:
Забавно наблюдать за собой в карьерном плане. В последние 3 дня я был очень тревожным, прям до невозможности, я заболел и была температура.
Выписал от чего такая тревожность возникла, помимо всяких личных мелких проблем, которые не вызывают такой уровень, выписал парочку:
1. Я разломал прод.
2. Я почему-то начал бояться, что не вывезу одну задачу. И если честно, я её реально не вывожу. Она важная, а я не знаю как её решить уже несколько месяцев, хотя я придумал идею аж два года назад. Страшно увидеть, что идея не работает :)
Казалось бы, мелочи, кто не ломал, и бывают сложные задачи. Притворяться, что у меня идеальная карьера не хочется и это, как бэээ, не правда. Успецкий мужицкий патриархальный успех с промо каждые 6 месяцев приправленный L7 до 28 лет и хатой в центре Лондона это не про меня. И меня, кстати, скорёжило недавно от вопроса, мол, когда следующее промо. Почему это логично меня спросить, что я как будто, должен этого хотеть, я не знаю. Всё само придёт, я просто это знаю, я не оптимизирую свою карьеру очень сильно, и никому не советую, это опасный риск.
Короче, я просто херовенько переживаю неудачи. Помогают народные методы, как
* Поговорить с менеджером
* Притормозить
* Взять отпуск
* Пописать код на Rust
* Починить прод, который сломал, хотя бы откатить
* Покушать вкусной еды
Ну и дальше ковырять, почему задача кажется невыполнимой. Потом возникают всякие интересные особенности
* Мне очень комфортно работать в командах 2-3 человека. Проект/задача включает в себя 5 человек, кто-то да работает не так, как хочу, не делает чего-то. Мне не хватает ни сил, ни желания руководить этим хаосом, когда люди не очень стабильны. Среди 5 людей очень часто кто-то не очень стабилен.
* Люблю близкое общение. Меня похвалили за менторство джунов. В команде на много людей работаю плохо. Check
* Когда у меня отнимают код и заставляют писать много псевдотехнических доков/рисовать презентации, хочется прям выбросить ноутбук и не приходить на работу. Check
Поболтали, поняли, что на самом деле брать много людей пока не надо. Можно расти работая близко с парой людей над проектами. Хочешь Individual Contributor Track -- пожалуйста, дорога посложнее, зато ты счастливее.
Да, счастливее. Буквально за полдня температура снизилась, завтра отдохну до конца и пойду снова херачить, может, дам проекту провалиться. It's fine. Пусть горит, я знаю как ещё в ста местах принести пользы. Пока бизнес позволяет это, это нормально.
Я давно замечаю, что люди самые продуктивные, когда всё в порядке с ментальным ощущением себя в мире. Никаких "через себя", "да ты что не мужик/слабый". Да, мир сложнее, и это утопия, если все такими будут. Тем не менее, искать пути сделать себя спокойнее по жизни важны. Расту, учусь, как ладить с собой.
А теперь учу как разломать прод:
В C++, вы можете создавать объекты в классе и объекты, которые задекларированы последними, могут принимать себе параметры предыдущих.
state_ зависит от dep_ в примере. При деструкторе state_ разрушится, потом dep_ разрушится. Всё хорошо. Объекты разрушаются в обратном порядке. Так учили ... всегда и везде, да? :)
Так вот, при default move операторе мы сначала делаем move на dep_, потом на state_ и между move мы получаем state_ с вероятно некорректной зависимостью (вектора или умные указатели вызывают деструктор при move dep_).
Бабах, пруф https://gcc.godbolt.org/z/xG14Wj3E7
Фикс: писать свой move оператор, где вы сначала делаете move на state_, потом на dep_, то есть в обратном порядке.
Скажите же, очень легко написать = default здесь, думая, что язык сам делает всё правильно?
Пойду попишу на расте, что ли
state_ зависит от dep_ в примере. При деструкторе state_ разрушится, потом dep_ разрушится. Всё хорошо. Объекты разрушаются в обратном порядке. Так учили ... всегда и везде, да? :)
Так вот, при default move операторе мы сначала делаем move на dep_, потом на state_ и между move мы получаем state_ с вероятно некорректной зависимостью (вектора или умные указатели вызывают деструктор при move dep_).
Бабах, пруф https://gcc.godbolt.org/z/xG14Wj3E7
Фикс: писать свой move оператор, где вы сначала делаете move на state_, потом на dep_, то есть в обратном порядке.
Скажите же, очень легко написать = default здесь, думая, что язык сам делает всё правильно?
Пойду попишу на расте, что ли
Далёкая память
В датацентрах давным давно уже проблемы с тем, чтобы закупать память -- она очень дорогая. Мы по сравнению с 2011 годом в Борге хоть и видим увеличение утилизации на 5-20%, но медианная утилизация остаётся на уровне 60% [1].
Из-за этого в статьях прошлых лет (года с 2018) сильно развивалось понятие далёкой памяти -- давайте мы абстрагируем память ещё, теперь будем забирать данные с помощью сети из других машин. Самые известные -- Hydra [2], мы на OSDI'22 в Google вышли с Carbink [3]. Обе статьи решают нужные нам задачи:
* Можем достигать лучшей утилизации и уменьшить простой
* Приложения могут аллоцировать больше памяти, чем одна машина
Проблемы очевидны: забирать по сети намного тяжелее, отказоустойчивость не так просто сделать (хранить память на SSD не подходит из-за ещё более медленных восстановлений), модель _памяти_ становится не очень ясной.
Для того, чтобы достигнуть репликации фактора два, можно реплицировать на три узла. Память дорогая и так делать не стоит. Поэтому лучше взять Erasure Coding: в самом простом случае это выглядит как поделить данные пополам, взять xor, теперь у нас есть 3 части, при выпадении любой, можно восстановить все данные. В итоге занимаем x1.5 места, но фактор репликации можно достигнуть любой. За Erasure Codes стоит большая теория, но основная идея, что мы делим данные на N несколько частей, считаем M дополнительных через линейное преобразование. При выпадении любых M, мы можем посчитать обратное линейное преобразование. Математика говорит, что матрицы существуют при почти всех разумных N и M, которые обладают таким свойством. Обычно выбирают что-то из пар (3,2), (4,3), (12,9).
Проблема в том, что когда вы аллоцируете память, её поделить даже на 3, 4 или 12 частей тяжело, так как аллокации объектов не очень большие и erasure codes начинают тормозить. Мы в Google вышли со статьёй о TCMalloc, где мы храним такие объекты в так называемых spans, много мелких объектов хранятся в одном чанке в пару мегабайт. Мы решили делать так же -- мы будем синхронизировать spans объектов (а также erasure codes на множество spans, что мы назвали spanset), а не каждый кусок. Это разгружает сеть, так как объекты в erasure codes находятся внутри спанов, а не разделены. Если ничего не выпало, из одного узла возьмутся данные, в худшем случае при падении узлов читать надо всё.
Изменения происходят в background тредах, если память становится холодной, она помещается в span, инвалидируется по сети, замещается. Узлы, которые отвечают за саму память, время от времени делают compaction, чтобы удалить ненужные spans. Если приложение memory intensive, это увеличивает нагрузку на сеть.
Использовать spans в пару мегабайт имеет лишние ненужные байты. В Carbink мы в итоге насчитали, что используем на 35% больше памяти, чем в Hydra, но зато сильно уменьшили 50 и 99 квантили (в ~1.2-1.5 раз) доступа и throughput.
В статье много интересных деталей -- например, как помечать биты в указателях, как брать RCU lock на указатели, которые приходят по сети и тд.
Меня, конечно, больше интересует вопрос о том, когда это можно ждать в датацентрах? Ну, в худшем случае никогда, в лучшем через 2-3 года, когда software и hardware stack будет к этому готов, потому что использование памяти по сети требует
* Писать немного кода, чтобы использовать такие указатели, прозрачно через аллокатор пока тяжело это сделать. Это кстати не особо большая проблема, сервисы обычно имеют понятное использование больших буфферов
* Perf будет страдать, надо будет аккуратно выбирать пользователей для этого, которым важен throughput, инфраструктура типа MapReduce/Spanner может подойти
* Можно сжимать холодую память в этой далёкой памяти, как делает, скажем, zswap, экономить ещё
Хорошая статья о том, как можно увеличивать утилизацию памяти, когда вы очень большие, ну и мы наконец-то поделились, что думаем о far memory и инвестируем research туда :)
[1] Borg: the next generation
[2] Hydra : Resilient and Highly Available Remote Memory
[3] Carbink: Fault-Tolerant Far Memory
В датацентрах давным давно уже проблемы с тем, чтобы закупать память -- она очень дорогая. Мы по сравнению с 2011 годом в Борге хоть и видим увеличение утилизации на 5-20%, но медианная утилизация остаётся на уровне 60% [1].
Из-за этого в статьях прошлых лет (года с 2018) сильно развивалось понятие далёкой памяти -- давайте мы абстрагируем память ещё, теперь будем забирать данные с помощью сети из других машин. Самые известные -- Hydra [2], мы на OSDI'22 в Google вышли с Carbink [3]. Обе статьи решают нужные нам задачи:
* Можем достигать лучшей утилизации и уменьшить простой
* Приложения могут аллоцировать больше памяти, чем одна машина
Проблемы очевидны: забирать по сети намного тяжелее, отказоустойчивость не так просто сделать (хранить память на SSD не подходит из-за ещё более медленных восстановлений), модель _памяти_ становится не очень ясной.
Для того, чтобы достигнуть репликации фактора два, можно реплицировать на три узла. Память дорогая и так делать не стоит. Поэтому лучше взять Erasure Coding: в самом простом случае это выглядит как поделить данные пополам, взять xor, теперь у нас есть 3 части, при выпадении любой, можно восстановить все данные. В итоге занимаем x1.5 места, но фактор репликации можно достигнуть любой. За Erasure Codes стоит большая теория, но основная идея, что мы делим данные на N несколько частей, считаем M дополнительных через линейное преобразование. При выпадении любых M, мы можем посчитать обратное линейное преобразование. Математика говорит, что матрицы существуют при почти всех разумных N и M, которые обладают таким свойством. Обычно выбирают что-то из пар (3,2), (4,3), (12,9).
Проблема в том, что когда вы аллоцируете память, её поделить даже на 3, 4 или 12 частей тяжело, так как аллокации объектов не очень большие и erasure codes начинают тормозить. Мы в Google вышли со статьёй о TCMalloc, где мы храним такие объекты в так называемых spans, много мелких объектов хранятся в одном чанке в пару мегабайт. Мы решили делать так же -- мы будем синхронизировать spans объектов (а также erasure codes на множество spans, что мы назвали spanset), а не каждый кусок. Это разгружает сеть, так как объекты в erasure codes находятся внутри спанов, а не разделены. Если ничего не выпало, из одного узла возьмутся данные, в худшем случае при падении узлов читать надо всё.
Изменения происходят в background тредах, если память становится холодной, она помещается в span, инвалидируется по сети, замещается. Узлы, которые отвечают за саму память, время от времени делают compaction, чтобы удалить ненужные spans. Если приложение memory intensive, это увеличивает нагрузку на сеть.
Использовать spans в пару мегабайт имеет лишние ненужные байты. В Carbink мы в итоге насчитали, что используем на 35% больше памяти, чем в Hydra, но зато сильно уменьшили 50 и 99 квантили (в ~1.2-1.5 раз) доступа и throughput.
В статье много интересных деталей -- например, как помечать биты в указателях, как брать RCU lock на указатели, которые приходят по сети и тд.
Меня, конечно, больше интересует вопрос о том, когда это можно ждать в датацентрах? Ну, в худшем случае никогда, в лучшем через 2-3 года, когда software и hardware stack будет к этому готов, потому что использование памяти по сети требует
* Писать немного кода, чтобы использовать такие указатели, прозрачно через аллокатор пока тяжело это сделать. Это кстати не особо большая проблема, сервисы обычно имеют понятное использование больших буфферов
* Perf будет страдать, надо будет аккуратно выбирать пользователей для этого, которым важен throughput, инфраструктура типа MapReduce/Spanner может подойти
* Можно сжимать холодую память в этой далёкой памяти, как делает, скажем, zswap, экономить ещё
Хорошая статья о том, как можно увеличивать утилизацию памяти, когда вы очень большие, ну и мы наконец-то поделились, что думаем о far memory и инвестируем research туда :)
[1] Borg: the next generation
[2] Hydra : Resilient and Highly Available Remote Memory
[3] Carbink: Fault-Tolerant Far Memory
research.google
Borg: the Next Generation