All streams
Search
Write a publication
Pull to refresh
-5
@veslemayread⁠-⁠only

User

Send message
На атомиках будете делать

Ну да, сравнить же нужно.

Я на хаскеле могу только с оверхедом по памяти масштаба размера чанка на количество потоков :(

Ну хоть что-то.
Так мы ж сверху договорились, что порядок величины не важен. Ну в два раза медленнее из-за проверок, подумаешь.

Дело не в этом. Дело в том, что есть некая базовая(самая примитивная) и единственно возможная имплементация — она написана на условном С++, императивно. Т.е. она примитивна и убога — её может повторить кто угодно, вот все и занимаются эти.

И смысл олимпиады в том, что попытаться выпилить из своего языка тонны оверхеда и проблем, которые не дают написать аналогичный условному С++ код. А то, что у кого-то там на копейку меньше, а у кого-то больше — это мало что меняет.

Смысл тут в том, что быстрее вы уже не сделаете. А вот питонист может сделать и мы не может у него это право забрать. Поэтому мы уславливаемся, что если все решения ± равны — они равны.

Чем отняли-то?

Ну как чем — задачей, которая является каноничным антипаттерном для всего, что связано с оптимизацией. Везде и во всём.

Так по дефолту пишется с Array, в чём вопрос-то тогда?

Ну почему тогда в хаскеле дефолт не array?

Лол. Когда удобно — просто баги, ничего не меняют, и то, что думать надо, тоже не меняют.

Нет, никаких удобно нет. Вы там сказали про «запутался в условиях», «перепутал, что массивы индексируются не с 1» — ничего из это не связано с оптимизацией, победами над каким-то там С++ и его идиомами — там нет.

Когда неудобно — мне рассказывают, как у меня происходил мыслительный процесс, когда я писал код.

Нет, разговор был не об этом.

Почему тогда в языках с одной имплементацией и без репорта вообще даже такого нет?

Какие языки? Вон раст — там есть. Такая же помойкаполигон. Везде и всюду.

Да, мы сейчас про PyPy вспомним, конечно. Ну давайте ещё повспоминаем про Hugs, Frege, ещё там какой-то хаскель под JVM был кроме него, да мало ли их.

Я не понял что это значит. Это совсем какие-то нонеймы. Очевидно, что если язык какая-то совсем дремучая маргинальная мертвечина, то там ничего не будет вне зависимости от есть/нет больше одно имплементации.

Почему?

Почему что?

Кстати, используемая денотационная семантика что для IntMap, что для Array тут одинаковая. Используемый синтаксис тоже одинаковый — формируем ленивый список как управляющую структуру данных и сворачиваем его. Всё. Канонично. Идиоматично.

Это попытка натянуть сову на глобус. Эта хрен генерирует индекс + значение, а вся неудобная магия происходит в кишках. Это по-сути та самая мутабельная хешмапа.

То есть, я правильно понимаю, что алгоритм плохой, потому что С++ на нём не сияет?

Нет. Не сияет здесь никто. Я уже приводил вполне понятную аналогию. К тому же, вы признаёте, что взяли по-сути подложный алгоритм, по-сути очередной факториал? Т.е. по-сути вы на старте отрубили ноги оппоненту и громогласно заявили о ничьей. Ну сильно, сильно.

К тому же, нету такого алгоритма в котором С++ не будет сиять. Именно поэтому я говорю о важности всех этих понятий «Канонично. Идиоматично.». Ведь именно на это поле вы сможете ответить С++, ведь если вы в каком-то алгоритме будете успешны, то С++ без проблем повторит и обойдёт ваш успех. Это битва не имеет смысла. Но, С++ будет вынужден использовать ваш подход. И именно это для вас должно быть важно.

И всё это каноничные вещи.

Постфактум можно назвать каноничным всё, что угодно. Хорошо, я не против.

А искать не пришлось.

Это неважно.

В чистом ФП нет reference'ов.

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

И это тоже не так. В ленивом языке у вас за термом типа Int в рантайме может лежать охренительный санк на стопицот вычислений. Который, если Int вам не нужен, можно подобрать GC.

Это всё элементарные вещи. В любом языке у вас за объектом может лежать стопицот чего угодно.

А, мы таки точно на императивщину переключились, причём на какой-то микс из C++ и непонятно чего. Ну ок.

Нет, мы переключились на матчасть. Так уж получилось, что железяка императивна, как и рантайм любого языка.

Массив — это просто структура данных с определённой семантикой и определёнными гарантиями производительности. GC тут ортогонален.

Нет. Вы же сами свой тезис в статье и опровергли. Именно потому, что ваш массив обладает правильной семантикой, которую описал я — вы что-то получили. Не обладай он этим, а обладай вашими определениями — этой статьи бы не было.

Я не знаю, как ещё написать, что это пример заведомо неэффективного кода, который писать не надо, и это очевидно сразу, заранее?

Я не говорю про алгоритмическую часть и про то, что можно посчитать(алгоритмически) проще и быстрее. Я именно про конкретную имплементацию, вернее про то, что у этой задачи только одна имплементация и это проблема. Нету никакого манёвра, никаких возможностей что-то сделать.

А добавьте многопоточную хаскель-версию. Я могу добавить С++-версию.
Особенно на питоне и прологе.

Да что вы прицепились к пистону. Я не знаю питона и не могу вам точно сказать — можно или нет. Но раз в pypy есть jit — я уверен, что производительность он выдаст.

И варианты оптимизации выше нигде не обсуждались? Замена accumArray на unsafeAccumArray порядок времени выполнения тоже не меняет так-то.

Да не особо — тут нечего оптимизировать просто. Кто-то умеет в unsafe, у кого-то рантайм не такой тормозной.

В любом случае вся эта борьба — борьба хаскелей с жаваскриптами. У нас есть просто наивный код на С++ и далее идёт соревнование уровня «кто сможет в этом хелоуворлде показать меньший оверхед». Замечу, оверхед не меньший/равный С++, не меньшей/равный в целом, а просто в рамках это задачи.

У С/С++ по-сути отняли тут всё. Это нечестно.

Если она будет частью стандартного питона (а у питона есть стандарт или хотя бы report?) — да без проблем.


Но Clean я не знаю

Как и я.

Его можно убрать повышением абстракций

Всё можно убрать, особенно постфактум. Как и в питоне можно сделать супер-массивы. Но мы имеем что имеем.
>>А чего с базовым?
С того, что мы говорим про «идиоматичный код», а это такой код, который пишет пользователь любого языка по-дефолту.

А какая разница в производительности по сравнению со случаем без transparent hugepages?

Аналогичная показанной. Именно хаскелю нужен transparent hugepages.

Нет, не сразу. Я ж даже написал, что сделал две тупых ошибки сначала (сделал вектор длины n, а не n + 1, и в цикле условия были <, а не <=, потому что рука так привыкла, если делать ни о чём не думая).

Это просто баги — эти обстоятельства ничего не меняют.

Брать специальные либы не нужно и в этом случае: Data.Array является частью спецификации языка.

Ну языки с одной имплементацией являются помойкойполигоном, в котором есть всё. В этом нет ничего удивительного. Абсолютно неважно где эта либа находится — она по-сути взламывает язык, привнося в него что-то непривычное. Если пример с массивом для вас слишком спорный — замените массив на ансейф. Это не особо важно.

Одна из целей (которая была в заметке аж в двух местах указана) — не только посмотреть, насколько эффективно можно сделать, но и какой разрыв между наиболее эффективным и заведомо неэффективным вариантом. Будет разница в 10 раз, в 100 раз, в 10000 раз? Для меня самого это ценная информация в копилку опыта.

Я уже говорил по этому поводу- нинасколько. Данный пример нельзя «сделать» эффективно. В этом и проблема. Вы не сделали ваш код эффективным — просто вы не дали С++ сделать его эффективным.

Ну и, кстати, это вообще какой-то странный критерий, про то, как я пишу сразу. Это было бы имело смысл, если бы на языке писал бы только я.

Вполне очевидный критерий. Да, везде и всюду можно пытаться эмулировать некий условный си, но зачем? Проще написать на си. Но суть заключается в том, что в реальных задачах вы же не станете так писать и ситуация измениться, координально.

Вот я прям сидел подбирал примеры.

Ну почему же подбирали. Просто нашли удобный пример.

А вы что именно имеете в виду? Я имею boxed/unboxed-типы, потому что reference семантику в контексте ФП обсуждать совсем бессмысленно. Да и дальнейший контекст на это намекает.

reference имеет смысл обсуждать всегда. В ситуации с гц есть два варианта. Объект — это объект гц и это всегда reference, со всеми вытекающими. Либо это value-type.

Почему?

Очень просто. Обычные объекты — существуют в скоупе gc и управляется гц, со всеми вытекающими. Элемент массива — это именно value-type, т.е. gc им не владеет(и не управляет), он хранит в себе только значение(и ничего больше).

В общем случае гц должен пройтись по массиву, чтобы пометить указывающие из массива thunk'и как доступные.

Это неважно. Важно лишь то, что гц владеет скоупом, т.е. куском памяти, которой является массив.

Кстати, это ещё одна причина — почему массив не каноничный. Массив целиком и полностью противоречит концепции gc.

На нём я бы не остановился заведомо, см. выше.

Я не понял ответ. Если компилятор/хаскель смог — дальше бы идти не пришлось.

Ну почему одинаково.

Люди привели уже тысячи примеров, я привёл жаваскрипт. Везде результаты одного и того же порядка. К тому же, люди писали(как и я) просто первое, что придёт в голову. Вы же занимались оптимизацией.

Вон, с Go интересные своей неконсистентностью результаты, например.

Вполне типичные результаты.

Питон работает на пару порядков медленнее.

Питон не имеет jit, а я говорил именно о jit+. К тому же, я уверен что эти «порядки» были просто наивным кодом. Если там взять либу с массивчиком(как сделали вы), прикрутить unsafe и какое-нибудь pypy с житом — результат будут другие, я уверен.

Чем на плюсах? Нет, даже тупо по строкам.

Да, особенно по строкам:

    std::vector<int64_t> arr(n + 1);    
    for(int64_t k1 = 1; k1 <= int64_t(std::sqrt(n)); ++k1) {
      for(int64_t k2 = k1; k2 <= n / k1; ++k2)
        arr[k1 * k2] += (k1 != k2 ? k1 + k2 : k1);
    }


Здесь меньше строк и во много раз меньше синтаксического мусора.

Только мы не говорим сейчас о лоу-левеле.

А о чём же? Любой unsafe это по определению лоулевел. Как минимум относительно абстракций. Любая производительность — лоулевел.

Отвечу на некоторые общие вопросы:

Они как минимум показывают нижнюю границу оверхеда от всей этой декларативщины.

Нет, это наивное допущение. Это лишь показывает существования таких алгоритмов, которые работают одинаково плохо ВЕЗДЕ, а декларативщина — это прост одно из этого множества «плохо».

Это примерно как взять человека без ног и с ногами, замотать в ковёр и заставить кататься. А после сказать, что мы получили «нижнюю границу оверхеда отсутствия ног». Нет. Вы выбрали такую игру, в которой забрали у человека с ногами его преимущество — его ноги. По-сути сделав его таким безногим.

За свободу приходится расплачиваться.

За всё приходится, свобода в этом плане не является чем-то уникальным.
Чуть большим объёмом кода

Кода на хаскеле больше.

чуть меньшей безопасностью, чуть меньшей дружественностью к рефакторингу, вот это всё.

Это всё правильно, но есть нюансы. Говорить об этом попросту нету смысла. Если зада требует производительности и масштаба — все эти рассуждения тут же теряют смысл.

Причина проста — все конкуренты тут же исчезаю и лоулевел мир остаётся сам с собою. И тут уже неважно насколько безопасен хаскель — он попросту выносится за скобки, т.к. применения не имеет.

Это с каким из вариантов?

Базовым вашим.

Я только аллокатор на нормальный поменял:
mmap(nullptr, (n + 1) * sizeof(int64_t), PROT_READ|PROT_WRITE, MAP_POPULATE|MAP_PRIVATE|MAP_ANONYMOUS|MAP_HUGETLB|(30 << MAP_HUGE_SHIFT), -1, 0)


На самом деле с transparent hugepage это не совсем обязательно.

Почему? ФП — это не про то, чтобы делать всё на одних списках.

Ну вот вы целую статью написали о том, как победили тысячи проблем. Вам бы не пришлось об этом писать, если бы проблем не было. Вы ведь на С++ написали код сразу, ни о чём не думая и ничего не меняя. Вот именно поэтому.

Data.Array — достаточно каноничная вещь

Вот смотрите. Вы берёте С/С++ и у вас там есть массив. Это база языка. Не нужно решать, делать специальные либы и решать тысячи проблем. Это называется канонично.

Если ещё проще. Канонично — это то, что вы пишете на языке сразу. И вы написали каноничный код на хаскеле в первом вашем примере, а далее попытались «написать как на си».

На самом деле это даже лишнее, но я уже не стал править статью — достаточно левую границу передвинуть с 1 на 0, и всё.

Это понятно, я говорил не об этом. Вы возмущались индексацией с нуля, а значит для вас подобное поведение не является привычным. Т.е. решение этой задачи «производительным» способом потребовало вас отказаться от «привычно».

Ну это ещё как-то можно притянуть, наверное, да. Хотя тогда и в C++ использование [] вместо .at() неидиоматично (или вы считаете идиоматичным потенциально опасный доступ?).

Да, считаю(и не только я). Именно поэтому at() там опция, а [] — дефолт. В этом опять же разница. Вам пришлось изменять своему видению, изменять видению языка и его базовой концепции. Именно это я и имел ввиду, когда приводил все эти примеры.

Действительно неидиоматичным кодом было бы засунуть всё в ST, или взять, например, ghc-primops и нафигачить параллелизм через ручное ковыряние атомиков, или написать всё руками с unboxed-версией инта (которая Int#).

Просто пример слишком просто и удачный. Вам не пришлось далеко уходить, но это просто хорошо подобранный пример. Причём, пример не особе адекватный реальному применению.

Но в хаскеле ghc нет jit,

Я написал это в контексте «как минимум».
>>и нет value-типов (если вы не начинаете брать #-типы, а их тут никто не брал).
Как так нету? А что такое ваш массив? Это именно value-тип.

Зато есть GC

Элемент массива неподвластен гц и является value-типом.
и тот факт, что компилятор может развернуть декларативный код

Неможет. В противном случае вы бы остановились на первом примере.

и это в каком-то смысле ответ на «да эта ваша функциональщина тормозит как не в себя»

Ещё раз, вы пытались доказать силу хаскеля и то, что он может не только в факториалы. Вы написали факториал и получили производительность жаваскрипта. Т.е. вы не сделали ни первого ни второго.

Нету такого

Это плохо, нужно включить в ядре:

CONFIG_TRANSPARENT_HUGEPAGE=y
CONFIG_TRANSPARENT_HUGEPAGE_ALWAYS=y

Скорее всего у вас старый гцц. И у вас очень странные результаты. Покажи результат: cat /sys/kernel/mm/transparent_hugepage/enabled — ядро должно само закатывать хотя-бы 2m страницами такие случаи.
Покритикую. Для затравочки:
62 sec //C++
119 sec //хаскель


Только факториалы и препроморфизмы на нём и писать! Или нет?

Только вот проблема — ваша задача такой же факториал.

Код по-прежнему идиоматичен.

Нет. «идиоматичен» там С++. Так можно что угодно назвать чем угодно, допустим, этот жс тоже «идиоматичен».

function f(n) {
  let arr = new Int32Array(n + 1);
  for(let k1 = 1; k1 <= Math.floor(Math.sqrt(n)); ++k1) {
    for (let k2 = k1; k2 <= n / k1; ++k2) {
      let val = k1 != k2 ? k1 + k2 : k1;
        arr[k1 * k2] += val;
    }
  }
  
  return arr;
}

Этот код, кстати, показывает результат почти такой же как и хаскель(с поправкой на 64битные инты). Правда, в жс нету 64 битных интов, но суть не в этом.

Очевидно, что код не является идиоматичным, когда существуют подобные допущения:
Почему бы нам не использовать массив

необходимости индексироваться с нуля

ленивость в нём нам не нужна.

Попробуем заменить вызов accumArray на unsafeAccumArray:


Думаю, можно сказать, что это победа.

Нет, это не победа. Наоборот — проигрыш. Что вы хотели показать этим примером? «хаскель может как С++», но вы показали не это. Вы показали «существуют такие примеры, которые везде работают хренова и хаскель тут очередной жс».

Да, существуют множество случаев, когда никакая оптимизация невозможна и случай прощает все(почти) недочёты «обычных» языков. Это один из них. Всё, что языку нужно для «показать производительность уровня си» — это value-типы, «массив» и jit. Это и было показано в теме. И подобные примеры все такие. Проблема только одна — такие случаи редки и это те самые факториалы. Они бесполезны, они ничего не показывают.

И самое важное. Сравнение языков — глупость. Нет никакого «быстрого С/С++». Есть люди, которые обладают знанием, пониманием и опытом. А С/С++ — это просто инструмент, который позволяет им этот всё применить на практике. И именно в этом случае что-то становится быстрым. И проблема хаскеля(и иже с ним) не в том, что они как-то плохо считают факториалы(хотя и это тоже. Ведь сколько вы потратили времени на обходные пути). Проблема в том, что они не дают той свободы и той возможности.

Поэтому, статьи на хабре с заголовками «очередной факториал на хаскеле не тормозит» — это хорошо, но мало что даёт. А вот затюнить ядро, чтобы исключить, если попроще, dtlb-промахи — это даёт х2.
Спасибо за статью — очень интересное открытие. Добавьте, пожалуйста, сравнение с gcc — у меня он чуть быстрее clang.

Information

Rating
Does not participate
Registered
Activity