Как стать автором
Обновить

Комментарии 45

Зачем сравнивать производительность с питоном если есть джулия?

Почему фикус?

Сравнение с Питоном проводилось поскольку это флагманский язык для ML/AI, на нем сейчас пишут практически все в этой индустрии.

С Джулией (Julia) интересно было бы сравниться, в следующей статье надо будет обязательно добавить. Это очень неплохой язык, где также реализована поддержка массивов на уровне Matlab, правда, насколько я понимаю, там выбрана динамическая типизация как вариант по-умолчанию, соответственно чисто теоретически эффективность кода будет скорее всего ниже чем в C++ и в Фикусе. На практике же и язык и компилятор и стандартная библиотека находятся в уже очень продвинутом состоянии, в компиляторе реализован вывод типов, есть возможность использовать SIMD-интринзики, есть возможность для конкретных случаев тонко настраивать код, например отключать проверку на диапазон при доступе к массиву, поэтому есть возможность при желании получить довольно быстрый код.

По поводу названия. Ну как-то так получилось, рассматривал названия, начинающиеся на F/Ф, чтобы подчеркнуть функциональность. Случайно узнал что Фикусовые - это целое семейство, включающее большое количество известных растений, от комнатных до совершенно эпических, используемых для совершенно разных целей. Меня такая универсальность и "масштабируемость" очень порадовала.

Но вы сравнивали же производительность, а удав инвалид по скорости это всем и так ясно.

"поддержка массивов на уровне Matlab" уровень Матлаба это очень низкая планка.
Джулия скорее компилируемая, я бы ожидаю что джулия будет в районе фикуса по производительности.

Мне вот интересно, в ваших тестах фикус отстаёт от С++, а понятно из за чего?

Да, понятно. Быстрейшие реализации часто используют SIMD-интринзики, иногда довольно нетривиальным образом. Иногда реализуется специальные аллокаторы памяти, иногда реализуются специализированные структуры данных под каждый бенчмарк

В-целом, если заглянуть внутрь быстрейших реализаций, то становится понятно что весь код или даже значительная часть кода в больших проектах таким образом не пишется. Подобным образом оформляется может 5% критичных циклов.

Поэтому цель здесь не победить в бенчмарках, хотя это был бы приятный побочный эффект, а выявить довольно общие проблемные места в самом языке, в компиляторе, рантайме или в стандартной библиотеке, и их улучшить.

Например бенчмарка btree значительно ускорилась после: 1) изменения представления рекурсивных вариантов, когда "пустые" листья стали представляться нулевыми указателями вместо выделения под них памяти, 2) подключения rpmalloc, который сейчас основной аллокатор в Фикусе и 3) распараллеливания бенчмарки по деревьям (@parallel for). Это общие полезные оптимизации. Дальнейшее ускорение скорее всего возможно при переходе с обычного подсчета ссылок на базе атомарных операций на т.н. biased reference counting, когда в большинстве случаев атомарных операций можно избежать. Опять же, это общая оптимизация которая будет полезна для многих программ.

Бенчмарки spectralnorm и mandelbrot должны хорошо ускориться после автоматического портирования некоторых циклов под GPU. Это интереснее и полезнее (хотя и гораздо сложнее) чем тьюнить код одной конкретной бенчмарки. Для mandelbrot также можно в одном месте чуть подкрутить компилятор. Возможно это позволит догнать суб-оптимальную/стандартную версию на C++.

k-nucleotide нуждается в более эффективной реализации хеш-таблицы, хотя может и что-то другое можно придумать

Для n-body также можно в одном месте чуть подкрутить компилятор, но в-целом код неплохой. В реальности n-body это довольно игрушечная симуляция. Будет интереснее попробовать Фикус на гораздо более тяжелых симуляциях, и тогда опять же портирование на GPU должно сыграть.

А, нет мысли порешать какие то задачи на Ficus ресурса http://rosettacode.org/ в сравнении с возможностями других языков?

