Писал quicksort для одной велосипедной библиотеки(про нее как-нибудь в другой раз), и мне стало лень писать еще и heapsort для последних шагов. Поэтому просто выбрал в качестве pivot случайный элемент массива. Стало интересно, насколько такое решение защищает от worst case. Интересное:
We will prove that for any given array input array I of n elements, the expected time of this algorithm E[T(I)] is O(n log n). Notice that this is better than an average-case bound because we are no longer assuming any special properties of the input. It is a little peculiar: making the algorithm probabilistic gives us more control over the running time.
https://www.cs.cmu.edu/~avrim/451f11/lectures/lect0906.pdf
Никогда раньше не сталкивался с анализом вероятностных алгоритмов, интересное чтение.
We will prove that for any given array input array I of n elements, the expected time of this algorithm E[T(I)] is O(n log n). Notice that this is better than an average-case bound because we are no longer assuming any special properties of the input. It is a little peculiar: making the algorithm probabilistic gives us more control over the running time.
https://www.cs.cmu.edu/~avrim/451f11/lectures/lect0906.pdf
Никогда раньше не сталкивался с анализом вероятностных алгоритмов, интересное чтение.
И, чтобы уж закрыть (на данный момент) тему сортировок, еще 2 факта:
1) Очень интересный хак, про который я не знал. Обычно, на последних стадиях quicksort, применяют более простой, но квадратичный алгоритм. Например, insertion sort. Потому что:
* он проще, для одного его шага нужно выполнить меньше инструкций, и не нужно поддерживать структуру стека вызовов
* insertion sort работает за O(n) для уже почти отсортированных массивов
Так вот, хак заключается в том, что inertion sort применяется не на отдельные кусочки, а на весь k-sorted(то есть, каждый элемент стоит на расстоянии не больше k от своей реальной позиции после сортировки) массив. Это позволяет компилятору сгенерить более хороший(компактный) код для основного цикла quicksort. А еще позволяет нивелировать некоторые недочеты в реализации основного алгоритма. Например, я в первой реализации partition всегда оставлял первый элемент неотсортированным, потому что для реализации "в лоб" для этого требовался указатель на элемент до начала массива. Последний шаг в виде глобального insertion sort пробулькивал неотсортированные элементы на свое место.
2) По идее, heapsort, в среднем, всегда медленнее, чем quicksort. Потому что он всегда перепахивает весь массив, делая nlogn операций, а quicksort адаптивно стремится ничего не делать для уже отсортированных подмассивов. И heapsort скачет по массиву практически рандомно, без всякой cache locality. quicksort же идет по памяти линейно(либо в одну сторону, как в схеме Ломуто, либо навстречу друг другу, как у Хоара). Но я наблюдаю очень консистентное поведение, что heapsort побеждал на некоторых диапазонах размеров входных данных(и не только, когда данные целиком влезали в кеш). Это интересно, и я пока с этим не разобрался.
1) Очень интересный хак, про который я не знал. Обычно, на последних стадиях quicksort, применяют более простой, но квадратичный алгоритм. Например, insertion sort. Потому что:
* он проще, для одного его шага нужно выполнить меньше инструкций, и не нужно поддерживать структуру стека вызовов
* insertion sort работает за O(n) для уже почти отсортированных массивов
Так вот, хак заключается в том, что inertion sort применяется не на отдельные кусочки, а на весь k-sorted(то есть, каждый элемент стоит на расстоянии не больше k от своей реальной позиции после сортировки) массив. Это позволяет компилятору сгенерить более хороший(компактный) код для основного цикла quicksort. А еще позволяет нивелировать некоторые недочеты в реализации основного алгоритма. Например, я в первой реализации partition всегда оставлял первый элемент неотсортированным, потому что для реализации "в лоб" для этого требовался указатель на элемент до начала массива. Последний шаг в виде глобального insertion sort пробулькивал неотсортированные элементы на свое место.
2) По идее, heapsort, в среднем, всегда медленнее, чем quicksort. Потому что он всегда перепахивает весь массив, делая nlogn операций, а quicksort адаптивно стремится ничего не делать для уже отсортированных подмассивов. И heapsort скачет по массиву практически рандомно, без всякой cache locality. quicksort же идет по памяти линейно(либо в одну сторону, как в схеме Ломуто, либо навстречу друг другу, как у Хоара). Но я наблюдаю очень консистентное поведение, что heapsort побеждал на некоторых диапазонах размеров входных данных(и не только, когда данные целиком влезали в кеш). Это интересно, и я пока с этим не разобрался.
Сегодня short story, небольшой, но полезный, хак.
(Наверняка его знает половина аудитории, но я уверен, что другая не знает)
Если у вас есть HashTable<String, V>, то замените ее на HashTable<u64, V>. В качестве ключа используйте хорошую хеш-функцию, типа CityHash64(кстати, пользуясь случаем, заявлю, что, несмотря на то, что всякие farm hash и xxhash хвастаются, что они побеждаю старичка city hash, не ведитесь, все мои измерения говорят, что это не так(ну или покажите мне benchmark, который я смогу сам воспроизвести)). Да, есть вероятность коллизии. И да, для очень большого числа задач этой вероятностью можно пренебречь. При этом, что мы получаем:
1) размер элемента хеш таблицы уменьшается. Это хорошо для cache locality, это хорошо для плоских таблиц.
2) часто элемент становится примитивным типом, и вместо циклов с вызовами конструкторов-деструкторов, случается крупноблочный memcpy, что всегда хорошо
3) для некоторых реализаций хеш-таблиц будет меньше вычислений хеш-функции в сумме(например, если таблица не включает в хранимый элемент хеш от ключа).
(Наверняка его знает половина аудитории, но я уверен, что другая не знает)
Если у вас есть HashTable<String, V>, то замените ее на HashTable<u64, V>. В качестве ключа используйте хорошую хеш-функцию, типа CityHash64(кстати, пользуясь случаем, заявлю, что, несмотря на то, что всякие farm hash и xxhash хвастаются, что они побеждаю старичка city hash, не ведитесь, все мои измерения говорят, что это не так(ну или покажите мне benchmark, который я смогу сам воспроизвести)). Да, есть вероятность коллизии. И да, для очень большого числа задач этой вероятностью можно пренебречь. При этом, что мы получаем:
1) размер элемента хеш таблицы уменьшается. Это хорошо для cache locality, это хорошо для плоских таблиц.
2) часто элемент становится примитивным типом, и вместо циклов с вызовами конструкторов-деструкторов, случается крупноблочный memcpy, что всегда хорошо
3) для некоторых реализаций хеш-таблиц будет меньше вычислений хеш-функции в сумме(например, если таблица не включает в хранимый элемент хеш от ключа).
#bug12309
Apple делает лучшее оборудование, у нее совершенно гениальные процессоры, отличные юзабилисты, но вот качество самой OS - ну такое. На самом деле, с пользовательской точки зрения вполне ОК(процессы шедулятся нормально, FS на 4-, система не фризится, когда копируешь на флешку, в отличие от(https://web.archive.org/web/20200206024633/https://bugzilla.kernel.org/show_bug.cgi?id=12309)), но вот для программирования не очень. Плохо документированные внутренности, довольно плохие способности контейнеризации(sandboxing - ну такое), почти полностью перекрыты возможности интроспекции(dtruss(аналог strace) неработоспособен от слова вообще).
Поэтому я пристально слежу за проектом https://asahilinux.org/
Интересный цикл заметок про реверс инжиниринг графического процессора Apple M1, от одного из разработчиков Asahi.
Красивое:
https://rosenzweig.io/blog/asahi-gpu-part-1.html
https://rosenzweig.io/blog/asahi-gpu-part-2.html
https://rosenzweig.io/blog/asahi-gpu-part-3.html
https://rosenzweig.io/blog/asahi-gpu-part-4.html
Apple делает лучшее оборудование, у нее совершенно гениальные процессоры, отличные юзабилисты, но вот качество самой OS - ну такое. На самом деле, с пользовательской точки зрения вполне ОК(процессы шедулятся нормально, FS на 4-, система не фризится, когда копируешь на флешку, в отличие от(https://web.archive.org/web/20200206024633/https://bugzilla.kernel.org/show_bug.cgi?id=12309)), но вот для программирования не очень. Плохо документированные внутренности, довольно плохие способности контейнеризации(sandboxing - ну такое), почти полностью перекрыты возможности интроспекции(dtruss(аналог strace) неработоспособен от слова вообще).
Поэтому я пристально слежу за проектом https://asahilinux.org/
Интересный цикл заметок про реверс инжиниринг графического процессора Apple M1, от одного из разработчиков Asahi.
Красивое:
https://rosenzweig.io/blog/asahi-gpu-part-1.html
https://rosenzweig.io/blog/asahi-gpu-part-2.html
https://rosenzweig.io/blog/asahi-gpu-part-3.html
https://rosenzweig.io/blog/asahi-gpu-part-4.html
asahilinux.org
Asahi Linux
Porting Linux to Apple Silicon
На днях дизассемблировал некую шаблонную функцию. Содержимое ее было скучным, а вот за mangled name глаз зацепился.
Оно было
https://stackoverflow.com/questions/40157489/in-the-itanium-c-abi-why-does-the-mangled-name-for-template-functions-not-res
long story short - в mangled name в Itanium ABI попадают полные названия dependend types ее аргументов. Не могу для себя решить, это правильно, или нет. Больше похоже на какой-то хак, когда древний компилятор не имел нужного контекста в месте выведения mangled name, и положили то, что было под рукой.
Вообще, мне кажется, делать ABI-compatible изменения С++ кода - нерешаемая задача на практике. Еще одна очень годная статья в тему - https://thephd.dev/binary-banshees-digital-demons-abi-c-c++-help-me-god-please
Оно было
_Z3fooIiEvN3Std4Meta7Private8EnableIfIXgtstT_Li0EES4_E1RE, хотя я ожидал увидеть что-то попроще, типа _Z3fooi(+ указание на то, что функция - шаблон от какого-то параметра, я с ходу, из головы, правильное название не напишу). Видно, что в сигнатуру функции попал enable_if одного из ее аргументов. Полез гуглить, нашел ответ на stackoverflow:
https://stackoverflow.com/questions/40157489/in-the-itanium-c-abi-why-does-the-mangled-name-for-template-functions-not-res
long story short - в mangled name в Itanium ABI попадают полные названия dependend types ее аргументов. Не могу для себя решить, это правильно, или нет. Больше похоже на какой-то хак, когда древний компилятор не имел нужного контекста в месте выведения mangled name, и положили то, что было под рукой.
Вообще, мне кажется, делать ABI-compatible изменения С++ кода - нерешаемая задача на практике. Еще одна очень годная статья в тему - https://thephd.dev/binary-banshees-digital-demons-abi-c-c++-help-me-god-please
Stack Overflow
In the Itanium C++ ABI, why does the mangled name for template functions not resolve dependent typedefs?
For example:
template <typename T>
struct foo
{
using bar = int;
};
// _Z3bazi
void baz(foo<int>::bar quux) {
}
template <typename T>
void baz(typename foo<T>::bar qu...
template <typename T>
struct foo
{
using bar = int;
};
// _Z3bazi
void baz(foo<int>::bar quux) {
}
template <typename T>
void baz(typename foo<T>::bar qu...
У меня зреет большой текст про состояние современного С++, и когда он свернул совершенно не в ту сторону.
Пока он зреет, простая задачка для современного языка С++(мы с коллегами пока не придумали, как ее решать).
Есть класс А, который, с точки зрения внешнего наблюдателя, хочет притвориться классом B. Для этого, как минимум, нужно, чтобы объекты класса A умели эффективно притворяться const/mut-&/&& на объекты класса B. При этом, делать это относительно эффективно(operator B() не подходит).
Вот godbolt: https://godbolt.org/z/9fTcna4dK
Помогите девочке Даше не сойти с ума :)
Пока он зреет, простая задачка для современного языка С++(мы с коллегами пока не придумали, как ее решать).
Есть класс А, который, с точки зрения внешнего наблюдателя, хочет притвориться классом B. Для этого, как минимум, нужно, чтобы объекты класса A умели эффективно притворяться const/mut-&/&& на объекты класса B. При этом, делать это относительно эффективно(operator B() не подходит).
Вот godbolt: https://godbolt.org/z/9fTcna4dK
Помогите девочке Даше не сойти с ума :)
godbolt.org
Compiler Explorer - C++ (x86-64 gcc (trunk))
struct TString {
operator std::string&() &;
operator const std::string&() const &;
operator std::string&&() &&;
};
auto test() {
std::vector<std::string> v;
TString str;
v.push_back(str);
const TString cstr{};
v.push_back(cstr);…
operator std::string&() &;
operator const std::string&() const &;
operator std::string&&() &&;
};
auto test() {
std::vector<std::string> v;
TString str;
v.push_back(str);
const TString cstr{};
v.push_back(cstr);…
https://habr.com/ru/company/otus/blog/580164/
С точки зрения разбора performance статья совершенно бессмысленная, но вот факт, что format string разбирается через регулярки, это жесть. True story?
С точки зрения разбора performance статья совершенно бессмысленная, но вот факт, что format string разбирается через регулярки, это жесть. True story?
Хабр
Измеряем производительность String.format() в Java
Бэкграунд Я раньше считал, что JDK в целом хорошо оптимизирована, и если в JDK есть простой способ решения какой-то задачи, то он вполне подойдет для большинства ситуаций и будет работать хорошо. Но я...
Про shell.
1) Недавно мне понадобился бьютифаер для sh файлов(не спрашивайте). Во первых, в интернетах нашлось даже это, во вторых, шелл оно парсит практически только регулярками(https://github.com/lovesegfault/beautysh/blob/master/src/beautysh.py), и делает это хорошо. Лемма о накачке, говорили они...
2) Вчера прилетело прекрасное - jit compiler для shell, с автовекторизацией! https://www.opennet.ru/opennews/art.shtml?num=55877 Мне кажется, в этой новости прекрасно все, и не надо больше ничего писать.
1) Недавно мне понадобился бьютифаер для sh файлов(не спрашивайте). Во первых, в интернетах нашлось даже это, во вторых, шелл оно парсит практически только регулярками(https://github.com/lovesegfault/beautysh/blob/master/src/beautysh.py), и делает это хорошо. Лемма о накачке, говорили они...
2) Вчера прилетело прекрасное - jit compiler для shell, с автовекторизацией! https://www.opennet.ru/opennews/art.shtml?num=55877 Мне кажется, в этой новости прекрасно все, и не надо больше ничего писать.
👍2
Хозяйке на заметку.
Изучал, как реализуется хвостовая рекурсия. Например, по https://github.com/justinethier/cyclone/raw/master/docs/research-papers/CheneyMTA.pdf
По результатам написал коротенькую портабельную программу, без ассемблерных вставок, которая должна бы выходить по Segmentation Fault, но не выходит:
Изучал, как реализуется хвостовая рекурсия. Например, по https://github.com/justinethier/cyclone/raw/master/docs/research-papers/CheneyMTA.pdf
По результатам написал коротенькую портабельную программу, без ассемблерных вставок, которая должна бы выходить по Segmentation Fault, но не выходит:
#include <alloca.h>Где применить, пока не придумал, но выглядит зело хорошо!
#include <util/stream/output.h>
void F(char* sb, unsigned int n) {
if (n % 1000 == 0) {
char ch;
alloca(sb - &ch);
}
if (n % 1000000 == 0) {
Cerr << n << Endl;
}
F(sb, n + 1);
}
int main() {
char ch;
F(&ch, 0);
}
❤🔥2
Компания Red Hat является главным разрабтчиком gcc. До настоящего времени Red Hat запрещала сборку проектов через clang в Fedora(кроме тех случаев, когда проект просто не собирается gcc). Эти правила совсем недавно поменялись - теперь maintainer проекта может выбирать, каким компилятором будет собираться его проект - https://fedoraproject.org/wiki/Changes/CompilerPolicy. Это незаметное, но очень серьезное изменение.
Я большой фанат clang/llvm(а так же хейтер FSF, GNU, и всего, что они делают), и очень рад наблюдать, что clang начинает теснить gcc и в его традиционном оплоте.
Кстати, интересный еженедельный дайджест про llvm - https://llvmweekly.org/. Clang 13 is coming!
Я большой фанат clang/llvm(а так же хейтер FSF, GNU, и всего, что они делают), и очень рад наблюдать, что clang начинает теснить gcc и в его традиционном оплоте.
Кстати, интересный еженедельный дайджест про llvm - https://llvmweekly.org/. Clang 13 is coming!
В догонку к предыдущему посту. В мире GNU наблюдается какое-то бурление:
https://sourceware.org/pipermail/libc-alpha/2021-July/129577.html
https://gcc.gnu.org/pipermail/gcc/2021-June/236182.html
Я очень надеюсь, что это не связано с нападками SJW на Столлмана, а связано исключительно с technical merits и политикой FSF.
https://sourceware.org/pipermail/libc-alpha/2021-July/129577.html
https://gcc.gnu.org/pipermail/gcc/2021-June/236182.html
Я очень надеюсь, что это не связано с нападками SJW на Столлмана, а связано исключительно с technical merits и политикой FSF.
>Clang 13 is coming!
Тем временем, в стане конкурента: https://twitter.com/ryan_levick/status/1443202538099073027
Тем временем, в стане конкурента: https://twitter.com/ryan_levick/status/1443202538099073027
Twitter
Ryan Levick
Performance highlight of the week for @rustlang: The new pass manager in LLVM 13 (now in nightly) leads to significantly better compile times. Almost every benchmark shows improvements between 5-20% (Right: % change in instruction count; Left: times above…
#cplpl_doomed
В С++ есть фича, которая описана не совсем так, как описаны большинство фич С++(с помощью понятия "наблюдаемое поведение"), а с помощью описания трансформации AST. Речь идет про range-based for loop.
Вот преобразование AST: #abi
Теперь и в твитторе: https://twitter.com/NicoJosuttis/status/1443267749854208000?s=19
С++ doomed. С этими старперами из IBM/Google/Microsoft, которые заинтересованы лишь в том, как бы ничего не сломать, у руля, C++ скоро превратится в Cobol.
Лично я об этом не жалею, для души буду писать на Rust, зато безбедная старость и образование для детей обеспечено.
В С++ есть фича, которая описана не совсем так, как описаны большинство фич С++(с помощью понятия "наблюдаемое поведение"), а с помощью описания трансформации AST. Речь идет про range-based for loop.
Вот преобразование AST: #abi
{
auto && __range = range-expression ;
auto __begin = begin_expr ;
auto __end = end_expr ;
for ( ; __begin != __end; ++__begin) {
range-declaration = *__begin;
loop-statement
}
}
Это приводит к очень печальным проблемам, например, вот в таком случае:for (auto& v : return_temporary().return_reference_to_temporary_subobject())Коллеги из комитета по стандартизации сегодня рассказали, что С++ комитет отказался чинить эту фичу, потому что нет общего решения для С++, его надо придумать до C++26, но придумать никто не может.
Теперь и в твитторе: https://twitter.com/NicoJosuttis/status/1443267749854208000?s=19
С++ doomed. С этими старперами из IBM/Google/Microsoft, которые заинтересованы лишь в том, как бы ничего не сломать, у руля, C++ скоро превратится в Cobol.
Лично я об этом не жалею, для души буду писать на Rust, зато безбедная старость и образование для детей обеспечено.
Twitter
Nicolai Josuttis
The C++ Standards Committee just decided they do not want to fix the broken range-based for loop for C++23 (it's broken for 10 years now). They agree that there is a severe problem; but want a general lifetime solution (but nobody wants to do the work). …