P.S. В i-net есть, для каких то языков, возможность проверки Online решений на них, а для Ficus предполагается такой же опционал?

Это было бы полезно, и для пополнения unit-тестов нетривиальными примерами, и для продвижения языка. Один из примеров, ycomb.fx как раз был взят из rosettacode, потому что засомневался что оно вообще в Фикусе заработает. Вопрос только в ресурсах. Это был бы полезный студенческий проект, а может и не один. Надо будет это записать и подумать, какими силами это сделать.

Насчет доступа к фикусу онлайн - также полезная возможность, но пока сам это делать не планирую.

Так питон там как клей для всяких си/фортран либ используется, у них должно быть норм с производительностью.

Очень интересно было бы попробовать, но, к сожалению, гитхабовская ссылка не работает :(

Прошу прощения, в конце ссылки магическим образом добавился лишний символ. Правильная ссылка https://github.com/vpisarev/ficus

Вообще, конечно, очень интересно -- всё-таки полноценный DSL язык с достаточно глубокой проработкой темы и явно не "на коленке" сделанный. Но штука специализированная, поэтому со стороны трудно предложить толковую реплику кроме "спасибо за статью". Но всё же слегка есть ощущение "начали за здравие..." Например, есть рассуждения о том, что в C++ с массивами не очень, нельзя выполнить глубокую оптимизацию вложенных циклов и т.п. Но в конечном итоге всё равно по бенчмаркам не выиграли же.

Вопрос вполне справедливый: где деньги, т.е. где обещанная скорость? Дайте немного времени. Считаю что проектные решения, заложенные в языке правильные, но нужно время чтобы их превратить в реальную скорость. Это будет один из главных приоритетов на ближайший период.

Ну и, справедливости ради, это не совсем DSL. Скорее полноценный язык (на котором можно написать компилятор например) с хорошей поддержкой массивов.

Спасибо, я попробовал, всё собралось и тестовые примерчики компиляются.

У меня есть практический вопрос. Как использовать Фикус из кода на других языках, ну скажем из С/С++? Я почитал документацию - там у вас пример, когда Си используется в Фикусе, а мне хочется обратного. В идеале получить просто DLL, куда передать указатели на массивы и получить обратно результат. Если у вас там Си используется, то это должно быть совсем несложно. Я, конечно, могу взять сгенерированнный код на Си, добавить там экспорт, а прямо из Фикуса можно DLL получить?

Ну вот, к примеру есть у меня примитивный код, который гонит 16 бит картинку в 8 бит для отображения на экране с линейной LUT между min и max:

__declspec(dllexport) void __cdecl  Mapping16to8(
		unsigned short *src_ptr_16bit, unsigned char *dst_ptr_8bit,
		int src_bytesPerLine, int dst_bytesPerLine,
		int width, int height, int min, int max)
//...опуская всякие проверки  
for (i = 0; i < imin; i++) LUT16[i] = 0;
for (i = imin; i < imax; i++) LUT16[i] = (int)(double(i - imin) / (imax - imin) * 255.0);
for (i = imax; i < 65536; i++) LUT16[i] = 255;
    
for (int y = 0; y < height; y++) {
    for (int x = 0; x < width; x++) {
        *dst_ptr_8bit = LUT16[*src_ptr_16bit];
        src_ptr_16bit++; dst_ptr_8bit++;
    		}
    //учитываем выравнивание
    src_ptr_16bit += (src_bytesPerLine >> 1) - width;
    dst_ptr_8bit += dst_bytesPerLine - width; 
}

Тут надо учесть ещё то, что массивы у меня выровненные (на 64 байта, что в общем стандартная практика для быстрой обработки изображений). Вот как это на Фикусе будет выглядеть?

НЛО прилетело и опубликовало эту надпись здесь

Я посмотрю непременно, спасибо. Halide выглядит интересно и вот как-то прошёл мимо меня, я и не знал про него, если честно.

Теоретически вызвать Фикус из C/C++ должно быть несложно. Практически в данный момент это не так просто поскольку пока компилятор работает только в режиме генерации кода для приложений.

Вот пример, который воспроизводит ваш C++ код:

fun make_lut_16to8(imin: int, imax: int)
{
    assert(0 <= imin <= imax < 65536)
    [| for i <- 0:65536 {sat_uint8((i - imin)*255./(imax - imin))} |]
}

fun apply_lut(img: uint16 [,], lut: uint8 []) =
    [| for pix <- img {lut[pix]} |]

val L = make_lut_16to8(16, 4096)
val img8 = apply_lut([| for i <- 0:6 for j <- 0:6 {uint16((i+j)*500)}|], L)
println(img8)

Проблема в том что если собрать его с -O3 или -O1, то эти функции подставятся и исчезнут сами по себе. Пока нужно компилировать только с -O0, но тогда скорость будет ниже. Но тем не менее, для проверки можно это сделать. Допустим, если сохранить этот код в файле lutop.fx и собрать, то вы получите каталог ficus/__fxbuild__/lutop/ с кучей .с файлов, включая lutop.c, там будет функция FX_EXTERN_C int _fx_M5lutopFM9apply_lutA2b2A2wA1b(fx_arr_t* img_0, fx_arr_t* lut_0, fx_arr_t* fx_result, void* fx_fv), которую можно вызвать, если соответствующим образом подготовить входные массивы:

_fx_arr_t img = {0, 0, 0, 0, 2, (char*)src_ptr,
   {{height, src_stride}, {width, sizeof(uint16_t)}};
fx_arr_t lut = {0, 0, 0, 0, 1, (char*)lut_ptr, {{65536, 1}};
fx_arr_t result = {0};
int errcode=_fx_M5lutopFM9apply_lutA2b2A2wA1b(&img, &lut, &result, 0);
...

Подобная обертка может располагаться в .с/.cpp файле и она как раз может быть dllexport. После чего останется включить все сгенерированные .c файлы, вместе с runtime, в свой проект. Но конечно это пока не очень простое и не удовлетворительное решение.

Спасибо большое, всё понятно. Но вы на заметку возьмите - может добавите когда-нибудь.

И вот ещё маленький пункт, ну что б два раза не вставать — насчёт свёртки вот здесь:

// применяем фильтр [|0, 1, 0; 1, 4, 1; 0, 1, 0|]/8
for y <- 1:h-1
  for x <- 1:w-1 {
     B[y-1, x-1] = uint8((A[y-1,x]+A[y,x-1]+A[y,x]*4+
                          A[y,x+1]+A[y+1,x])>>3)
  }

Было бы здорово иметь возможность создавать массивы с "бордерами". Ну то есть вокруг двумерного массива ещё несколько строк и столбцов резервируется, что даёт возможность применять фильтр ко всему массиву, не вылетая за его границы и вам не придётся отступать от границы, как в примере выше. При этом первый элемент так и остаётся (0,0), а индекс имеет возможность становиться "негативным". Сам бордер обычно зеркалируется, но иногда константой заливается. Для фильтра 5х5 нам "двойной" бордер потребуется. Ну и другие фильтры будет удобнее применять, медианный, к примеру. Надеюсь я понятно изложил (если нет - я картинку могу нарисовать).

С бордерами это да, иногда очень нужно. В Фикусе это реализовано, специально для обработки изображений, просто не стал в основную статью включать, чтобы не перегружать:

for y <- 0:h for x <- 0:w {
   B[y,x] = uint8((A.clip[y-1,x] + A.clip[y,x-1] +
         A[y,x] + A.clip[y,x+1] + A.clip[y+1,x]) >> 3)
}

кроме .clip еще можно .wrap (то есть из плоской картинки делаем тор) и .zero - дополнение нулями.

Как использовать Фикус из кода на других языках, ну скажем из С/С++? 

Простите, что влезаю в чужой разговор, но Вы уверены, что эти вызываемые процедуры оптимально писать именно на Фикусе? В общем, я бы тоже предложил глянуть в сторону других вариантов - а конкретно фортрана. Модульность там реализована уже довольно давно (начиная с фортрана-95), а поддержка массивов просто близка к идеалу. Язык позволяет удобно писать практически любые расчеты. Начиная от простейших массивных операций (это когда в арифметическом выражении вместо имени переменной можно указать имя массива) и задания почти произвольных сечений массива, и кончая такими операторами, как FORALL и WHERE. Если использовать только чистые функции (которые тоже появились в стандарте фортран-95), то циклы для обработки массивов вообще практически не нужны. А главное, все эти операторы очень эффективно оптимизируются компилятором. Скорость исполнения получается примерно такая же, как на Си, при том, что высокоэффективный код писать значительно проще. (Только не забудьте при тестах производительности включать в обоих языках оптимизацию максимально, иначе неоптимизированная программа кратно проиграет оптимизированной).

Впрочем, насчет легкости кодирования это наверно уже вопрос привычки и вкуса. К примеру, в фортране ввод-вывод - это часть языка, и поэтому есть целый зоопарк операторов ввода-вывода с кучей спецификаторов для работы с разными типами файлов (а в Си это вынесено в библиотеки). Еще один минус фортрана - он "тащит" совместимость со старым кодом, из-за этого в языке до сих пор сохраняются устаревшие операторы и возможности (вроде Include, goto, common и др.). Использовать их никто не заставляет, но документацию это слегка усложняет.

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

  real(16) ::        a(100), d(100)
  ...
d=sin(a**2)

явно проще, чем "крутить цикл". Который компилятор потом будут оптимизировать на свое усмотрение (и если он сложный - то может случиться, что поймет его не совсем так, как хотел программист). А приведенная выше запись просто не оставляет простора для толкований.

Но и плюсов (простите за каламбур) у фортрана тоже не мало. Я бы сюда отнес развитые инструменты для контроля точности вычислений с плавающей точкой и написания переносимых программ, а также средства для мультиязычного программирования (с помощью интерфейсов можно как вызывать функции из других языков, так и писать свои функции, которые будут передавать параметры в вызывающую программу "по ее правилам"). Или такой нюанс: проверка использования неинициализированных переменных, выхода индекса за границы массива и т.д. и т.п. - это отдельные опции компилятора, и их можно включать/выключать вне зависимости от того, собираю ли я debug-версию или release. (Да, это свойство компилятора, а не языка, но подозреваю, что все основные компиляторы это умеют). Кстати, для желающих в языке сейчас есть минимальные элементы ООП. Но для тех, кто занимается вычислениями, одно из основных преимуществ - это наличие мощных библиотек для всевозможных расчетов, которые обычно прилагаются к компилятору, поэтому их использование выглядит, как использование "стандартных библиотек языка". Само собой, операции с матрицами и линейная алгебра там тоже есть ;-) Причем обычно эти библиотеки имеют версии для расчетов с разной разрядностью (не обязательно все считать в double), и т.д.

В общем, если кому-то будут интересны подробности - пишите, расскажу что смогу (с примерами кода).

НЛО прилетело и опубликовало эту надпись здесь

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

А написать самому - разумеется, можно. В простейшем варианте я бы это так сделал: завел массив размером с картинку, и написал один оператор FORALL с логикой (взвешенное усреднение по соседним пикселлам, с маской игнорирования пикселлов вне картинки). А затем доверился оптимизатору, чтобы всю остальную работу он сам сделал. Так как FORALL однозначно говорит компилятору, какой результат я хочу - а вот каким путем конкретно он будет достигнут, уже не мои проблемы. То есть, у компилятора есть неплохая свобода оптимизаций. А учитывая, что это обычная свертка, есть некоторая надежда, что компилятор все по-умному разрулит.

Понятно, что потом все равно надо будет тестировать скорость и сравнивать. Априори у меня есть маленькое подозрение, что скорость будет не очень плохая, так как включение в компиляторе опции "/Qparallel" мою программу на моем компьютере (6 ядер) в несколько раз ускоряет. Без каких-либо телодвижений с моей стороны. Но это именно домыслы - твердых знаний по этой части у меня нет.

В принципе, в языке есть возможность получить количество ядер и распараллелить расчеты вручную. Но если честно, я с этим вопросом не разбирался пока - хватает оптимизатора. У меня ведь временные ряды достаточно небольшие - обычно не больше нескольких миллионов значений. Для случая двух измерений это получится картинка со стороной 1-10K. Ну и свертка у меня всегда идет по одному измерению, а не по двум.

Вообще, я думаю, что метеорологи в этих вопросах лучше меня шарят. У них ведь там трехмерные модели и сетки. Только вот на хабре я таких пока не встречал. Если у Вас с языком нет проблем, то в англоязычных форумах наверняка можно более точный ответ получить. Либо могу попытаться через знакомых такие контакты в РФ найти. Я сам давно собирался этим поинтересоваться. Но это дело очень небыстрое, надо искать седьмую воду на киселе....

Спасибо за такой развёрнутый ответ. Об оптимальности речи не идёт вовсе, да и не собираюсь я с Фикусом идти в продакшн. Я просто интересуюсь всякими новыми (и местами эзотерическими) штуками - просто ради любопытства. Вот неспешно пилю "пет-проект" для души - это программка для работы с 16-ти битными картинками (типа ImageJ, но чуть удобнее). Основной язык - C#, интерфейс на Avalonia, основная библиотека OpenCV через opencvsharp, ну и самописные функции, в которых я могу использовать любой стек технологий, какой заблагорассудится - тут я ничем не ограничен.

Что касается работы с массивами без циклов - то по работе я использую LabVIEW и там примерно тоже самое:

Ну а мощнейшая математическая библиотека вкупе с весьма приличной библиотекой обработки изображений покрывают в общем все нужды с лихвой. Но "эзотерики" в LabVIEW конечно хватает.

К Фортрану я как-нибудь вернусь. У нас было два семестра численных методов (один на Фортране, второй на Паскале) и я верю, что современный Фортран далеко ушёл от того, что я учил тридцать лет назад.

Фортран это очень мощный язык, и он конечно еще всех переживет (как и Лисп). Тем не менее, если взять свежую версию Фикуса, и запустить вот такой примерчик:

val a = [| for i <- 0:100 {i/100.*M_PI} |]
println(sin(a.**2))

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

for (int_ i_0 = 0; i_0 < 100; i_0++, dstptr_0++) {
   *dstptr_0 = sin(pow(i_0 / 100.0 * 3.141592653589793, 2));
}

То есть, компиляторы Фортрана круты, а библиотеки на Фортране бесценны, но и мы уже кое-что можем.

Синтаксис просто прекрасен, OCaml обернутый в Julia. Без шуток, очень приятно

Спасибо. Cтиль Julia немного отличается, мне кажется, скорее здесь параллели можно провести со Swift, Scala, Rust. Насчет Ocaml все справедливо - это основной источник влияния.

В качестве лайфхака - если взять редактор с поддержкой лигатур (например, Sublime или VSCode) и соответствующий шрифт, то будет еще красивее. В репозитории Фикуса можно найти один из таких шрифтов.

Вам бы из Джулии взять основную её сверхспособность - она очень универсальная, считай почти что это Лисп с его лозунгом «Лучше иметь 100 функций, которые работают с одной структурой данных, чем 10 функций работающих с 10 структурами».

Главное – это именно multiple dispatch в сочетании с опциональной типизацией и выводом типов. Это позволяет решить expression problem (https://en.wikipedia.org/wiki/Expression_problem – The goal is to define a datatype by cases, where one can add new cases to the datatype and new functions over the datatype, without recompiling existing code, and while retaining static type safety (e.g., no casts)) – отдельно пополнять алгоритмы и структуры данных, причём не теряя в производительности и не залезая внутрь уже имеющегося кода. Это ключ к модульности, платформенности, созданию библиотек. Пополнить код новыми типами данных и процедурами можно без перекомпиляции уже имеющихся библиотек и потери в производительности, в этом фишка. И не путайте multiple dispatch с перегруженными операторами, это не про синтаксис вообще! Это типа засунуть концепт базы данных внутрь языка: позволить писать алгоритмы, которые не нужно потом менять по мере постепенного раскучерявливания структур данных. Особенности обработки новых структур данных можно просто добавлять, как программы обработки данных в базах данных. С другой стороны, можно добавлять процедуры – это как засунуть базу процедур внутрь языка, симметричная базам данных идея, которая должна бы использоваться при распухании не данных, а кода.

Это типа засунуть концепт базы данных внутрь языка: позволить писать алгоритмы, которые не нужно потом менять по мере постепенного раскучерявливания структур данных. Особенности обработки новых структур данных можно просто добавлять, как программы обработки данных в базах данных. С другой стороны, можно добавлять процедуры – это как засунуть базу процедур внутрь языка, симметричная базам данных идея, которая должна бы использоваться при распухании не данных, а кода.

https://ailev.livejournal.com/1218155.html

Я не знаю ни одного языка, который бы так близко и эффективно приблизился к решению проблемы Expression problem.

Спасибо, поизучаю вопрос. На первый взгляд, не вижу проблем, по крайней мере в той области где я работаю (компьютерное зрение, машинное обучение), перекомпилировать компоненты программы при добавлении к ней новых фич или при изменении в структурах данных. Строгий контроль над типами лично по моему опыту очень сильно экономит время на приведение кода к рабочему состоянию. В дополнение к этому, он позволяет организовать перегрузку функций с буквально нулевым накладным расходом, т.к. нужная функция для соответствующего типа данных (или комбинации типов данных) выбирается в момент компиляции, а не в runtime. Если же вдруг необходимо сделать динамический dispatch, то можно использовать интерфейсы:

// generic interface for feature detectors
interface FeatureDetector {
    fun detect(image: uint8 [,], maxfeatures: int): (float*2) []   
}

// 2 implementations: SIFT and SURF
class SIFT : FeatureDetector {
   ... // SIFT parameters
}

fun SIFT.detect(image: uint8 [,], maxfeatures: int) {...}

class SURF : FeatureDetector {
   ... // SURF parameters
}

fun SURF.detect(image: uint8 [,], maxfeatures: int) {...}

...
var param_fdetector = "SIFT"
fun parse(args: string list) {
   | [] => {}
   | "-features" :: algoname :: rest =>
       param_fdetector = algoname.toupper(); parse(rest)
   ... // process other command-line parameters
   }
parse(Sys.arguments()) // read command-line parameters,
                       // set the preferred detector
...
fun apply_some_feature_detector(image: uint8 [,],
                                ~maxfeatures:int=1000)
{
    val fdetector =
       if param_fdetector == "SURF" {(SURF {...} :> FeatureDetector)}
       else {(SIFT {...} :> FeatureDetector)}
    fdetector.detect(image, maxfeatures)  
}
также есть препроцессор, напоминающий таковой в C/C++, но более простой и думается более безопасный. Задачу условной компиляции он вроде как решает.

А чем гарантируется безопасность?
А что с автовекторизацией и раскручиванием циклов?
Есть ли compile time выражения?
Вообще перечисляется довольно немало языковых фич, но при этом крайне малое количество примеров кода. У фикуса есть здоровенная документация? Что там с генерацией доков кстати?

В препроцессоре Фикуса нет директивы include. Вместо этого есть настоящие модули. Тем самым, локально объявленные в модуле символы не влияют на остальные модули.

Во-вторых, символы препроцессора содержатся в отдельной таблице, и не конфликтуют с символами в коде.

// preprocess_test.fx
@IFNDEF UI
@DEFINE UI "CLI"
@ENDIF
val UI = @{UI} // публикуем значение символа препроцессора в настоящем коде,
               // сохраняем в неизменяемую переменную с таким же именем
println(f"selected UI: {UI}")

запускаем чтобы напечаталось "GUI"

`bin/ficus -run -D UI=GUI preprocess_test.fx`

Автовекторизация/раскрутка циклов в настоящий момент осуществляется только компилятором C/C++, который подхватывает код сгенерированный Фикусом. Наша первая задача - сгенерировать максимально дружественный для векторизации код, не отягощенный динамической типизацией, boxing/unboxing и т.д. Для простых циклов эта задача решается неплохо.

Compile-time выражения не имеют отдельного синтаксиса, но во время компиляции константные выражения подхватываются и заменяются их значениями. Например, при компиляции примера выше про LUT с -O3 (imax - imin) благополучно заменилось на 4080.

Примеров много и в тьюториале на 100 страниц и в подкаталоге examples. Собираюсь написать еще статью, может и не одну, с более подробным обзором фич языка с примерами. Помести я это все в эту статью, объем бы превысил все разумные пределы.

Генерации доков пока нет, это в планах, хотя и не краткосрочных

Ну то есть фактически только избавление от scope clashing. Я думал маленько про другую безопасность, но ладно.


@IFNDEF UI
@DEFINE UI "CLI"
@ENDIF

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


@scope UI {
@define UI "CLI"
} 
или
#[!defined(UI)]
@define UI "CLI"

За f-strings отдельный респект.

Не хотелось бы дополнительно фигурные скобки нагружать, они и так очень хорошо работают в Фикусе. Напротив, хотелось бы чтобы визуально директивы препроцессора хорошо искались и легко обрабатывались, в т.ч. внутри IDE. Вся реализация препроцессора - это отдельный шаг после лексического и до синтаксического анализа, ~400 строчек кода, большая часть которого - это вычисление выражений в @IF/@ELIF/@{}.

Нативная поддержка полноценной линейной алгебры. Этого везде страшно не хватает. NumPy/SciPy, Julia не предлагать.

Будет ли? Или как всегда только несколько самых применяемых и задач и методов?

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

Я понимаю, что это очень сверзадачная сверхзадача :).

В Julia BLAS не нативный(если я правильно вас понял), это всунутая в stdlib обёртка над LAPACK

Нативная поддержка это прежде всего интерфейс средствами языка. Чтобы не надо было для каждого вызова исполнять сложный танец по преобразованию матриц из одного формата в другой, отработка ошибок и предупреждений средствами языка и контроль не выхода за пределы входных и выходных массивов тоже средствами языка.

Как реализовано под капотом это уже вопрос реализации.

Обертки над BLAS и LAPACK в самых ближайших планах. Функций там много, но попробуем обернуть хотя бы часть для generic double-precision матриц (ленточные, верхне/нижнетреугольные пока можно отложить)

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

Именно так. В Фикусе реализованы иммутабельные вектора, добавленные под влиянием доклада Хуана Пуэнте: https://www.youtube.com/watch?v=dbFfpTp3EhA. Для представления потоковых одномерных данных типа звука, текста, финансовых данных и прочее они я думаю вполне бы подошли. Но для изображений, тензоров все-таки пока это не очень эффективно.

Интересный проект, спасибо. Буду следить

По поводу ускорения вычислений на GPU мне вспомнился периодически возникающий диалог:
— Ну что, разобрались, почему нейросеть так тормозила?
— Да. Там число каналов было не кратно четырём, сеть стала вычисляться на GPU, вот и тормоза.

(Речь о том, что «правильный» способ вычислений — использование Apple Neural Engine, а вычисления на GPU мы называем «отсутствием аппаратной акселерации» :) ).

Скажите, пожалуйста, поддерживаются ли массивы структур?

У меня есть небольшой проект по обработке изображений на Java, где нужно преобразовать массив пикселей в массив градиентов (структур типа {magnitude; x_direction; y_direction}). Я использую один массив типа int, в котором последовательно хранятся тройки значений в формате fixed point. Это неудобно, т.к. надо вручную манипулировать индексами, и все данные должны быть одного типа. А вариант с тремя отдельными массивами работает медленнее. Есть ли в вашем языке инструменты для работы с такими данными?

Вы смотрели проект Petalisp - https://github.com/marcoheisig/Petalisp ? Там вроде бы обещают примитивы для работы с многомерными массивами, которые даже лучше, чем примитивы numpy.

Ещё интересное направление - unique типы (Clean/Idris/Rust). В Клине сделали array-comprehension с ними, но в "чистом" языке в том виде, как это есть сейчас, они слишком неудобны, хотя и позволяют легко реализовывать изменяемые массивы.

Спасибо, посмотрел немного на Petalisp. Ну вроде как там есть определенные моменты где он действительно технологически опережает numpy. numpy это чисто библиотека операций над массивами, а тут предоставляются средства для написания собственных циклов, и возможно Лисп'овский компилятор из этого сможет сгенерировать нормальный код. Но синтаксис конечно на любителя Лиспа.

Про unique типы тоже сейчас посмотрел. Если честно, мне это направление не нравится. Я не очень понимаю как с этим работать не натыкаясь постоянно на необходимость делать глубокое копирование чтобы удовлетворить компилятор. Если уж хочется полной функциональной чистоты (хотя такое себе желание), более перспективное направление, с моей точки зрения, это современные неизменяемые структуры данных, такие как RRB вектора и HAMT хэш-таблицы. В Фикусе шаблонный тип 't vector как раз построен на базе RRB. Когда вектор создается с помощью comprehension, то, само собой, он пишется сразу, без создания копий при добавлении каждого элемента, поскольку мы уверены что в момент создания никто больше на него не ссылается:

val rng = RNG(0x12345u64)
// create big vector efficiently,
// no data is reallocated during the process
val bigvec = [< for i <- 0:99999999 {rng.uniform(0.f, 1.f)} >]
val parts = bigvec[:1000] + bigvec[10000:100000] +
            bigvec[1000000:] // reuse parts of the vector,
                             // almost no data is copied
val sz = size(parts)
for i <- 0:1000 {
    val idx = rng.uniform(0, sz)
    // random access is ~O(1) operation
    println(f"{i}. parts[{idx}]={parts[idx]}") 
}

К сожалению, пока аналогов RRB векторов для многомерных массивов не знаю. Вектор из векторов для каждой строки — это слишком неэффективно, да и не нужно, изображения не растут непрерывно вширь и в высоту. Может быть можно что-нибудь соорудить на базе quad-tree, oct-tree и т.д., например, разбить картинку на тайлы, ~128x128 пикселей, и эти тайлы поместить в quad-tree.

Я не очень понимаю как с этим работать не натыкаясь постоянно на необходимость делать глубокое копирование чтобы удовлетворить компилятор.

А и не надо этого делать, если есть средства для выражения view к данным, то бишь возможности передать значение аргументом без того, чтобы оно поглощалось функцией.

Теоретически - да, так и надо поступать. А практически вскоре появляются статьи вроде "времена жизни (lifetimes) — это одна из самых запутанных вещей в Rust, которая часто вызывает затруднение у новичков, даже несмотря на официальную документацию. ..."

Времена жизни в Rust возникают скорее из-за отсутствия сборщика мусора. Для Фикуса это вроде как не является требованием.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Изменить настройки темы

Истории